当前位置: 首页 > 面试题库 >

为什么SQLAlchemy用sqlite插入比直接使用sqlite3慢25倍?

勾炳
2023-03-14
问题内容

为什么这个简单的测试用例用SQLAlchemy插入100,000行比直接使用sqlite3驱动程序慢25倍?我在现实世界的应用程序中看到过类似的减速情况。难道我做错了什么?

#!/usr/bin/env python
# Why is SQLAlchemy with SQLite so slow?
# Output from this program:
# SqlAlchemy: Total time for 100000 records 10.74 secs
# sqlite3:    Total time for 100000 records  0.40 secs


import time
import sqlite3

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String,  create_engine 
from sqlalchemy.orm import scoped_session, sessionmaker

Base = declarative_base()
DBSession = scoped_session(sessionmaker())

class Customer(Base):
    __tablename__ = "customer"
    id = Column(Integer, primary_key=True)
    name = Column(String(255))

def init_sqlalchemy(dbname = 'sqlite:///sqlalchemy.db'):
    engine  = create_engine(dbname, echo=False)
    DBSession.configure(bind=engine, autoflush=False, expire_on_commit=False)
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)

def test_sqlalchemy(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    for i in range(n):
        customer = Customer()
        customer.name = 'NAME ' + str(i)
        DBSession.add(customer)
    DBSession.commit()
    print "SqlAlchemy: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"

def init_sqlite3(dbname):
    conn = sqlite3.connect(dbname)
    c = conn.cursor()
    c.execute("DROP TABLE IF EXISTS customer")
    c.execute("CREATE TABLE customer (id INTEGER NOT NULL, name VARCHAR(255), PRIMARY KEY(id))")
    conn.commit()
    return conn

def test_sqlite3(n=100000, dbname = 'sqlite3.db'):
    conn = init_sqlite3(dbname)
    c = conn.cursor()
    t0 = time.time()
    for i in range(n):
        row = ('NAME ' + str(i),)
        c.execute("INSERT INTO customer (name) VALUES (?)", row)
    conn.commit()
    print "sqlite3: Total time for " + str(n) + " records " + str(time.time() - t0) + " sec"

if __name__ == '__main__':
    test_sqlalchemy(100000)
    test_sqlite3(100000)

我尝试了许多变体(请参阅http://pastebin.com/zCmzDraU)


问题答案:

将更改同步到数据库时,SQLAlchemy
ORM使用工作单元模式。这种模式远远超出了简单的数据“插入”。它包括使用属性检测系统接收在对象上分配的属性,该系统会跟踪对象进行更改时的更改,包括在标识图中跟踪所有插入的行这样做的结果是,对于每行,SQLAlchemy必须检索其“最后插入的ID”(如果尚未给出),并且还涉及对要插入的行进行扫描并根据需要对相关性进行排序。对象也要经过一定程度的记账才能保持所有这些运行,这对于大量行一次而言可能会浪费大量时间处理大型数据结构,因此最好对它们进行分块。

基本上,工作单元是高度自动化的,以实现将复杂对象图持久化到没有显式持久性代码的关系数据库中的任务,并且这种自动化是有代价的。

因此,ORM基本上不适合用于高性能批量插入。这就是SQLAlchemy具有 两个
单独的库的全部原因,如果您查看http://docs.sqlalchemy.org/zh-
CN/latest/index.html
,您会注意到,您会在索引页面上看到两个不同的部分-
一个用于ORM,一个用于Core。如果您不了解两者,就无法有效地使用SQLAlchemy。

对于快速批量插入的用例,SQLAlchemy提供了core,它是ORM在其之上构建的SQL生成和执行系统。有效地使用该系统,我们可以生产出与原始SQLite版本相比具有竞争力的INSERT。下面的脚本对此进行了说明,还提供了一个预分配主键标识符的ORM版本,以便ORM可以使用executemany()插入行。两种ORM版本也一次将刷新记录分块进行,这会对性能产生重大影响。

这里观察到的运行时是:

SqlAlchemy ORM: Total time for 100000 records 16.4133379459 secs
SqlAlchemy ORM pk given: Total time for 100000 records 9.77570986748 secs
SqlAlchemy Core: Total time for 100000 records 0.568737983704 secs
sqlite3: Total time for 100000 records 0.595796823502 sec

脚本:

import time
import sqlite3

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String,  create_engine
from sqlalchemy.orm import scoped_session, sessionmaker

Base = declarative_base()
DBSession = scoped_session(sessionmaker())

class Customer(Base):
    __tablename__ = "customer"
    id = Column(Integer, primary_key=True)
    name = Column(String(255))

def init_sqlalchemy(dbname = 'sqlite:///sqlalchemy.db'):
    global engine
    engine = create_engine(dbname, echo=False)
    DBSession.remove()
    DBSession.configure(bind=engine, autoflush=False, expire_on_commit=False)
    Base.metadata.drop_all(engine)
    Base.metadata.create_all(engine)

def test_sqlalchemy_orm(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    for i in range(n):
        customer = Customer()
        customer.name = 'NAME ' + str(i)
        DBSession.add(customer)
        if i % 1000 == 0:
            DBSession.flush()
    DBSession.commit()
    print "SqlAlchemy ORM: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"

def test_sqlalchemy_orm_pk_given(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    for i in range(n):
        customer = Customer(id=i+1, name="NAME " + str(i))
        DBSession.add(customer)
        if i % 1000 == 0:
            DBSession.flush()
    DBSession.commit()
    print "SqlAlchemy ORM pk given: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"

def test_sqlalchemy_core(n=100000):
    init_sqlalchemy()
    t0 = time.time()
    engine.execute(
        Customer.__table__.insert(),
        [{"name":'NAME ' + str(i)} for i in range(n)]
    )
    print "SqlAlchemy Core: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"

def init_sqlite3(dbname):
    conn = sqlite3.connect(dbname)
    c = conn.cursor()
    c.execute("DROP TABLE IF EXISTS customer")
    c.execute("CREATE TABLE customer (id INTEGER NOT NULL, name VARCHAR(255), PRIMARY KEY(id))")
    conn.commit()
    return conn

def test_sqlite3(n=100000, dbname = 'sqlite3.db'):
    conn = init_sqlite3(dbname)
    c = conn.cursor()
    t0 = time.time()
    for i in range(n):
        row = ('NAME ' + str(i),)
        c.execute("INSERT INTO customer (name) VALUES (?)", row)
    conn.commit()
    print "sqlite3: Total time for " + str(n) + " records " + str(time.time() - t0) + " sec"

if __name__ == '__main__':
    test_sqlalchemy_orm(100000)
    test_sqlalchemy_orm_pk_given(100000)
    test_sqlalchemy_core(100000)
    test_sqlite3(100000)

另请参阅:http :
//docs.sqlalchemy.org/en/latest/faq/performance.html



 类似资料:
  • 我试图理解JTA,并将Bitronix用作首选的事务管理器(只是为了学习和理解)。我正在看《Bitronix参考指南》中的代码,我想知道:如果我使用的是JDBC,它本身就是事务性的(连接可以提交/回滚),为什么我要编写这样的代码?!?! 现在,这段代码的要点可能是简单地演示如何在现有事务性数据存储上使用Bitronix/JTA,但我仍然不知道它提供了什么固有的好处。 然后,这个代码片段让我思考:“

  • 问题内容: 我正在使用较大的随机数作为密钥(来自另一个系统)。在相当小的表(如几百万行)上进行插入和更新所花费的时间比我认为合理的长得多。 我已经提炼了一个非常简单的测试来说明。在测试表中,我尝试使其尽可能简单。我的真实代码没有如此简单的布局,并具有关系和附加索引等。但是,更简单的设置将显示等效的性能。 结果如下: 在MyISAM中插入1M行需要6秒钟;进入InnoDB需要 3433秒 ! 我究竟

  • 问题内容: 当我发现抽象类和接口之间的区别时,这个问题就浮现在脑海中。在这篇文章中,我知道接口很慢,因为它们需要额外的间接访问。但是我没有得到接口而不是抽象类或具体类所需的间接类型,请对此进行澄清。提前致谢 问题答案: 关于性能的神话很多,有些可能在几年前是正确的,而在没有JIT的VM上可能仍然正确。 Android文档(请记住,Android没有JVM,而是Dalvik VM)曾经说过,在接口上

  • 问题内容: 在ARKit中,我发现了2种在hitTest之后插入节点的方法 插入一个ARAnchor,然后在renderer中创建节点(_ renderer:SCNSceneRenderer,nodeFor锚:ARAnchor)-> SCNNode? sceneView.session.add(anchor:anchor) 直接插入节点 sceneView.scene.rootNode.addCh

  • 我可以以大约每秒10,000次插入的速度将插入直接流式传输到BigQuery,但是当我试图使用Dataflow插入时,'tobqrow'步骤(如下所示)非常慢。每10分钟只有50排,这是4名工人。知道为什么吗?以下是相关代码: