2014-09-10 78 views
5

當生成大型結果集時,典型的MySQLdb庫查詢可以使用大量內存並且在Python中執行效果很差。例如:將python MySQLDB SScursor與嵌套查詢結合使用

cursor.execute("SELECT id, name FROM `table`") 
for i in xrange(cursor.rowcount): 
    id, name = cursor.fetchone() 
    print id, name 

有一個可選的遊標將獲取在同一時間只有一行,真正加快腳本和切割腳本很多的內存佔用。

import MySQLdb 
import MySQLdb.cursors 

conn = MySQLdb.connect(user="user", passwd="password", db="dbname", 
         cursorclass = MySQLdb.cursors.SSCursor) 
cur = conn.cursor() 
cur.execute("SELECT id, name FROM users") 
row = cur.fetchone() 
while row is not None: 
    doSomething() 
    row = cur.fetchone()  
cur.close() 
conn.close() 

但我找不到任何有關使用SSCursor與嵌套查詢。如果是這樣的doSomething()定義:

def doSomething() 
    cur2 = conn.cursor() 
    cur2.execute('select id,x,y from table2') 
    rows = cur2.fetchall() 
    for row in rows: 
     doSomethingElse(row) 
    cur2.close() 

那麼腳本引發以下錯誤:

_mysql_exceptions.ProgrammingError: (2014, "Commands out of sync; you can't run this command now") 

聽起來好像SSCursor不與嵌套查詢兼容。真的嗎?如果這樣太糟糕了,因爲主循環似乎與標準光標運行速度太慢。

回答

6

這個問題在MySQLdb的用戶指南中商量了一下,the threadsafety attribute(重點煤礦)的標題下:

The MySQL protocol can not handle multiple threads using the same connection at once. Some earlier versions of MySQLdb utilized locking to achieve a threadsafety of 2. While this is not terribly hard to accomplish using the standard Cursor class (which uses mysql_store_result()), it is complicated by SSCursor (which uses mysql_use_result() ; with the latter you must ensure all the rows have been read before another query can be executed.

爲MySLQ C API函數mysql_use_result()文檔提供有關您的錯誤消息的詳細信息:

When using mysql_use_result() , you must execute mysql_fetch_row() until a NULL value is returned, otherwise, the unfetched rows are returned as part of the result set for your next query. The C API gives the error Commands out of sync; you can't run this command now if you forget to do this!

換句話說,你必須完全取任何緩衝的遊標結果集(即一個使用mysql_use_result()代替mysql_store_result() - 與MySQLdb的影響,這就意味着s SSCursorSSDictCursor),然後才能在同一個連接上執行另一條語句。

在您的情況中,最直接的解決方案是在迭代未緩衝查詢的結果集的同時打開第二個連接以供使用。 (從同一個連接中獲取緩衝光標將無法工作;在使用緩衝光標之前,您仍然必須超過無緩衝結果集。)

如果您的工作流類似於「通過循環大的結果集,對每行執行N個小查詢「,考慮將MySQL的存儲過程作爲嵌套來自不同連接的遊標的替代方法。您仍然可以使用MySQLdb調用該過程並獲得結果,但您肯定會想要read the documentation of MySQLdb's callproc() method,因爲它在檢索過程輸出時不符合Python的database API specs


第二種方法是堅持緩衝遊標,但將您的查詢拆分成批。這就是去年我爲某個項目所做的工作,我需要循環訪問數百萬行,使用內部模塊解析一些數據,並在處理每一行後執行一些INSERTUPDATE查詢。總的想法看起來是這樣的:

QUERY = r"SELECT id, name FROM `table` WHERE id BETWEEN %s and %s;" 
BATCH_SIZE = 5000 

i = 0 
while True: 
    cursor.execute(QUERY, (i + 1, i + BATCH_SIZE)) 
    result = cursor.fetchall() 

    # If there's no possibility of a gap as large as BATCH_SIZE in your table ids, 
    # you can test to break out of the loop like this (otherwise, adjust accordingly): 
    if not result: 
     break 

    for row in result: 
     doSomething() 

    i += BATCH_SIZE 

一個我想指出你的示例代碼的其他事情是,你可以在MySQLdb的直接迭代的光標,而不是調用fetchone()明確了xrange(cursor.rowcount)。這在使用非緩衝光標時尤其重要,因爲rowcount屬性未定義,並且會給出非常意外的結果(請參閱:Python MysqlDB using cursor.rowcount with SSDictCursor returning wrong count)。

+0

蟒蛇 - 如此正確,如此緩慢在同一時間! – shigeta 2014-09-12 11:33:54