【问题标题】:Speed up inserts into SQL Server from pyodbc加快从 pyodbc 插入 SQL Server 的速度
【发布时间】:2016-10-15 15:27:29
【问题描述】:

python 中,我有一个从一个数据库中选择数据的过程(Redshift 通过psycopg2),然后将该数据插入SQL Server(通过pyodbc)。我选择进行读/写而不是读/平面文件/加载,因为行数约为每天 100,000。似乎更容易简单地连接和插入。但是 - 插入过程很慢,需要几分钟。

有没有更好的方法使用 Pyodbc 将数据插入 SQL Server?

select_cursor.execute(output_query)

done = False
rowcount = 0

while not done:
    rows = select_cursor.fetchmany(10000)

    insert_list = []

    if rows == []:
        done = True
        break

    for row in rows:
        rowcount += 1

        insert_params = (
            row[0], 
            row[1], 
            row[2]
            )

        insert_list.append(insert_params)            

    insert_cnxn = pyodbc.connect('''Connection Information''')

    insert_cursor = insert_cnxn.cursor()

    insert_cursor.executemany("""
        INSERT INTO Destination (AccountNumber, OrderDate, Value)
        VALUES (?, ?, ?)
        """, insert_list)

    insert_cursor.commit()
    insert_cursor.close()
    insert_cnxn.close()

select_cursor.close()
select_cnxn.close()

【问题讨论】:

    标签: python sql-server pyodbc


    【解决方案1】:

    更新: pyodbc 4.0.19 添加了一个Cursor#fast_executemany 选项,通过避免下面描述的行为可以大大提高性能。详情请见this answer


    您的代码确实遵循正确的形式(除了另一个答案中提到的一些小调整),但请注意,当 pyodbc 执行 .executemany 时,它实际上所做的是为每个单独的行提交一个单独的 sp_prepexec。也就是对于代码

    sql = "INSERT INTO #Temp (id, txtcol) VALUES (?, ?)"
    params = [(1, 'foo'), (2, 'bar'), (3, 'baz')]
    crsr.executemany(sql, params)
    

    SQL Server 实际执行以下操作(由 SQL Profiler 确认)

    exec sp_prepexec @p1 output,N'@P1 bigint,@P2 nvarchar(3)',N'INSERT INTO #Temp (id, txtcol) VALUES (@P1, @P2)',1,N'foo'
    exec sp_prepexec @p1 output,N'@P1 bigint,@P2 nvarchar(3)',N'INSERT INTO #Temp (id, txtcol) VALUES (@P1, @P2)',2,N'bar'
    exec sp_prepexec @p1 output,N'@P1 bigint,@P2 nvarchar(3)',N'INSERT INTO #Temp (id, txtcol) VALUES (@P1, @P2)',3,N'baz'
    

    因此,对于 10,000 行的 .executemany“批处理”,您将是

    • 执行 10,000 个单独的插入,
    • 与服务器往返 10,000 次,并且
    • 发送相同的 SQL 命令文本 (INSERT INTO ...) 10,000 次。

    可能让 pyodbc 发送一个初始的sp_prepare,然后调用sp_execute 调用.executemany,但.executemany 的本质是你仍然会调用10,000 @987654335 @ 调用,只执行 sp_execute 而不是 INSERT INTO ...。如果 SQL 语句相当长且复杂,这可能会提高性能,但对于像您问题中的示例这样的简短语句,它可能不会有太大的不同。

    您也可以发挥创意并构建“表值构造函数”,如 this answer 所示,但请注意,当原生批量插入机制不是可行的解决方案时,它仅作为“B 计划”提供。

    【讨论】:

    • 哇。我不知道executemany 这样做了。那么有什么(与性能相关的)理由更喜欢它而不是单独调用execute
    • 不,我没有看到任何证据表明.executemany 与带有单独.execute 调用的显式循环有任何不同,至少在Windows 上使用SQL Server ODBC 驱动程序。 pymssql 在执行.executemany 时为每一行发送单独构造的INSERT 语句,因此看起来.executemany 实际上只是一个编码快捷方式,至少对于SQL Server 是这样。 (OTOH,根据documentation,MySQL 连接器/Python 实际上确实为.executemany 重写了INSERTs。)
    【解决方案2】:

    很高兴您已经在使用executemany() [阅读其他答案后感到震惊。]

    如果您将connect()cursor()insert_cnxninsert_cursor 的调用移到while 循环之外,它应该会加快一点速度。 (当然,如果您这样做,您还应该将 2 个相应的 close() 调用也移到循环之外。)除了不必每次都(重新)建立连接之外,重新使用光标会阻止每次都必须重新编译 SQL。

    但是,您可能不会看到由此带来的巨大速度提升,因为无论如何您可能只通过该循环约 10 次(假设您说每天约 100,000 次,并且您的循环一次组合 10,000 次) )。

    您可能会考虑的另一件事是您的OrderDate 参数是否进行了任何“幕后”转换。您可以转到 SQL Server Management Studio 并查看查询的执行计划。 (通过右键单击服务器节点并选择“活动监视器”在“最近昂贵的查询”列表中查找您的插入查询;右键单击插入查询并查看其执行计划。)

    【讨论】:

      猜你喜欢
      • 2019-07-24
      • 2015-06-20
      • 1970-01-01
      • 2018-07-12
      相关资源
      最近更新 更多