pyodbc - 非常慢的批量插入速度

时间:2011-04-17 13:50:33

标签: sql-server bulkinsert pyodbc

使用此表:

CREATE TABLE test_insert (
    col1 INT,
    col2 VARCHAR(10),
    col3 DATE
)

以下代码需要40秒才能运行:

import pyodbc

from datetime import date


conn = pyodbc.connect('DRIVER={SQL Server Native Client 10.0};'
    'SERVER=localhost;DATABASE=test;UID=xxx;PWD=yyy')

rows = []
row = [1, 'abc', date.today()]
for i in range(10000):
    rows.append(row)

cursor = conn.cursor()
cursor.executemany('INSERT INTO test_insert VALUES (?, ?, ?)', rows)

conn.commit()

psycopg2的等效代码只需3秒钟。我不认为mssql比postgresql慢得多。有关如何在使用pyodbc时提高批量插入速度的想法吗?

编辑:在ghoerz发现后添加一些注释

在pyodbc中,executemany的流程为:

  • 准备陈述
  • 每个参数集的
  • 循环
    • 绑定参数集
    • 执行

在ceODBC中,executemany的流量为:

  • 准备陈述
  • 绑定所有参数
  • 执行

6 个答案:

答案 0 :(得分:10)

我在使用executemany()将pyODBC插入SQL Server 2008数据库时遇到了类似的问题。当我在SQL端运行探查器跟踪时,pyODBC正在创建连接,准备参数化的insert语句,并为一行执行它。然后它会对语句毫无准备,并关闭连接。然后它为每一行重复这个过程。

我无法在pyODBC中找到任何没有这样做的解决方案。我最终切换到ceODBC连接到SQL Server,它正确地使用了参数化语句。

答案 1 :(得分:3)

尝试了ceODBC和mxODBC,两者都非常缓慢。在http://www.ecp.cc/pyado.html的帮助下结束adodb连接。总运行时间提高了6倍!

comConn = win32com.client.Dispatch(r'ADODB.Connection')
DSN = 'PROVIDER=Microsoft.Jet.OLEDB.4.0;DATA SOURCE=%s%s' %(dbDIR,dbOut)
comConn.Open(DSN)

rs = win32com.client.Dispatch(r'ADODB.Recordset')
rs.Open('[' + tblName +']', comConn, 1, 3)

for f in values:
    rs.AddNew(fldLST, f)

rs.Update()

答案 2 :(得分:3)

与在Postgres(psycopg2)和Oracle(cx_Oracle)中进行批量操作相比,尝试使用pyodbc在MSSQL中插入+ 2M行要花费大量的时间。我没有使用BULK INSERT操作的特权,但是能够使用下面的方法解决问题。

许多解决方案正确地建议使用fast_executemany,但是,有一些技巧可以正确使用它。首先,我注意到在connect方法中将autocommit设置为True时,pyodbc在每一行之后提交,因此必须将其设置为False。我还观察到一次插入超过2万行时出现非线性减慢,即插入1万行不到一秒,但50k超过20秒。我认为事务日志变得很大,并且使整个过程变慢。因此,必须在每个块之后对插入和提交进行大块处理。我发现每块5k行提供了良好的性能,但这显然取决于许多因素(数据,计算机,数据库配置等)。

import pyodbc

CHUNK_SIZE = 5000

def chunks(l, n):
    """Yield successive n-sized chunks from l."""
    for i in xrange(0, len(l), n): #use xrange in python2, range in python3
        yield l[i:i + n]

mssql_conn = pyodbc.connect(driver='{ODBC Driver 17 for SQL Server}',
                            server='<SERVER,PORT>',
                            timeout=1,
                            port=<PORT>,
                            uid=<UNAME>, 
                            pwd=<PWD>,
                            TDS_Version=7.2,
                            autocommit=False) #IMPORTANT

mssql_cur = mssql_conn.cursor()
mssql_cur.fast_executemany = True #IMPORTANT

params = [tuple(x) for x in df.values]

stmt = "truncate table <THE TABLE>"
mssql_cur.execute(stmt)
mssql_conn.commit()

stmt = """
INSERT INTO <THE TABLE> (field1...fieldn) VALUES (?,...,?)
"""
for chunk in chunks(params, CHUNK_SIZE): #IMPORTANT
    mssql_cur.executemany(stmt, chunk)
    mssql_conn.commit()

答案 3 :(得分:2)

pyodbc 4.0.19添加了Cursor#fast_executemany选项以帮助解决此问题。有关详细信息,请参阅this answer

答案 4 :(得分:1)

我将数据写入文本文件,然后调用BCP实用程序。快得多。从大约20到30分钟到几秒钟。

答案 5 :(得分:0)

我正在使用pypyODBC w / python 3.5和Microsoft SQL Server Management Studio。 使用带有pypyodbc的.executemany()方法,一个特定的表(~70K行w / 40 vars)用112秒进行INSERT。

使用ceODBC需要4秒钟。