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

say*_*yap 21 sql-server bulkinsert pyodbc

有了这个表:

CREATE TABLE test_insert (
    col1 INT,
    col2 VARCHAR(10),
    col3 DATE
)
Run Code Online (Sandbox Code Playgroud)

以下代码需要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()
Run Code Online (Sandbox Code Playgroud)

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

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

在pyodbc中,流程executemany是:

  • 准备声明
  • 循环每组参数
    • 绑定参数集
    • 执行

在ceODBC,流程executemany是:

  • 准备声明
  • 绑定所有参数
  • 执行

小智 11

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

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


小智 5

与 Postgres (psycopg2) 和 Oracle (cx_Oracle) 中的批量操作相比,尝试使用 pyodbc 将 +2M 行插入 MSSQL 花费了荒谬的长时间。我没有使用 BULK INSERT 操作的权限,但能够通过以下方法解决问题。

许多解决方案正确地建议了 fast_executemany,但是,有一些技巧可以正确使用它。首先,我注意到当自动提交在连接方法中设置为 True 时,pyodbc 在每一行之后提交,因此必须将其设置为 False。当一次插入超过 20k 行时,我还观察到非线性减速,即插入 10k 行是亚秒,但 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()
Run Code Online (Sandbox Code Playgroud)