pyodbc - 非常慢的批量插入速度
Posted
技术标签:
【中文标题】pyodbc - 非常慢的批量插入速度【英文标题】:pyodbc - very slow bulk insert speed 【发布时间】:2011-08-07 07:47:01 【问题描述】:有了这张桌子:
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
的流向是:
【问题讨论】:
尝试使用显式事务。 阅读***.com/questions/1063770/…,pyodbc 似乎不支持显式事务。 我不是这样读的。您关闭自动提交,并且必须显式调用回滚或提交。但是,我不知道它是否会有所作为,但我会自己尝试。 您所描述的正是我的代码所做的。自动提交默认关闭。 我看不出有什么原因会变慢。什么版本的 SQL Server,安装是标准安装,即没有有趣的配置等?喜欢从 USB 等运行数据库?您也可以尝试将 SQL Profiler 附加到数据库,看看您是否可以发现效率低下的原因,但是您在 c# 中的等效代码在我的电脑上执行不到 3 秒。 【参考方案1】:我在使用 executemany() 将 pyODBC 插入 SQL Server 2008 数据库时遇到了类似的问题。当我在 SQL 端运行探查器跟踪时,pyODBC 正在创建一个连接,准备参数化的插入语句,并为一行执行它。然后它会取消准备语句,并关闭连接。然后它对每一行重复这个过程。
我无法在 pyODBC 中找到任何不这样做的解决方案。我最终切换到 ceODBC 来连接 SQL Server,它正确使用了参数化语句。
【讨论】:
感谢您的确认和提示。我已将其归档为code.google.com/p/pyodbc/issues/detail?id=250【参考方案2】:与 Postgres (psycopg2) 和 Oracle (cx_Oracle) 中的批量操作相比,尝试使用 pyodbc 将 +2M 行插入 MSSQL 所花费的时间非常长。我没有使用 BULK INSERT 操作的权限,但是可以通过下面的方法解决问题。
许多解决方案正确地建议了 fast_executemany,但是,正确使用它有一些技巧。首先,我注意到当在 connect 方法中将 autocommit 设置为 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()
【讨论】:
【参考方案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()
【讨论】:
【参考方案4】:pyodbc 4.0.19 添加了Cursor#fast_executemany
选项来帮助解决此问题。详情请见this answer。
【讨论】:
【参考方案5】:我将数据写入文本文件,然后调用 BCP 实用程序。快得多。从大约 20 到 30 分钟到几秒钟。
【讨论】:
【参考方案6】:我使用的是带有 python 3.5 和 Microsoft SQL Server Management Studio 的 pypyODBC。 使用带有 pypyodbc 的 .executemany() 方法插入特定表(约 70K 行 w/40 个变量)需要 112 秒。
使用 ceODBC 需要 4 秒。
【讨论】:
以上是关于pyodbc - 非常慢的批量插入速度的主要内容,如果未能解决你的问题,请参考以下文章
使用 pyodbc 批量插入 SQL Server 表:找不到文件
如何在 CrateDB 中使用 python 执行批量插入?