加速从pyodbc插入SQL Server

时间:2016-10-15 15:27:30

标签: python pyodbc

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()

2 个答案:

答案 0 :(得分:4)

更新: 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事件探查器确认)

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'

因此,对于.executemany"批次"你将是10,000行

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

可能让pyodbc发送初始sp_prepare然后执行.executemany调用sp_execute,但.executemany的性质是您仍然会执行10,000 sp_prepexec次来电,只执行sp_execute而不是INSERT INTO ...。如果SQL语句很长很复杂,那么这可以提高性能,但对于像你问题中的例子这样的简短版本,它可能不会产生那么大的差异。

还可以创造性地构建"表值构造函数"如this answer所示,但请注意,它仅作为"计划B"当本机批量插入机制不可行时。

答案 1 :(得分:0)

您已经在使用executemany()了。 [在阅读其他答案后被淘汰。]

如果您在while循环之外移动connect()cursor() insert_cnxninsert_cursor来调用它,则应加快(非常小)位。 (当然,如果你这样做,你也应该在循环之外移动2个相应的close()调用。)除了不必每次都重新建立连接,重新使用游标将阻止每次都重新编译SQL。

但是,你可能不会因此而看到一个巨大的加速,因为你可能只需要通过该循环进行约10次传递(假设你说每天大约100,000次,你的循环组合在一起)一次10,000个。)

您可能会关注的另一件事是,是否有任何"幕后花絮"正在对您的OrderDate参数进行转换。您可以转到SQL Server Management Studio并查看查询的执行计划。 (通过右键单击服务器节点并选择" Activity Monitor"在"最近的昂贵查询"列表中查找插入查询;右键单击插入查询并查看其执行计划。)