选择语句上的 SQLAlchemy 内存占用
Posted
技术标签:
【中文标题】选择语句上的 SQLAlchemy 内存占用【英文标题】:SQLAlchemy memory hog on select statement 【发布时间】:2010-09-13 10:26:23 【问题描述】:根据 SQLAlchemy,选择语句在 for 循环中被视为可迭代。其效果是,将返回大量行的 select 语句不会使用过多的内存。
我在 mysql 表上发现以下语句:
for row in my_connections.execute(MyTable.__table__.select()):
yield row
似乎没有遵循这一点,因为我溢出了可用内存并在第一行产生之前开始颠簸。我做错了什么?
【问题讨论】:
【参考方案1】:基本的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:zenoss@localhost/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 是 Python 的另一组 MySQL 绑定。 oursql 游标是真正的服务器端游标,fetch rows lazily by default。安装了oursql
,如果你改变了
conn = mysqldb_conn()
到
conn = oursql_conn()
然后two_cursors_one_conn()
运行而不会挂起或引发异常。
【讨论】:
这解决了我使用 MySQL 和 yield_per 的内存问题。知道为什么 Trac 上的回复说这“几乎没用”吗? @bcoughlan:我添加了一些代码并讨论了同时使用 SSCursors 的限制。 这应该可以解决 mysqldb,mysqlconnector 是否有类似的选项,因为我在使用该驱动程序时遇到了类似的问题。 @YogeshSajanikar:我对 mysql-connector 不太熟悉,但根据this page 的说法,mysql-connector 中还没有服务器端游标。解决方法是分块获取数据。以上是关于选择语句上的 SQLAlchemy 内存占用的主要内容,如果未能解决你的问题,请参考以下文章