Release: 1.4.25 | Release Date: September 22, 2021

SQLAlchemy 1.4 Documentation

其他持久性技术

将SQL插入/更新表达式嵌入到刷新中

此功能允许将数据库列的值设置为SQL表达式,而不是文字值。它对于原子更新、调用存储过程等特别有用。您所要做的就是为属性分配一个表达式::

class SomeClass(Base):
    __tablename__ = "some_table"

    # ...

    value = Column(Integer)

someobject = session.query(SomeClass).get(5)

# set 'value' attribute to a SQL expression adding one
someobject.value = SomeClass.value + 1

# issues "UPDATE some_table SET value=value+1"
session.commit()

这种技术对INSERT和UPDATE语句都有效。执行刷新/提交操作后, value 属性对 someobject 上面的已过期,因此下次访问时,将从数据库加载新生成的值。

该功能还具有与主键列一起使用的条件支持。一个支持返回的数据库,例如PostgreSQL、Oracle或SQL Server,或者作为一种特殊情况,当使用带pysqlite驱动程序的sqlite和一个自动递增列时,也可以将一个SQL表达式分配给主键列。这既允许计算SQL表达式,也允许在插入时修改主键值的任何服务器端触发器被ORM成功检索为对象主键的一部分:

class Foo(Base):
    __tablename__ = 'foo'
    pk = Column(Integer, primary_key=True)
    bar = Column(Integer)

e = create_engine("postgresql://scott:tiger@localhost/test", echo=True)
Base.metadata.create_all(e)

session = Session(e)

