2010-09-13 37 views
9

根據SQLAlchemy,select語句被視爲for循環中的迭代器。結果是,將返回大量行的select語句不會使用過多的內存。SELECT語句上的SQLAlchemy內存管理器

我發現,在一個MySQL表如下聲明:

for row in my_connections.execute(MyTable.__table__.select()): 
    yield row 

似乎並沒有遵循這一點,因爲我溢出可用內存和第一行產生之前就開始抖動。我究竟做錯了什麼?

回答

12

基本的MySQLdb遊標一次從服務器獲取整個查詢結果。 這會消耗大量的內存和時間。 使用MySQLdb.cursors.SSCursor當你想做一個巨大的查詢和 從服務器一次拉取結果。

因此,試圖通過connect_args={'cursorclass': MySQLdb.cursors.SSCursor} 創建engine時:

from sqlalchemy import create_engine, MetaData 
    import MySQLdb.cursors 
    engine = create_engine('mysql://root:[email protected]/e2', connect_args={'cursorclass': MySQLdb.cursors.SSCursor}) 
    meta = MetaData(engine, reflect=True) 
    conn = engine.connect() 
    rs = s.execution_options(stream_results=True).execute() 

http://www.sqlalchemy.org/trac/ticket/1089


注意,使用SSCursor鎖定表,直到提取完成。這將影響使用相同連接的其他遊標:來自同一連接的兩個遊標無法同時從表中讀取。

但是,來自不同連接的遊標可以同時從同一張表中讀取。

下面是一些代碼演示問題:

import MySQLdb 
import MySQLdb.cursors as cursors 
import threading 
import logging 
import config 

logger = logging.getLogger(__name__) 
query = 'SELECT * FROM huge_table LIMIT 200' 

def oursql_conn(): 
    import oursql 
    conn = oursql.connect(
     host=config.HOST, user=config.USER, passwd=config.PASS, 
     db=config.MYDB) 
    return conn 

def mysqldb_conn(): 
    conn = MySQLdb.connect(
     host=config.HOST, user=config.USER, 
     passwd=config.PASS, db=config.MYDB, 
     cursorclass=cursors.SSCursor) 
    return conn 

def two_cursors_one_conn(): 
    """Two SSCursors can not use one connection concurrently""" 
    def worker(conn): 
     cursor = conn.cursor() 
     cursor.execute(query) 
     for row in cursor: 
      logger.info(row) 

    conn = mysqldb_conn() 
    threads = [threading.Thread(target=worker, args=(conn,)) 
       for n in range(2)] 
    for t in threads: 
     t.daemon = True 
     t.start() 
     # Second thread may hang or raise OperationalError: 
     # File "/usr/lib/pymodules/python2.7/MySQLdb/cursors.py", line 289, in _fetch_row 
     # return self._result.fetch_row(size, self._fetch_type) 
     # OperationalError: (2013, 'Lost connection to MySQL server during query') 

    for t in threads: 
     t.join() 

def two_cursors_two_conn(): 
    """Two SSCursors from independent connections can use the same table concurrently"""  
    def worker(): 
     conn = mysqldb_conn()   
     cursor = conn.cursor() 
     cursor.execute(query) 
     for row in cursor: 
      logger.info(row) 

    threads = [threading.Thread(target=worker) for n in range(2)] 
    for t in threads: 
     t.daemon = True 
     t.start() 
    for t in threads: 
     t.join() 


logging.basicConfig(level=logging.DEBUG, 
        format='[%(asctime)s %(threadName)s] %(message)s', 
        datefmt='%H:%M:%S') 
two_cursors_one_conn() 
two_cursors_two_conn() 

注意oursql是另一套的MySQL綁定的Python。 oursql遊標是真正的服務器端遊標,其中fetch rows lazily by default。在安裝oursql,如果你改變

conn = mysqldb_conn() 

conn = oursql_conn() 

然後two_cursors_one_conn()運行沒有懸掛或引發異常。

+0

這解決了我的內存問題與MySQL和yield_per。任何想法爲什麼Trac的回覆說這是「近乎無用」? – bcoughlan 2013-12-10 15:34:12

+1

@bcoughlan:我已經添加了一些代碼並討論了同時使用SSCursors的限制。 – unutbu 2013-12-10 17:35:42

+0

這應該解決mysqldb,有沒有類似的選項爲mysqlconnector,因爲我面臨類似的問題,使用該驅動程序。 – 2014-11-07 08:42:21