2013-07-09 78 views
4

作爲構建數據倉庫的一部分,我必須查詢大約75M行的源數據庫表。與從CSV文件導出和導入相比,Python MySQLdb SScursor速度較慢。可以加速嗎?

我想對75M行做什麼是一些處理,然後將結果添加到另一個數據庫。現在,這是相當多的數據,我主要有兩種方法取得了成功:

1)使用MySQL的「SELECT ... INTO」功能將查詢導出到CSV文件,並使用文件輸入2)使用MySQLdb的SScursor(默認遊標將查詢放入內存中,殺死python腳本)連接到MySQL數據庫並以大約10k行的塊形式獲取結果(其中是我發現最快的塊大小)。

第一種方法是「手動」執行的SQL查詢(大約需要6分鐘),然後是一個讀取csv文件並處理它的python腳本。我使用fileinput來讀取文件的原因是fileinput不會從頭開始將整個文件加載到內存中,並且對於較大的文件運行良好。只需遍歷文件(讀取文件中的每一行並調用傳遞)需要大約80秒,即1M行/秒。

第二種方法是執行相同查詢的python腳本(也需要大約6分鐘,或稍微長一些),然後只要在SScursor中留下任何剩餘的行,就可以獲取行的塊。在這裏,只要讀取這些行(一個接一個地讀取數據塊而不做其他任何事)需要大約15分鐘,即大約85k行/秒。

上面的兩個數字(行/秒)可能並沒有真正的可比性,但是在我的應用程序中對兩種方法進行基準測試時,第一個需要大約20分鐘(其中大約五分鐘是MySQL轉儲到CSV文件中),第二個大約需要35分鐘(其中大約五分鐘是正在執行的查詢)。這意味着從CSV文件轉儲和讀取數據的速度是直接使用SScursor的兩倍。

如果不限制我的系統的可移植性,這將不成問題:「SELECT ... INTO」語句要求MySQL具有寫入權限,並且我懷疑這不像使用遊標那樣安全。另一方面,15分鐘(隨着源數據庫的增長而增長)並不是我在每次構建時都可以節省的東西。

所以,我錯過了什麼?是否有任何已知的原因讓SScursor比從CSV文件轉儲/讀取要慢得多,例如SScursor不是用於C優化的文件輸入?關於如何解決這個問題的任何想法?任何要測試的東西?我相信SScursor可以像第一種方法一樣快,但在閱讀完所有關於此事的信息後,我很難過。現在

,到代碼:

不,我想查詢任何問題(這是一樣快,我可以要求並採取類似的時間在這兩種方法),但在這裏它是着想完整性:

SELECT LT.SomeID, LT.weekID, W.monday, GREATEST(LT.attr1, LT.attr2) 
    FROM LargeTable LT JOIN Week W ON LT.weekID = W.ID 
    ORDER BY LT.someID ASC, LT.weekID ASC; 

在第一方法的主要代碼是這樣

import fileinput 
    INPUT_PATH = 'path/to/csv/dump/dump.csv' 
    event_list = [] 
    ID = -1 

    for line in fileinput.input([INPUT_PATH]): 
      split_line = line.split(';') 
      if split_line[0] == ID: 
       event_list.append(split_line[1:]) 
      else: 
       process_function(ID,event_list) 
       event_list = [ split_line[1:] ] 
       ID = split_line[0] 

    process_function(ID,event_list) 

在第二方法的主要代碼是:

import MySQLdb 
    ...opening connection, defining SScursor called ssc... 
    CHUNK_SIZE = 100000 

    query_stmt = """SELECT LT.SomeID, LT.weekID, W.monday, 
        GREATEST(LT.attr1, LT.attr2) 
        FROM LargeTable LT JOIN Week W ON LT.weekID = W.ID 
        ORDER BY LT.someID ASC, LT.weekID ASC""" 
    ssc.execute(query_stmt) 

    event_list = [] 
    ID = -1 

    data_chunk = ssc.fetchmany(CHUNK_SIZE) 
    while data_chunk: 
     for row in data_chunk: 
      if row[0] == ID: 
       event_list.append([ row[1], row[2], row[3] ]) 
      else: 
       process_function(ID,event_list) 
       event_list = [[ row[1], row[2], row[3] ]] 
       ID = row[0] 
     data_chunk = ssc.fetchmany(CHUNK_SIZE) 

    process_function(ID,event_list) 

最後,我在Ubuntu 13.04上使用MySQL服務器5.5.31。我使用Python 2.7.4和MySQLdb 1.2.3。感謝你和我在一起這麼久!

+0

既然您沒有代表回答您自己的問題,我已將您的問題的部分似乎更像解決方案移到社區維基答案中。如果您有更多的改進來共享和/或接受它,如果它或多或少是完整的,您可以編輯這個wiki答案。 – Air

回答

1

使用cProfile後,我發現花費了大量的時間隱含建設十進制的對象,因爲這是從SQL查詢返回到我的Python腳本數字類型。在第一種方法中,Decimal值以整數形式寫入CSV文件,然後通過Python腳本讀取。 CSV文件I/O「展平」數據,使腳本更快。現在這兩個腳本的速度大致相同(第二種方法仍然稍慢一點)。

我也做了一些在MySQL數據庫中的日期到整數類型的轉換。我的查詢是:

SELECT LT.SomeID, 
     LT.weekID, 
     CAST(DATE_FORMAT(W.monday,'%Y%m%d') AS UNSIGNED), 
     CAST(GREATEST(LT.attr1, LT.attr2) AS UNSIGNED) 
FROM LargeTable LT JOIN Week W ON LT.weekID = W.ID 
ORDER BY LT.someID ASC, LT.weekID ASC; 

這幾乎消除了兩種方法之間的處理時間差異。

這裏的教訓是,在做大量查詢時,數據類型的後處理確實重要!在Python中將查詢重寫爲減少函數調用可顯着提高總體處理速度。