查询大型 SQL Server 表时,pymssql/pyodbc 性能(cursor.execute)非常慢
Posted
技术标签:
【中文标题】查询大型 SQL Server 表时,pymssql/pyodbc 性能(cursor.execute)非常慢【英文标题】:pymssql/pyodbc performance (cursor.execute) is very slow when querying large SQL Server table 【发布时间】:2020-01-07 14:17:07 【问题描述】:我们对 SQL Server 有很大的了解(大约有 5 亿条记录)。由于它不适合内存,我正在考虑使用 fetchmany 进行块处理,如下所示:
with pymssql.connect(host, user, pass, db) as conn:
query = f"SELECT * FROM view_name;"
with conn.cursor() as cursor, futures.ThreadPoolExecutor(3) as executor:
cursor.execute(query)
chunk_size = 5000
data = cursor.fetchmany(chunk_size)
while data:
future_rs = executor.submit(process_chunk, data)
data = cursor.fetchmany(chunk_size)
但是,看起来 cursor.execute
实际上会在我调用 fetchmany 之前尝试获取所有行,因为它非常慢。
我对文档的理解是cursor.execute
应该只准备查询而不是实现完整结果?
您将如何在可管理的时间内处理如此大的表格/视图?
PS:我也试过pyodbc
,同样的问题。正如预期的那样,将查询更改为 select top 100 * from view_name
很快。
【问题讨论】:
您为什么要尝试将 5 亿行返回到您的应用程序?那是不可用的。如果这是一份工作,那么我预计它需要很长时间才能处理 5 亿行。但是 execute 方法会执行它所声明的操作,它会执行您传递给它的查询。 是的,返回 100 行所需的时间将少于 500,000,000...execute
表示执行,就像您认为的那样。您似乎“想要”在这里稍微改变一下单词的定义...
查看this post 了解有关分页的一些想法。
这是一个批处理,而不是应用程序。我需要稍微处理一下这些数据并移动到不同的地方。我认为执行是懒惰的,因为有可用的 fetchall 和 fetchmeny 方法。如果完整的结果已经可用,我认为调用 fetchall 没有任何意义。它似乎只是将结果转换为列表。
我不能代表 pymssql,但 pyodbc 绝对不会获取 .execute
上的所有行。使用默认事务隔离 (READ_COMMITTED) 的快速 Wireshark 测试显示——连接到 SQL Server:8_960 字节;执行“SELECT * FROM MillionRows”:82_310字节; fetchmany(5_000): 314_810 字节; fetchmany(995_000): 62_564_304 字节
【参考方案1】:
好的,经过一段时间的调试,我有一个解决方案。
部分问题原来是非常缓慢的底层视图。我误判了这一点,因为像 DBeaver 这样的数据库客户端返回结果非常快(可能是因为它将分页应用于后台查询?)。无论如何,我试图用cursor.fetchmany
做的事情,我用数据库功能做到了。
SQL Server 12 及更高版本使用OFFSET
和FETCH NEXT
具有非常好的分页功能。所以我的解决方案看起来像这样:
offset = 0
offset_increment = 200000
def get_chunk(cursor, offset):
query = f"""
SELECT * FROM table ORDER BY some_col
OFFSET offset ROWS FETCH NEXT offset_incriment ROWS ONLY;
"""
return cursor.execute(query).fetchall()
with futures.ThreadPoolExecutor(6) as executor:
chunk = get_chunk(query, offset)
while chunk:
executor.submit(process_chunk, chunk)
offset += offset_increment
chunk = get_chunk(query, offset)
所以这里的实现是:
使用带有OFFSET
和FETCH NEXT
的SQL Server 分页功能仅获取有限数量的行。
使用多个线程并行处理块。您还可以并行化 SQL 查询执行部分以使其更快。这需要更多的工作,因为您需要知道何时停止。
这是我的解决方案背后的基本思想。上面的代码只是一个例子,实际上我必须根据资源使用情况(主要是内存)在我的项目中做更多的调整。您也可以ProcessPoolExecutor
进行多处理而不是线程。想法是一样的,代码需要一些更改,因为多处理只需要可挑选的对象。
因此,在块中同时使用分页和处理结果,您可以非常轻松地处理大型表/视图:)
【讨论】:
这是否省略了前 200000 条记录?偏移量最初是否应该设置为零?以上是关于查询大型 SQL Server 表时,pymssql/pyodbc 性能(cursor.execute)非常慢的主要内容,如果未能解决你的问题,请参考以下文章