查询大型 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 及更高版本使用OFFSETFETCH 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)

所以这里的实现是:

使用带有OFFSETFETCH NEXT 的SQL Server 分页功能仅获取有限数量的行。 使用多个线程并行处理块。您还可以并行化 SQL 查询执行部分以使其更快。这需要更多的工作,因为您需要知道何时停止。

这是我的解决方案背后的基本思想。上面的代码只是一个例子,实际上我必须根据资源使用情况(主要是内存)在我的项目中做更多的调整。您也可以ProcessPoolExecutor 进行多处理而不是线程。想法是一样的,代码需要一些更改,因为多处理只需要可挑选的对象。

因此,在块中同时使用分页和处理结果,您可以非常轻松地处理大型表/视图:)

【讨论】:

这是否省略了前 200000 条记录?偏移量最初是否应该设置为零?

以上是关于查询大型 SQL Server 表时,pymssql/pyodbc 性能(cursor.execute)非常慢的主要内容,如果未能解决你的问题,请参考以下文章

在打开sqlservice数据库中的每一个数据库或者是表时总是出现目录名无效,该怎么解决呢?

大型sql server查询性能优化

sql如何在创建表时设置外键

提高大型表上的 SQL Server 查询性能

在 SQL Server 中调整大型查询

SQL Server 中对大型数据集的慢速不同查询