Pandas to_sql() 在一个 DataFrame 上慢,但在其他 DataFrame 上快

Pandas to_sql() slow on one DataFrame but fast on others

目标

我正在尝试使用 pandas DataFrame.to_sql() 将大型 DataFrame(>1M 行)发送到 MS SQL 服务器数据库。

问题

该命令在一个特定的 DataFrame 上明显较慢,发送 10,000 行大约需要 130 秒。相比之下,类似的 DataFrame 仅需 7 秒 即可发送相同数量的行。后者的 DataFrame 实际上有更多的列,以及更多的数据 df.memory_usage(deep=True).

详情

SQLAlchemy 引擎是通过

创建的

engine = create_engine('mssql+pyodbc://@<server>/<db>?driver=ODBC+Driver+17+for+SQL+Server', fast_executemany=True)

to_sql()调用如下: df[i:i+chunksize].to_sql(table, conn, index=False, if_exists='replace') 其中 chunksize = 10000.

我试图通过 cProfile 定位瓶颈,但这只表明几乎所有时间都花在了 pyodbc.Cursor.executemany

如有任何调试提示,我们将不胜感激!

原因

性能差异是由于 pyodbc 中的 issue 在使用 [=14] 时将 None 值传递给 SQL 服务器 INSERT 语句=] 选项会导致速度下降。

解决方案

我们可以将值打包为 JSON 并使用 OPENJSON(SQL Server 2016+ 支持)而不是 fast_executemany。该解决方案使我的应用程序性能提高了 30 倍!这是一个 self-contained 示例,基于文档 here,但适用于 pandas 用户。

import pandas as pd
from sqlalchemy import create_engine

df = pd.DataFrame({'First Name': ['Homer', 'Ned'], 'Last Name': ['Simpson', 'Flanders']})
rows_as_json = df.to_json(orient='records')

server = '<server name>'
db = '<database name>'
table = '<table name>'
engine = create_engine(f'mssql+pyodbc://<user>:<password>@{server}/{db}')

sql = f'''
INSERT INTO {table} ([First Name], [Last Name])
SELECT [First Name], [Last Name] FROM
    OPENJSON(?)
    WITH (
        [First Name] nvarchar(50) '$."First Name"',
        [Last Name] nvarchar(50) '$."Last Name"'
    )
'''

cursor = engine.raw_connection().cursor()
cursor.execute(sql, rows_as_json)
cursor.commit()

替代解决方法

  • 将数据导出为 CSV 并使用外部工具完成传输(例如 bcp utility)。
  • 将转换为 None 的值人工替换为 non-empty 填充值,添加辅助列以指示更改了哪些行,通过 to_sql() 正常完成操作,然后通过基于辅助列的单独查询将填充值重置为 Null

致谢

非常感谢@GordThompson 指出我的解决方案!