foo = Foo(pk=sql.select(sql.func.coalesce(sql.func.max(Foo.pk) + 1, 1))
session.add(foo)
session.commit()

在PostgreSQL上,上面 Session 将发出以下插入:

INSERT INTO foo (foopk, bar) VALUES
((SELECT coalesce(max(foo.foopk) + %(max_1)s, %(coalesce_2)s) AS coalesce_1
FROM foo), %(bar)s) RETURNING foo.foopk

1.3 新版功能: SQL表达式现在可以在ORM刷新期间传递到主键列;如果数据库支持返回,或者如果使用了pysqlite,ORM将能够检索服务器生成的值作为主键属性的值。

在会话中使用SQL表达式

SQL表达式和字符串可以通过 Session 在其事务上下文中。使用 Session.execute() 方法,它返回 CursorResult 以与 EngineConnection ::

Session = sessionmaker(bind=engine)
session = Session()

# execute a string statement
result = session.execute("select * from table where id=:id", {'id':7})

# execute a SQL expression construct
result = session.execute(select(mytable).where(mytable.c.id==7))

当前 ConnectionSession 可以使用 Session.connection() 方法:

connection = session.connection()

上面的例子涉及 Session 那一定是单身 EngineConnection . 使用 Session 它要么绑定到多个引擎,要么完全不绑定(即依赖绑定的元数据),两者都是 Session.execute()Session.connection() 接受绑定参数字典 Session.execute.bind_arguments 其中可能包括传递映射类的“mapper”或 Mapper 实例,用于定位所需引擎的适当上下文:

Session = sessionmaker()
session = Session()

# need to specify mapper or class when executing
result = session.execute(
    text("select * from table where id=:id"),
    {'id':7},
    bind_arguments={'mapper': MyMappedClass}
)

result = session.execute(
    select(mytable).where(mytable.c.id==7),
    bind_arguments={'mapper': MyMappedClass}
)

connection = session.connection(MyMappedClass)

在 1.4 版更改: 这个 mapperclause 论据 Session.execute() 现在作为字典的一部分传递给 Session.execute.bind_arguments 参数。前面的参数仍然被接受,但是这种用法已被弃用。

对具有默认值的列强制空值

ORM将从未在对象上设置的任何属性视为“默认”情况;该属性将从insert语句中省略::

class MyObject(Base):
    __tablename__ = 'my_table'
    id = Column(Integer, primary_key=True)
    data = Column(String(50), nullable=True)

obj = MyObject(id=1)
session.add(obj)
session.commit()  # INSERT with the 'data' column omitted; the database
                  # itself will persist this as the NULL value

从insert中省略一列意味着该列将设置空值, 除非 该列有一个默认设置,在这种情况下,将保留默认值。这既适用于纯SQL视角下的服务器端默认值,也适用于SQLAlchemy插入行为中的客户端和服务器端默认值:

class MyObject(Base):
    __tablename__ = 'my_table'
    id = Column(Integer, primary_key=True)
    data = Column(String(50), nullable=True, server_default="default")

obj = MyObject(id=1)
session.add(obj)
session.commit()  # INSERT with the 'data' column omitted; the database
                  # itself will persist this as the value 'default'

但是,在ORM中,即使指定了python值 None 显式地对对象,这将被处理为 same 好像从未分配过值:

class MyObject(Base):
    __tablename__ = 'my_table'
    id = Column(Integer, primary_key=True)
    data = Column(String(50), nullable=True, server_default="default")

obj = MyObject(id=1, data=None)
session.add(obj)
session.commit()  # INSERT with the 'data' column explicitly set to None;
                  # the ORM still omits it from the statement and the
                  # database will still persist this as the value 'default'

以上操作将持续到 data 列服务器默认值 "default" 而不是SQL空值,即使 None 已通过;这是ORM的一个长期行为,许多应用程序将其作为假设。

那么,如果我们真的想将空值放入这个列中,即使这个列有一个默认值,又该怎么办呢?有两种方法。一种是在每个实例级别上,我们使用 null SQL构造:

from sqlalchemy import null

obj = MyObject(id=1, data=null())
session.add(obj)
session.commit()  # INSERT with the 'data' column explicitly set as null();
                  # the ORM uses this directly, bypassing all client-
                  # and server-side defaults, and the database will
                  # persist this as the NULL value

这个 null SQL构造总是转换为直接出现在目标插入语句中的SQL空值。

如果我们想使用python值 None 并且,尽管存在列默认值,但也将其保持为空,我们可以使用核心级别的修饰符为ORM配置它。 TypeEngine.evaluates_none() ,指示ORM应在其中处理值的类型 None 与任何其他值相同并将其传递,而不是将其作为“缺少的”值省略:

class MyObject(Base):
    __tablename__ = 'my_table'
    id = Column(Integer, primary_key=True)
    data = Column(
      String(50).evaluates_none(),  # indicate that None should always be passed
      nullable=True, server_default="default")

obj = MyObject(id=1, data=None)
session.add(obj)
session.commit()  # INSERT with the 'data' column explicitly set to None;
                  # the ORM uses this directly, bypassing all client-
                  # and server-side defaults, and the database will
                  # persist this as the NULL value

无评价

这个 TypeEngine.evaluates_none() 修饰符主要用于表示一个类型,其中python值“none”很重要,主要示例是一个JSON类型,它可能希望持久化JSON。 null 值而不是SQL空值。我们在这里稍作调整,以便向ORM发出我们想要的信号 None 即使没有为其分配特殊类型级别的行为,也要在任何时候传递到类型中。

1.1 新版功能: 增加了 TypeEngine.evaluates_none() 方法以指示“无”值应视为重要值。

正在获取服务器生成的默认值

如章节所述 服务器调用了DDL显式默认表达式标记隐式生成的值、时间戳和触发的列 核心支持数据库列的概念,数据库本身在插入时为其生成一个值,在不太常见的情况下,则在更新语句时为其生成一个值。ORM的特点是支持这样的列,即在刷新时能够获取这些新生成的值。对于由服务器生成的主键列,此行为是必需的,因为一旦对象被持久化,ORM就必须知道该对象的主键。

在绝大多数情况下,由数据库自动生成值的主键列是简单的整数列,由数据库作为所谓的“autoincrement”列或与该列关联的序列实现。sqlAlchemy core中的每个数据库方言都支持检索这些主键值的方法,这些主键值通常是python dbapi固有的,而且通常这个过程是自动的,除了像oracle这样的数据库要求我们指定 Sequence 明确地。有关此问题的更多文档,请访问 Column.autoincrement .

对于服务器生成的列不是主键列或不是简单的自增整数列,ORM要求用适当的 server_default 指令,该指令允许ORM检索此值。然而,并非所有的方法都支持所有的后端,因此必须小心使用适当的方法。要回答的两个问题是:1。这个列是否是主键的一部分,2。数据库是否支持return或等效语句,例如“OUTPUT inserted”;这些是SQL语句,在调用INSERT或UPDATE语句的同时返回服务器生成的值。支持返回或等效的数据库包括PostgreSQL、Oracle和SQL Server。不包括SQLite和MySQL的数据库。

情况1:支持非主键、返回或等效项

在这种情况下,列应标记为 FetchedValue 或者用一个明确的 Column.server_default . 这个 mapper.eager_defaults 标志可用于指示插入时应立即提取这些列,有时还应更新::

class MyModel(Base):
    __tablename__ = 'my_table'

    id = Column(Integer, primary_key=True)
    timestamp = Column(DateTime(), server_default=func.now())

    # assume a database trigger populates a value into this column
    # during INSERT
    special_identifier = Column(String(50), server_default=FetchedValue())

    __mapper_args__ = {"eager_defaults": True}

上面,没有从客户端为“timestamp”或“special_identifier”指定显式值的insert语句将在返回子句中包含“timestamp”和“special_identifier”列,以便它们立即可用。在PostgreSQL数据库中,上表的插入内容如下:

INSERT INTO my_table DEFAULT VALUES RETURNING my_table.id, my_table.timestamp, my_table.special_identifier

案例2:不支持或不需要非主键、返回或等效项

这种情况与上面的情况1相同,只是我们没有指定 mapper.eager_defaults ::

class MyModel(Base):
    __tablename__ = 'my_table'

    id = Column(Integer, primary_key=True)
    timestamp = Column(DateTime(), server_default=func.now())

    # assume a database trigger populates a value into this column
    # during INSERT
    special_identifier = Column(String(50), server_default=FetchedValue())

插入具有上述映射的记录后,“timestamp”和“special_identifier”列将保持为空,并在刷新后首次访问时通过第二个select语句获取,例如,它们被标记为“expired”。

如果 mapper.eager_defaults 仍在使用,并且后端数据库不支持返回或等效数据库,ORM将在insert语句之后立即发出此select语句。这通常是不可取的,因为它向可能不需要的刷新进程添加了额外的select语句。使用上面的映射 mapper.eager_defaults 对mysql设置为true的标志会在刷新时产生这样的SQL结果(减去注释,仅用于澄清):

INSERT INTO my_table () VALUES ()

-- when eager_defaults **is** used, but RETURNING is not supported
SELECT my_table.timestamp AS my_table_timestamp, my_table.special_identifier AS my_table_special_identifier
FROM my_table WHERE my_table.id = %s

案例3:支持主键、返回键或等效键

插入时必须立即获取具有服务器生成值的主键列;ORM只能访问其具有主键值的行,因此如果主键是由服务器生成的,ORM需要一种方法,以便数据库在插入时立即为我们提供该新值。

如前所述,对于整数“autoincrement”列以及postgresql-serial,这些类型由核心自动处理;数据库包括获取“last inserted id”的函数,其中不支持返回,并且在支持返回的地方,sqlachemy将使用该函数。

但是,对于非整数值以及必须显式链接到序列或其他触发例程的整数值,必须在表元数据中标记服务器默认生成。

对于我们与Oracle一起使用的显式序列,这意味着我们正在使用 Sequence 结构:

class MyOracleModel(Base):
    __tablename__ = 'my_table'

    id = Column(Integer, Sequence("my_sequence"), primary_key=True)
    data = Column(String(50))

Oracle上上述模型的插入内容如下:

INSERT INTO my_table (id, data) VALUES (my_sequence.nextval, :data) RETURNING my_table.id INTO :ret_0

在上面的位置,SQLAlchemy呈现 my_sequence.nextval 对于主键列,还使用返回立即返回新值。

对于自动生成值的数据类型或由触发器填充的列,我们使用 FetchedValue . 下面是一个使用SQL Server时间戳列作为主键的模型,它自动生成值:

class MyModel(Base):
    __tablename__ = 'my_table'

    timestamp = Column(TIMESTAMP(), server_default=FetchedValue(), primary_key=True)

在SQL Server上为上表插入的内容如下:

INSERT INTO my_table OUTPUT inserted.timestamp DEFAULT VALUES

情况4:不支持主键、返回键或等效键

在这个区域中,我们为数据库(如sqlite或mysql)生成行,其中一些生成默认值的方法出现在服务器上,但不在数据库的常规自动增量例程中。在这种情况下,我们必须确保SQLAlchemy可以“预执行”默认值,这意味着它必须是显式SQL表达式。

注解

本节将说明涉及mysql和sqlite的datetime值的多个配方,因为这两个后端上的datetime数据类型具有其他有用的特殊要求。但是请记住,sqlite和mysql需要一个显式的“预执行”默认生成器 any 自动生成的数据类型用作主键,而不是常规的单列自动递增整数值。

带日期时间主键的MySQL

使用 DateTime 对于mysql,我们使用“now()”sql函数添加了一个显式的预执行支持的默认值:

class MyModel(Base):
    __tablename__ = 'my_table'

    timestamp = Column(DateTime(), default=func.now(), primary_key=True)

在上面的位置,我们选择“now()”函数向列传递日期时间值。上面生成的SQL是:

SELECT now() AS anon_1
INSERT INTO my_table (timestamp) VALUES (%s)
('2018-08-09 13:08:46',)

带时间戳主键的MySQL

当使用 TIMESTAMP 数据类型与mysql,mysql通常会自动将服务器端默认值与该数据类型关联起来。但是,当我们使用一个作为主键时,核心无法检索新生成的值,除非我们自己执行函数。AS TIMESTAMP 在mysql上,实际上存储了一个二进制值,我们需要在“now()”的用法中添加一个额外的“cast”,以便检索可以持久化到列中的二进制值:

from sqlalchemy import cast, Binary

class MyModel(Base):
    __tablename__ = 'my_table'

    timestamp = Column(
        TIMESTAMP(),
        default=cast(func.now(), Binary),
        primary_key=True)

上面,除了选择“now()”函数外,我们还使用 Binary 数据类型与 cast() 所以返回的值是二进制的。在插入中从上面呈现的SQL如下所示:

SELECT CAST(now() AS BINARY) AS anon_1
INSERT INTO my_table (timestamp) VALUES (%s)
(b'2018-08-09 13:08:46',)

带日期时间主键的sqlite

对于sqlite,可以使用sql函数生成新的时间戳 datetime('now', 'localtime') (或指定) 'utc' 但是,对于UTC,使事情变得更复杂的是,这将返回一个字符串值,该值随后与sqlacalchemy的值不兼容。 DateTime 数据类型(即使数据类型将信息转换回sqlite后端的字符串,它也必须作为python datetime进行传递)。因此,我们还必须指定要强制返回值为 DateTime 当它从函数返回时,我们通过将其作为 type_ 参数::

class MyModel(Base):
    __tablename__ = 'my_table'

    timestamp = Column(
        DateTime,
        default=func.datetime('now', 'localtime', type_=DateTime),
        primary_key=True)

插入时的上述映射如下所示:

SELECT datetime(?, ?) AS datetime_1
('now', 'localtime')
INSERT INTO my_table (timestamp) VALUES (?)
('2018-10-02 13:37:33.000000',)

使用INSERT、UPDATE和ON CONFLICATION(即upsert)返回ORM对象

Deep Alchemy

将ORM对象链接到返回的特性是一个新的实验性特性。

1.4.0 新版功能.

这个 DML 构造 insert()update() ,以及 delete() 以一种方法为特色 UpdateBase.returning() 其在支持返回的数据库后端(PostgreSQL、SQL Server、某些MariaDB版本)上可用于返回由语句生成或匹配的数据库行,就好像它们是被选中的一样。启用ORM的UPDATE和DELETE语句可以与此功能结合使用,以便它们返回与条件匹配的所有行相对应的行::

from sqlalchemy import update

stmt = update(User).where(User.name == "squidward").values(name="spongebob").\
    returning(User.id)

for row in session.execute(stmt):
    print(f"id: {row.id}")

上面的示例返回 User.id 匹配的每一行的属性。如果每行至少包含一个主键值,我们可以选择将这些行作为ORM对象接收,从而允许从数据库中自动加载ORM对象,对应于针对这些行的UPDATE语句。要实现这一点,我们可以将 Update 构造,该构造返回 User 带有 select() 方法在ORM上下文中运行此UPDATE语句 Select.from_statement() 方法:

stmt = update(User).where(User.name == "squidward").values(name="spongebob").\
    returning(User)

orm_stmt = select(User).from_statement(stmt).execution_options(populate_existing=True)

for user in session.execute(orm_stmt).scalars():
    print("updated user: %s" % user)

上面,我们制作了一个 update() 构造,该构造包括 Update.returning() 在充分考虑到这一点的情况下 User 实体,它将在更新数据库表时从数据库表中生成完整的行;只要包含完整的主键,就可以指定要加载的任意列集。接下来,通过生成一个 select() 对于所需的实体,然后将其与UPDATE语句相适应,方法是将 Update 构造到 Select.from_statement() 方法;这种特殊的ORM方法,在 从文本和核心语句获取ORM结果 生成特定于ORM的适配器,该适配器允许给定语句的行为就像它是首先描述的行的SELECT一样。数据库中实际上没有发出SELECT,只有我们构造的UPDATE..返回。

最后,我们利用 填充现有 这样,更新返回的所有数据(包括我们已更新的列)都填充到返回的对象中,从而替换已经存在的任何值。这与我们使用 synchronize_session='fetch' 前面在上介绍的策略 选择同步策略

在上使用PostgreSQL与返回返回已插入的ORM对象冲突

上面的方法也可以用于带有返回的插入。下面是一个更高级的示例,说明了如何使用PostgreSQL 冲突时插入(向上插入) 构造以在数据库中插入或更新行,同时将这些对象作为ORM实例生成:

from sqlalchemy.dialects.postgresql import insert

stmt = insert(User).values(
    [
        dict(name="sandy", fullname="Sandy Cheeks"),
        dict(name="squidward", fullname="Squidward Tentacles"),
        dict(name="spongebob", fullname="Spongebob Squarepants"),
    ]
)

stmt = stmt.on_conflict_do_update(
    index_elements=[User.name], set_=dict(fullname=stmt.excluded.fullname)
).returning(User)

orm_stmt = (
    select(User)
    .from_statement(stmt)
    .execution_options(populate_existing=True)
)
for user in session.execute(
    orm_stmt,
).scalars():
    print("inserted or updated: %s" % user)

首先,我们确保使用的是 insert() 构造。接下来,我们构造一个多值INSERT语句,其中单个INSERT语句将提供多个要插入的行。在PostgreSQL数据库上,此语法提供了一次发送数百行以供插入的最有效方法。

从那里,如果我们想要添加 RETURNING 子句来生成大容量插入。但是,为了使该示例更加有趣,我们还将添加特定于PostgreSQL的 ON CONFLICT..DO UPDATE 语法,以便已存在的基于唯一条件的行将改为更新。我们假设存在索引或唯一约束 name 栏目中的 user_account 表,然后指定相应的 Insert.on_conflict_do_update() 将更新 fullname 列表示已存在的行。

最后,我们将添加 Insert.returning() 子句,并选择我们的 User 对象使用相同的 Select.from_statement() 就像我们之前做的那样接近。假设数据库只有一行用于 (1, "squidward", NULL) Present;此行将触发上面语句中的on Conflicts例程,换句话说,执行相当于UPDATE语句的操作。另外两排, (NULL, "sandy", "Sandy Cheeks")(NULL, "spongebob", "Spongebob Squarepants") 在数据库中尚不存在,将使用普通插入语义插入;主键列 id 使用 SERIALIDENTITY 若要自动生成新的整数值,请执行以下操作。

使用上面的表单,我们可以看到PostgreSQL数据库发出的SQL如下:

INSERT INTO user_account (name, fullname) VALUES (%(name_m0)s, %(fullname_m0)s), (%(name_m1)s, %(fullname_m1)s), (%(name_m2)s, %(fullname_m2)s) ON CONFLICT (name) DO UPDATE SET fullname = excluded.fullname RETURNING user_account.id, user_account.name, user_account.fullname {'name_m0': 'sandy', 'fullname_m0': 'Sandy Cheeks', 'name_m1': 'squidward', 'fullname_m1': 'Squidward Tentacles', 'name_m2': 'spongebob', 'fullname_m2': 'Spongebob Squarepants'}
inserted or updated: User(id=2, name='sandy', fullname='Sandy Cheeks') inserted or updated: User(id=3, name='squidward', fullname='Squidward Tentacles') inserted or updated: User(id=1, name='spongebob', fullname='Spongebob Squarepants')

在上面我们还可以看到,插入的 User 正如我们对任何其他面向ORM的INSERT语句所期望的那样,对象有一个新生成的主键值。

分区策略(例如,每个会话有多个数据库后端)

简单垂直分区

垂直分区通过配置 SessionSession.binds 争论。此参数接收一个字典,该字典包含ORM映射类、映射层次结构中的任意类(如声明性基类或混合类)的任何组合, Table 对象,以及 Mapper 对象作为键,然后通常引用 Engine 或者不太典型 Connection 对象作为目标。每当 Session 需要代表特定类型的映射类发出SQL,以便找到适当的数据库连接源::

engine1 = create_engine('postgresql://db1')
engine2 = create_engine('postgresql://db2')

Session = sessionmaker()

# bind User operations to engine 1, Account operations to engine 2
Session.configure(binds={User:engine1, Account:engine2})

session = Session()

上面,针对任何一个类的SQL操作都将使用 Engine 链接到那个类。该功能在读写操作中都是全面的;a Query 这是针对映射到的实体的 engine1 (通过查看请求项目列表中的第一个实体确定)将利用 engine1 运行查询。冲洗操作将利用 both 当引擎刷新类型的对象时,基于类的引擎 UserAccount .

在更常见的情况下,通常可以使用基本类或混合类来区分针对不同数据库连接的操作。这个 Session.binds 参数可以容纳任意的python类作为键,如果发现它在 __mro__ (python方法解析顺序)用于特定的映射类。假设两个声明性基表示两个不同的数据库连接:

BaseA = declarative_base()

BaseB = declarative_base()

class User(BaseA):
    # ...

class Address(BaseA):
    # ...


class GameInfo(BaseB):
    # ...

class GameStats(BaseB):
    # ...


Session = sessionmaker()

# all User/Address operations will be on engine 1, all
# Game operations will be on engine 2
Session.configure(binds={BaseA:engine1, BaseB:engine2})

上面,从 BaseABaseB 将把它们的SQL操作路由到两个引擎中的一个,根据它们从哪个超类(如果有的话)下降。对于从多个“绑定”超类下降的类,将选择目标类层次结构中最高的超类来表示应使用哪个引擎。

参见

Session.binds

多引擎会话的事务协调

使用多绑定引擎的一个警告是,在一个后端上的提交操作在另一个后端上成功之后可能会失败。这是一个在关系数据库中使用“两阶段事务”解决的不一致问题,它在提交序列中添加了一个额外的“准备”步骤,允许多个数据库在实际完成事务之前同意提交。

由于DBAPIS内部的支持有限,SQLAlchemy对后端的两阶段事务的支持有限。最典型的是,人们知道它可以很好地与PostgreSQL后端一起工作,而在较小程度上与MySQL后端一起工作。然而, Session 通过设置 Session.use_twophase 内旗 sessionmakerSession . 见 启用两阶段提交 举个例子。

自定义垂直分区

通过重写 Session.get_bind() 方法。下面我们将展示一个习惯 Session 它提供了以下规则:

  1. 刷新操作以及批量“更新”和“删除”操作将被传递到名为的引擎 leader .

  2. 对子类对象的操作 MyOtherClass 都发生在 other 发动机。

  3. 所有其他类的读取操作都发生在 follower1follower2 数据库。

engines = {
    'leader':create_engine("sqlite:///leader.db"),
    'other':create_engine("sqlite:///other.db"),
    'follower1':create_engine("sqlite:///follower1.db"),
    'follower2':create_engine("sqlite:///follower2.db"),
}

from sqlalchemy.sql import Update, Delete
from sqlalchemy.orm import Session, sessionmaker
import random

class RoutingSession(Session):
    def get_bind(self, mapper=None, clause=None):
        if mapper and issubclass(mapper.class_, MyOtherClass):
            return engines['other']
        elif self._flushing or isinstance(clause, (Update, Delete)):
            return engines['leader']
        else:
            return engines[
                random.choice(['follower1','follower2'])
            ]

以上 Session 类已使用 class_ 参数 sessionmaker ::

Session = sessionmaker(class_=RoutingSession)

这种方法可以与多个 MetaData 对象,使用声明性的方法 __abstract__ 关键字,描述于 __abstract__ .

参见

Django-style Database Routers in SQLAlchemy - blog post on a more comprehensive example of Session.get_bind()

水平分区

水平分区将单个表(或一组表)的行跨多个数据库进行分区。圣卢西亚 Session 包含对此概念的支持,但是要完全使用它,需要 SessionQuery 使用子类。这些子类的基本版本可在 水平切分 ORM扩展。使用示例如下: 水平切分 .

散装作业

Deep Alchemy

批量操作本质上是工作单元功能较低的版本,用于在主键目标行上发出INSERT和UPDATE语句。添加这些例程是为了适应某些情况,在这种情况下,插入或更新的许多行可以运行到数据库中,而不需要像通常那样多的工作单元开销,因为大多数工作单元特性都是 残疾人 .

这些对象通常不需要使用ORM中的许多对象,而这些对象通常不需要使用 ;对于高效的大容量插入,最好使用核心 Insert 直接构造。请阅读所有注意事项 ORM兼容性/注意事项 .

注解

不应将BULK INSERT和UPDATE与更常见的功能(称为 使用任意WHERE子句更新和删除 。此功能允许发出具有任意WHERE条件的单个UPDATE或DELETE语句。在一些后端上,还有一个选项可以对ORM使用真正的“upsert”,比如在PostgreSQL上。请参阅部分 使用INSERT、UPDATE和ON CONFLICATION(即upsert)返回ORM对象 举个例子。

参见

使用任意WHERE子句更新和删除 -在ORM上下文中使用直接的多行UPDATE和DELETE语句。

使用INSERT、UPDATE和ON CONFLICATION(即upsert)返回ORM对象 -使用UPDATE、INSERT或UPERT操作返回ORM对象

1.0.0 新版功能.

上的大容量插入/每行更新操作 Session 包括 Session.bulk_save_objects()Session.bulk_insert_mappings()Session.bulk_update_mappings() . 这些方法的目的是直接公开工作单元系统的内部元素,这样就可以单独使用发出给定字典或对象状态的插入和更新语句的工具,而不必使用状态、关系和属性管理的正常工作单元机制。这种方法的优点严格来说是减少了Python开销:

  • flush()过程,包括对所有对象的调查、它们的状态、它们的级联状态、与它们关联的所有对象的状态,通过 relationship() ,并且要执行的所有操作的拓扑类型都被完全忽略。这减少了大量的Python开销。

  • 给定的对象与目标没有定义的关系 Session ,即使操作完成,也意味着在标识映射或会话方面附加它们或管理它们的状态没有开销。

  • 这个 Session.bulk_insert_mappings()Session.bulk_update_mappings() 方法接受纯Python字典的列表,而不是对象;这进一步减少了与实例化映射对象和将状态分配给它们相关联的大量开销,通常每个属性的历史跟踪成本也很高。

  • 传递给所有批量方法的对象集按接收顺序进行处理。在情况下 Session.bulk_save_objects() ,当传递不同类型的对象时,insert和update语句必须按类型分组。为了减少传递给DBAPI的批插入或更新语句的数量,请确保按类型对传入的对象列表进行分组。

  • 默认情况下,插入后获取主键的过程也被禁用。如果正确执行,插入语句现在可以更容易地由工作流程单元批处理到 executemany() 块,其性能远远优于单个语句调用。

  • 同样,可以对update语句进行定制,使所有属性都无条件地受制于set子句,这也使得 executemany() 可以使用块。

应使用 性能 示例套件。这是一系列示例脚本,演示了跨各种场景(包括大容量插入和更新场景)的Python调用计数。

参见

性能 -包括与传统的核心和ORM方法(包括性能指标)相比的批量操作的详细示例。

使用

每个方法都在 Session 对象的事务,与其他事务一样:

s = Session()
objects = [
    User(name="u1"),
    User(name="u2"),
    User(name="u3")
]
s.bulk_save_objects(objects)

为了 Session.bulk_insert_mappings()Session.bulk_update_mappings() ,传递字典::

s.bulk_insert_mappings(User,
  [dict(name="u1"), dict(name="u2"), dict(name="u3")]
)

与核心插入/更新结构的比较

批量方法提供的性能在特定情况下可以接近使用核心的性能 InsertUpdate 在“ExecuteMany”上下文中构造(有关“ExecuteMany”的描述,请参见 执行多个语句 在核心教程中)。为了实现这一点, Session.bulk_insert_mappings.return_defaults 应禁用标志,以便可以将行批处理在一起。中的示例套件 性能 应仔细研究,以熟悉批量性能的实现速度。

ORM兼容性/注意事项

警告

在使用批量例程之前,请务必熟悉这些限制。

与传统的ORM使用相比,批量插入/更新方法会丢失大量的功能。以下是以下功能的列表: 无法使用的 使用这些方法时:

  • 坚持下去 relationship() 连杆机构

  • 按依赖项的顺序对行进行排序;按传递给方法的顺序直接插入或更新行

  • 对给定对象进行会话管理,包括会话的附件、标识映射管理。

  • 与主键突变相关的功能,更新级联- 主键列的变异将不起作用 -由于每行的原始主键值不存在,所以无法生成WHERE条件。

  • SQL表达式插入/更新(例如 将SQL插入/更新表达式嵌入到刷新中 )—必须对这些语句求值将阻止INSERT和UPDATE语句以简单的方式批处理在一起,因为它们会更改语句本身的SQL编译。

  • ORM事件,例如 MapperEvents.before_insert() 等等。批量会话方法没有事件支持。

特点是 是可用的 包括:

  • 插入和更新映射对象

  • 版本标识符支持

  • 多表映射,例如联合继承-但是,要跨多个表插入的对象要么需要提前完全填充主键标识符,要么 Session.bulk_save_objects.return_defaults 必须使用标志,这将大大降低性能优势

Previous: 事务和连接管理 Next: 上下文/线程本地会话