【问题标题】:sqlalchemy bulk insert is slower than building raw SQLsqlalchemy 批量插入比构建原始 SQL 慢
【发布时间】:2018-01-11 01:21:20
【问题描述】:

我正在通过this article 了解 sqlalchemy 批量插入性能。我尝试了基准测试中指定的各种方法 - SQLAlchemy ORM bulk_insert_mappings()SQLAlchemy Core。不幸的是,对于插入 1000 行,所有这些方法都需要大约 1 分钟来插入它们。这是非常缓慢的。我还尝试了here 指定的方法 - 这需要我构建一个大型 SQL 语句,例如:

INSERT INTO mytable (col1, col2, col3)
VALUES (1,2,3), (4,5,6) ..... --- up to 1000 of these

这个原始 SQL 的插入是这样的:

MySession.execute('''
insert into MyTable (e, l, a)
values {}
'''.format(",".join(my_insert_str)))

使用这种方法,我在 10-11 秒内将性能提高了 50 倍以上,达到 10000 次插入。

这是使用内置库的方法的代码。

class MyClass(Base):
    __tablename__ = "MyTable"
    e = Column(String(256), primary_key=True)
    l = Column(String(6))
    a = Column(String(20), primary_key=True)

    def __repr__(self):
        return self.e + " " + self.a+ " " + self.l

.......

        dict_list = []
        for i, row in chunk.iterrows():

            dict_list += [{"e" : row["e"], "l" : l, "a" : a}]

        MySession.execute(
            Myclass.__table__.insert(),
            dict_list
        )

这是我连接到数据库的方式。

    params = urllib.quote_plus("DRIVER={SQL Server Native Client 10.0};SERVER=servername;DATABASE=dbname;UID=user;PWD=pass")
    engine = create_engine("mssql+pyodbc:///?odbc_connect=%s" % params )
    MySession.configure(bind=engine, autoflush=False, expire_on_commit=False)

我的设置是否存在大幅降低性能的问题?我尝试使用不同的数据库驱动程序 - pyodbc 和 pymssql。无论我尝试什么,我都无法接近他们在文章中声称的数字:

SQLAlchemy ORM: Total time for 100000 records 2.192882061 secs
SQLAlchemy ORM pk given: Total time for 100000 records 1.41679310799 secs
SQLAlchemy ORM bulk_save_objects(): Total time for 100000 records 0.494568824768 secs
SQLAlchemy ORM bulk_insert_mappings(): Total time for 100000 records 0.325763940811 secs
SQLAlchemy Core: Total time for 100000 records 0.239127874374 secs
sqlite3: Total time for 100000 records 0.124729156494 sec

我正在连接到 MS SQL Server 2008。如果我遗漏了任何其他详细信息,请告诉我。

原始 SQL 方法的问题在于它不是 SQL 注入安全的。因此,或者,如果您有解决此问题的建议,它也会非常有帮助:)。

【问题讨论】:

标签: python sql-server performance sqlalchemy bulkinsert


【解决方案1】:

你在做

MySession.execute(
    Myclass.__table__.insert(),
    dict_list
)

使用executemany()。它与INSERT INTO ... VALUES ... 不同。要使用VALUES,请执行以下操作:

MySession.execute(
    Myclass.__table__.insert().values(dict_list)
)

附带说明,SQL 注入问题是使用参数解决的:

MySession.execute('''
insert into MyTable (e, l, a)
values (?, ?, ?), (?, ?, ?), ...
''', params)

这里的要点是您没有比较等效的结构。您没有在 SQLAlchemy 生成的查询中使用VALUES,而是在您的文本 SQL 中,并且您没有在文本 SQL 中使用参数化,但您在 SQLAlchemy 生成的查询中。如果您为已执行的 SQL 语句打开日志记录,您将确切地看到不同之处。

【讨论】:

  • 并不是说它适用于所有情况,尤其是。在这里,但是编译一个insert().values() proved to take even longer in one case。执行本身比预期的更快。应该对此进行更多研究。
  • @IljaEverilä 这是一个很好的观点。对于像这样的大型数据集,我倾向于将 CSV 流式传输到COPY,就像您的答案一样,只是没有在内存中生成整个文件。不过,对于大约 1k 个条目,使用 VALUES 的开销应该相对较小。
  • @univerio 感谢您的回答,我实际上最终使用了参数来防止 SQL 注入,到目前为止,我认为这给了我最好的性能。很高兴看到你也提出了这个建议!我将再次检查 .insert().values(dict_list) 尽管我现在对任何内置方法都持怀疑态度:D
  • 性能文章如何在 100,000 次插入中获得
猜你喜欢
  • 2017-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-03
  • 2011-04-09
  • 2014-10-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多