SqlAlchemy 2.0 中文文档(四十四)(8)https://developer.aliyun.com/article/1563075
插入语句的“插入多个值”行为
新功能 2.0 版本中:有关更改的背景,请参见 优化的 ORM 批量插入现在已针对除 MySQL 外的所有后端实现,其中包括示例性能测试
提示
insertmanyvalues 功能是一种透明可用的性能特性,无需最终用户进行干预即可按需发生。本节描述了该特性的架构以及如何衡量其性能并调整其行为,以优化批量 INSERT 语句的速度,特别是 ORM 中使用的情况。
随着越来越多的数据库支持 INSERT…RETURNING,SQLAlchemy 在处理需要获取服务器生成值的 INSERT 语句的方式上发生了重大变化,最重要的是服务器生成的主键值,它允许在后续操作中引用新行。特别是,这种情况长期以来一直是 ORM 中的一个重大性能问题,ORM 依赖于能够检索服务器生成的主键值,以便正确填充 identity map。
随着最近对 SQLite 和 MariaDB 添加了对 RETURNING 的支持,SQLAlchemy 不再需要依赖于大多数后端仅支持单行 cursor.lastrowid 属性提供的 DBAPI;现在可以对所有 SQLAlchemy 包含的 后端使用 RETURNING,除了 MySQL 外。剩下的性能限制是,cursor.executemany() DBAPI 方法不允许获取行,对于大多数后端来说,通过放弃使用 executemany()
,而是重构单个 INSERT 语句以适应在单个语句中容纳大量行,并使用 cursor.execute()
调用该语句来解决。这种方法源自于 psycopg2
DBAPI 的 psycopg2 快速执行辅助功能 特性,SQLAlchemy 在最近的发布系列中逐渐增加了对其的更多支持。
当前支持
该功能对支持 RETURNING 的 SQLAlchemy 中的所有后端启用,但 Oracle 是个例外,因为 cx_Oracle 和 OracleDB 驱动程序都提供了自己的等效功能。该功能通常在使用 Insert.returning()
方法与 executemany 执行结合使用时发生,这发生在将字典列表传递给 Connection.execute.parameters
参数的 Connection.execute()
或 Session.execute()
方法(以及 asyncio 下的等效方法和类似 Session.scalars()
的速记方法)。在使用方法如 Session.add()
和 Session.add_all()
向表中添加行时,它也在 ORM 工作单元 过程中发生。
对于 SQLAlchemy 的包含方言,支持或等效支持目前如下:
- SQLite - 对 SQLite 版本 3.35 及以上提供支持
- PostgreSQL - 所有支持的 Postgresql 版本(9 及以上)
- SQL Server - 所有支持的 SQL Server 版本 [1]
- MariaDB - 对 MariaDB 版本 10.5 及以上提供支持
- MySQL - 没有支持,没有 RETURNING 功能
- Oracle - 支持 RETURNING,使用本机 cx_Oracle / OracleDB API 和 executemany,适用于所有支持的 Oracle 版本 9 及以上,使用多行 OUT 参数。这与“executemanyvalues”不是同一实现,但具有相同的使用模式和等效性能优势。
从版本 2.0.10 开始更改:
禁用该功能
要为特定后端禁用“insertmanyvalues”功能,可以将 create_engine.use_insertmanyvalues
参数传递为 False
给 create_engine()
:
engine = create_engine( "mariadb+mariadbconnector://scott:tiger@host/db", use_insertmanyvalues=False )
还可以通过将 Table.implicit_returning
参数传递为 False
来禁用对特定 Table
对象的隐式使用。
t = Table( "t", metadata, Column("id", Integer, primary_key=True), Column("x", Integer), implicit_returning=False, )
禁用 RETURNING 对于特定表格的原因是为了解决特定后端的限制。
批量模式操作
这个特性有两种操作模式,根据方言和Table
选择透明模式。其中一种是批量模式,通过重写形如以下的 INSERT 语句来减少数据库往返次数:
INSERT INTO a (data, x, y) VALUES (%(data)s, %(x)s, %(y)s) RETURNING a.id
转换为“批量”形式,如下:
INSERT INTO a (data, x, y) VALUES (%(data_0)s, %(x_0)s, %(y_0)s), (%(data_1)s, %(x_1)s, %(y_1)s), (%(data_2)s, %(x_2)s, %(y_2)s), ... (%(data_78)s, %(x_78)s, %(y_78)s) RETURNING a.id
在上面的语句中,语句是针对输入数据的一个子集(一个“批量”)组织的,其大小由数据库后端以及每个批次中参数的数量确定,以对应于已知的语句大小/参数数量限制。然后,该特性对每个输入数据批次执行一次 INSERT 语句,直到所有记录都被使用,将每个批次的 RETURNING 结果连接成一个单一的大型结果集,可从单个Result
对象获取。
这种“批量”形式允许使用更少的数据库往返次数插入多行,并且已经证明在大多数支持的后端中可以实现显着的性能提升。
将 RETURNING 行与参数集相关联
2.0.10 版本中的新功能。
在上一节中示例的“批量”模式查询不保证返回的记录顺序与输入数据的顺序相对应。当由 SQLAlchemy ORM 工作单元 进程使用时,以及与将返回的服务器生成的值与输入数据进行关联的应用程序一起使用时,Insert.returning()
和 UpdateBase.return_defaults()
方法包括一个选项 Insert.returning.sort_by_parameter_order
,表示“insertmanyvalues”模式应保证此对应关系。这与实际由数据库后端实际插入记录的顺序无关,这在任何情况下都不被假定;只有在接收返回的记录时,返回的记录应该被组织起来,以对应于原始输入数据传递的顺序。
当 Insert.returning.sort_by_parameter_order
参数存在时,对于使用服务器生成的整数主键值的表,如 IDENTITY
、PostgreSQL SERIAL
、MariaDB AUTO_INCREMENT
或 SQLite 的 ROWID
方案,“batch” 模式可能会选择使用更复杂的 INSERT…RETURNING 形式,并结合基于返回值的行的后执行排序,或者如果不存在这样的形式,“insertmanyvalues” 功能可能会优雅地降级到 “non-batched” 模式,为每个参数集运行单独的 INSERT 语句。
例如,在 SQL Server 上,当自动增量的 IDENTITY
列用作主键时,将使用以下 SQL 形式:
INSERT INTO a (data, x, y) OUTPUT inserted.id, inserted.id AS id__1 SELECT p0, p1, p2 FROM (VALUES (?, ?, ?, 0), (?, ?, ?, 1), (?, ?, ?, 2), ... (?, ?, ?, 77) ) AS imp_sen(p0, p1, p2, sen_counter) ORDER BY sen_counter
当主键列使用 SERIAL 或 IDENTITY 时,PostgreSQL 也使用类似的形式。上述形式不保证插入行的顺序。但是,它确保 IDENTITY 或 SERIAL 值将与每个参数集按顺序创建[2]。然后,“insertmanyvalues” 功能通过递增整数标识对上述 INSERT 语句的返回行进行排序。
对于 SQLite 数据库,没有适当的 INSERT 形式可以将新的 ROWID 值的生成与传递的参数集的顺序相关联。因此,当使用服务器生成的主键值时,当请求有序 RETURNING 时,SQLite 后端将降级为 “non-batched” 模式。对于 MariaDB,默认的 INSERT 形式由 insertmanyvalues 使用,因为此数据库后端在使用 InnoDB 时会将 AUTO_INCREMENT 的顺序与输入数据的顺序对齐[3]。
对于客户端生成的主键,例如使用 Python 的 uuid.uuid4()
函数为 Uuid
列生成新值时,“insertmanyvalues” 功能会透明地将此列包含在 RETURNING 记录中,并将其值与给定输入记录的值相关联,从而保持输入记录和结果行之间的对应关系。由此可见,当使用客户端生成的主键值时,所有后端都允许批量、参数相关的 RETURNING 顺序。
“insertmanyvalues” “batch” 模式确定用作输入参数和 RETURNING 行之间对应点的列或列的主题被称为 insert sentinel,这是一种特定的列或列,用于跟踪此类值。通常会自动选择“insert sentinel”,但也可以为极端特殊情况进行用户配置;章节 配置 Sentinel 列 描述了这一点。
对于没有提供适当的 INSERT 形式以确定性地提供与输入值对齐的服务器生成值的后端,或对于具有其他类型的服务器生成主键值的 Table
配置,当请求保证 RETURNING 排序时,“insertmanyvalues” 模式将在需要时使用 非批量 模式。
另请参阅
- Microsoft SQL Server 的原理
- “使用 SELECT 结合 ORDER BY 来填充行的 INSERT 查询保证了如何计算标识值,但不保证插入行的顺序。”
learn.microsoft.com/en-us/sql/t-sql/statements/insert-transact-sql?view=sql-server-ver16#limitations-and-restrictions
- PostgreSQL 批量插入讨论
- 2018 年的原始描述
www.postgresql.org/message-id/29386.1528813619@sss.pgh.pa.us
2023 年的跟进 -www.postgresql.org/message-id/be108555-da2a-4abc-a46b-acbe8b55bd25%40app.fastmail.com
- MariaDB AUTO_INCREMENT 行为(使用与 MySQL 相同的 InnoDB 引擎):
dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html
dba.stackexchange.com/a/72099
### 非批量模式操作
对于没有客户端主键值并提供服务器生成主键值(或没有主键)的 Table
配置,并且数据库无法根据多个参数集以确定性或可排序的方式调用的情况,“insertmanyvalues” 特性在为 Insert
语句满足 Insert.returning.sort_by_parameter_order
要求时可能选择使用 非批量 模式。
在这种模式下,保留了原始的 SQL INSERT 形式,并且 “insertmanyvalues” 特性将为每个参数集单独运行语句,将返回的行组织成完整的结果集。与之前的 SQLAlchemy 版本不同,它通过最小化 Python 开销来紧密循环执行。在某些情况下,例如在 SQLite 上,“非批量” 模式的性能与 “批量” 模式完全相同。
语句执行模型
无论是“批量”模式还是“非批量”模式,该功能都会必然使用 DBAPI cursor.execute()
方法调用 多个 INSERT 语句,在 单个 调用核心级别 Connection.execute()
方法的范围内,每个语句包含多达一组固定参数的限制。如下所述,此限制可以配置在 控制批处理大小 处。对 cursor.execute()
的单独调用将分别记录,并且也将分别传递给事件监听器,例如 ConnectionEvents.before_cursor_execute()
(请参阅下面的 日志和事件)。
配置哨兵列
在典型情况下,“insertmanyvalues”功能为了提供带有确定性行顺序的 INSERT…RETURNING 将自动从给定表的主键中确定哨兵列,如果无法识别,则会优雅地降级为“逐行”模式。作为一个完全 可选 的功能,为了获取对于具有服务器生成的主键的表的完整“insertmanyvalues”批量性能,其默认生成器函数与“哨兵”用例不兼容,其他非主键列可以被标记为“哨兵”列,假设它们符合某些要求。一个典型的例子是具有客户端默认值的非主键 Uuid
列,例如 Python uuid.uuid4()
函数。还有一种构造用于创建简单的整数列,其具有面向“insertmanyvalues”用例的客户端整数计数器。
可以通过在合格列上添加 Column.insert_sentinel
来指示哨兵列。最基本的“合格”列是一个非空唯一列,具有客户端默认值,例如以下 UUID 列:
import uuid from sqlalchemy import Column from sqlalchemy import FetchedValue from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import Uuid my_table = Table( "some_table", metadata, # assume some arbitrary server-side function generates # primary key values, so cannot be tracked by a bulk insert Column("id", String(50), server_default=FetchedValue(), primary_key=True), Column("data", String(50)), Column( "uniqueid", Uuid(), default=uuid.uuid4, nullable=False, unique=True, insert_sentinel=True, ), )
在使用 ORM 声明性模型时,可以使用 mapped_column
构造相同的表单:
import uuid from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column class Base(DeclarativeBase): pass class MyClass(Base): __tablename__ = "my_table" id: Mapped[str] = mapped_column(primary_key=True, server_default=FetchedValue()) data: Mapped[str] = mapped_column(String(50)) uniqueid: Mapped[uuid.UUID] = mapped_column( default=uuid.uuid4, unique=True, insert_sentinel=True )
虽然默认生成器生成的值 必须 是唯一的,但上述“哨兵”列上的实际 UNIQUE 约束,由 unique=True
参数指示,本身是可选的,如果不需要可以省略。
还有一种特殊形式的“插入哨兵”,它是一个专用的可空整数列,利用一个特殊的默认整数计数器,仅在“insertmanyvalues”操作期间使用;作为额外的行为,该列将在 SQL 语句和结果集中省略自身,并以基本透明的方式行为。但是,它确实需要在实际数据库表中物理存在。可以使用insert_sentinel()
函数构建这种Column
的风格:
from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import Uuid from sqlalchemy import insert_sentinel Table( "some_table", metadata, Column("id", Integer, primary_key=True), Column("data", String(50)), insert_sentinel("sentinel"), )
在使用 ORM Declarative 时,可以使用 Declarative 友好版本的insert_sentinel()
,称为orm_insert_sentinel()
,它可以用于 Base 类或 mixin;如果使用declared_attr()
打包,该列将应用于所有绑定表的子类,包括连接继承层次结构:
from sqlalchemy.orm import declared_attr from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column from sqlalchemy.orm import orm_insert_sentinel class Base(DeclarativeBase): @declared_attr def _sentinel(cls) -> Mapped[int]: return orm_insert_sentinel() class MyClass(Base): __tablename__ = "my_table" id: Mapped[str] = mapped_column(primary_key=True, server_default=FetchedValue()) data: Mapped[str] = mapped_column(String(50)) class MySubClass(MyClass): __tablename__ = "sub_table" id: Mapped[str] = mapped_column(ForeignKey("my_table.id"), primary_key=True) class MySingleInhClass(MyClass): pass
在上面的示例中,“my_table”和“sub_table”都将有一个额外的整数列名为“_sentinel”,可以被“insertmanyvalues”功能使用,以帮助优化 ORM 使用的批量插入。### 控制批量大小
“insertmanyvalues”的一个关键特征是 INSERT 语句的大小受限于固定数量的“values”子句以及每次在一个 INSERT 语句中可以表示的特定方言固定总数的绑定参数。当给定的参数字典数量超过固定限制,或者当要在单个 INSERT 语句中呈现的绑定参数总数超过固定限制时(这两个固定限制是分开的),将在单个Connection.execute()
调用范围内调用多个 INSERT 语句,每个 INSERT 语句都适应一部分参数字典,称为“批量”。每个“批量”中表示的参数字典数量称为“批量大小”。例如,批量大小为 500 意味着每个发出的 INSERT 语句最多插入 500 行。
能够调整批处理大小可能是很重要的,因为较大的批处理大小对于值集本身相对较小的插入可能更有效率,而较小的批处理大小可能更适合于使用非常大的值集的插入,其中渲染的 SQL 大小以及传递给一个语句的总数据大小可能受益于根据后端行为和内存约束限制到某个特定大小。因此,批处理大小可以在每个Engine
以及每个语句的基础上进行配置。另一方面,参数限制是基于正在使用的数据库的已知特性固定的。
对于大多数后端,默认的批处理大小为 1000,还有一个每个方言的“最大参数数”限制因素,可能会在每个语句的基础上进一步减少批处理大小。最大参数数因方言和服务器版本而异;最大大小为 32700(选择了一个距离 PostgreSQL 限制的 32767 和 SQLite 现代限制的 32766 的健康距离,同时为语句中的额外参数以及 DBAPI 的怪癖留出空间)。较旧版本的 SQLite(3.32.0 之前)将此值设置为 999。MariaDB 没有确定的限制��但是 32700 仍然是 SQL 消息大小的限制因素。
“批处理大小”的值可以通过create_engine.insertmanyvalues_page_size
参数在Engine
范围内受到影响。例如,为了影响 INSERT 语句在每个语句中包含多达 100 个参数集:
e = create_engine("sqlite://", insertmanyvalues_page_size=100)
批处理大小也可以通过Connection.execution_options.insertmanyvalues_page_size
执行选项在每个语句的基础上受到影响,例如每次执行:
with e.begin() as conn: result = conn.execute( table.insert().returning(table.c.id), parameterlist, execution_options={"insertmanyvalues_page_size": 100}, )
或在语句本身上进行配置:
stmt = ( table.insert() .returning(table.c.id) .execution_options(insertmanyvalues_page_size=100) ) with e.begin() as conn: result = conn.execute(stmt, parameterlist) ```### 日志记录和事件 “insertmanyvalues”功能与 SQLAlchemy 的语句日志记录以及游标事件完全集成,例如`ConnectionEvents.before_cursor_execute()`。当参数列表被分成单独的批次时,**每个 INSERT 语句都会被记录并单独传递给事件处理程序**。这与之前 SQLAlchemy 1.x 系列中仅适用于 psycopg2 的功能的工作方式相比是一个重大变化,其中多个 INSERT 语句的生成被隐藏在日志记录和事件之外。日志显示将截断长参数列表以便阅读,并且还将指示每个语句的特定批次。下面的示例说明了此日志的摘录: ```py INSERT INTO a (data, x, y) VALUES (?, ?, ?), ... 795 characters truncated ... (?, ?, ?), (?, ?, ?) RETURNING id [generated in 0.00177s (insertmanyvalues) 1/10 (unordered)] ('d0', 0, 0, 'd1', ... INSERT INTO a (data, x, y) VALUES (?, ?, ?), ... 795 characters truncated ... (?, ?, ?), (?, ?, ?) RETURNING id [insertmanyvalues 2/10 (unordered)] ('d100', 100, 1000, 'd101', ... ... INSERT INTO a (data, x, y) VALUES (?, ?, ?), ... 795 characters truncated ... (?, ?, ?), (?, ?, ?) RETURNING id [insertmanyvalues 10/10 (unordered)] ('d900', 900, 9000, 'd901', ...
当 非批处理模式 发生时,日志将指示此情况以及 insertmanyvalues 消息:
... INSERT INTO a (data, x, y) VALUES (?, ?, ?) RETURNING id [insertmanyvalues 67/78 (ordered; batch not supported)] ('d66', 66, 66) INSERT INTO a (data, x, y) VALUES (?, ?, ?) RETURNING id [insertmanyvalues 68/78 (ordered; batch not supported)] ('d67', 67, 67) INSERT INTO a (data, x, y) VALUES (?, ?, ?) RETURNING id [insertmanyvalues 69/78 (ordered; batch not supported)] ('d68', 68, 68) INSERT INTO a (data, x, y) VALUES (?, ?, ?) RETURNING id [insertmanyvalues 70/78 (ordered; batch not supported)] ('d69', 69, 69) ...
另请参阅
配置日志记录
Upsert 支持
PostgreSQL、SQLite 和 MariaDB 方言提供了特定于后端的“upsert” 构造 insert()
、insert()
和 insert()
,它们都是 Insert
构造,具有额外的方法,如 on_conflict_do_update()
或 on_duplicate_key()
。当它们与 RETURNING 一起使用时,这些构造还支持“insertmanyvalues”行为,允许有效地进行带 RETURNING 的 upsert 操作。
当前支持
该功能适用于 SQLAlchemy 中支持 RETURNING 的所有后端,但不包括 Oracle,因为 cx_Oracle 和 OracleDB 驱动程序都提供了自己的等效功能。当使用 Insert.returning()
方法与 executemany 执行结合使用时,该功能通常发生在将字典列表传递给 Connection.execute.parameters
参数的 Connection.execute()
或 Session.execute()
方法(以及 asyncio 和类似方法如 Session.scalars()
)时。它还在使用诸如 Session.add()
和 Session.add_all()
等方法添加行时,在 ORM 工作单元 过程中发生。
对于 SQLAlchemy 包含的方言,支持或等效支持目前如下:
- SQLite - 支持 SQLite 版本 3.35 及以上
- PostgreSQL - 所有支持的 PostgreSQL 版本(9 及以上)
- SQL Server - 所有支持的 SQL Server 版本 [1]
- MariaDB - 支持 MariaDB 版本 10.5 及以上
- MySQL - 不支持,没有 RETURNING 功能
- Oracle - 使用本机 cx_Oracle / OracleDB API 支持
executemany
与RETURNING
,适用于所有支持的 Oracle 版本 9 及以上,使用多行 OUT 参数。这与 “executemanyvalues” 不是同一实现,但具有相同的使用模式和等效的性能优势。
自版本 2.0.10 更改:
禁用该功能
要为给定后端禁用 “insertmanyvalues” 功能,可以将 create_engine.use_insertmanyvalues
参数设置为 False
以 create_engine()
的方式传递:
engine = create_engine( "mariadb+mariadbconnector://scott:tiger@host/db", use_insertmanyvalues=False )
该功能也可以通过将 Table.implicit_returning
参数设置为 False
,显式禁用特定的 Table
对象的使用隐式 RETURNING:
t = Table( "t", metadata, Column("id", Integer, primary_key=True), Column("x", Integer), implicit_returning=False, )
有人可能想要为特定表禁用 RETURNING 的原因是为了解决特定后端的限制。
批处理模式操作
该功能有两种操作模式,可在每个方言、每个 Table
基础上进行透明选择。一种是批处理模式,它通过重写形如以下 INSERT 语句来减少数据库往返次数:
INSERT INTO a (data, x, y) VALUES (%(data)s, %(x)s, %(y)s) RETURNING a.id
转换为“批处理”形式,例如:
INSERT INTO a (data, x, y) VALUES (%(data_0)s, %(x_0)s, %(y_0)s), (%(data_1)s, %(x_1)s, %(y_1)s), (%(data_2)s, %(x_2)s, %(y_2)s), ... (%(data_78)s, %(x_78)s, %(y_78)s) RETURNING a.id
在上述情况下,语句是针对输入数据的子集(一个“批次”)组织的,其大小由数据库后端以及每个批次中的参数数量确定,以对应于已知的语句大小 / 参数数量的限制。然后,该功能为每个输入数据批次执行一次 INSERT 语句,直到所有记录都被消耗完毕,并将每个批次的 RETURNING 结果连接到一个单独的大行集中,该行集可以从单个 Result
对象中访问。
这种 “批处理” 形式允许使用更少的数据库往返进行许多行的 INSERT,并已被证明可以允许在大多数支持它的后端上实现显着的性能改进。
将 RETURNING 行与参数集关联起来
版本 2.0.10 中的新功能。
在前一节中说明的“批量”模式查询并不保证返回的记录顺序与输入数据的顺序相对应。当被 SQLAlchemy ORM 工作单元 过程使用时,以及用于将返回的服务器生成的值与输入数据相关联的应用程序时,Insert.returning()
和 UpdateBase.return_defaults()
方法包括一个选项 Insert.returning.sort_by_parameter_order
,指示“insertmanyvalues”模式应保证这种对应关系。这与数据库后端实际执行的记录插入顺序无关,在任何情况下都不假设;只是返回的记录应该在接收时有序排列,以对应原始输入数据传递的顺序。
当存在 Insert.returning.sort_by_parameter_order
参数时,对于使用服务器生成的整数主键值(如 IDENTITY
、PostgreSQL SERIAL
、MariaDB AUTO_INCREMENT
或 SQLite 的 ROWID
方案)的表,"批量"模式可能选择使用更复杂的 INSERT…RETURNING 形式,结合基于返回值的行后执行排序,或者如果这样的形式不可用,则“insertmanyvalues”功能可能会优雅地降级为运行每个参数集合的单独 INSERT 语句的“非批量”模式。
例如,在 SQL Server 中,当使用自增的 IDENTITY
列作为主键时,使用以下 SQL 表单:
INSERT INTO a (data, x, y) OUTPUT inserted.id, inserted.id AS id__1 SELECT p0, p1, p2 FROM (VALUES (?, ?, ?, 0), (?, ?, ?, 1), (?, ?, ?, 2), ... (?, ?, ?, 77) ) AS imp_sen(p0, p1, p2, sen_counter) ORDER BY sen_counter
在 PostgreSQL 中也使用类似的形式,当主键列使用 SERIAL 或 IDENTITY 时。上述形式并不保证插入行的顺序。但是,它确保了 IDENTITY 或 SERIAL 值将按照每个参数集合的顺序创建[2]。然后,“insertmanyvalues”功能通过递增整数标识对上述 INSERT 语句返回的行进行排序。
对于 SQLite 数据库,没有适当的 INSERT 形式可以将新的 ROWID 值的生成与传递的参数集合的顺序相关联。因此,当使用服务器生成的主键值时,SQLite 后端将在请求有序返回时降级为“非批量”模式。对于 MariaDB,insertmanyvalues 使用的默认 INSERT 形式足够,因为在使用 InnoDB 时,这个数据库后端会将 AUTO_INCREMENT 的顺序与输入数据的顺序对齐[3]。
对于客户端生成的主键,例如在使用 Python uuid.uuid4()
函数为 Uuid
列生成新值时,“insertmanyvalues” 特性会将该列透明地包含在 RETURNING 记录中,并将其值与给定的输入记录相对应,从而保持输入记录和结果行之间的对应关系。由此可见,当使用客户端生成的主键值时,所有后端都允许在批处理时进行参数相关的 RETURNING 排序。
“insertmanyvalues” “batch” 模式确定用作输入参数和 RETURNING 行之间对应点的列或列的主题被称为 insert sentinel,这是用于跟踪此类值的特定列或列。通常会自动选择“insert sentinel”,但也可以对极端特殊情况进行用户配置;章节 配置 Sentinel 列 对此进行了描述。
对于不提供适当的 INSERT 表单以可以确定地与输入值对齐生成服务器值的后端,或对于 Table
配置具有其他类型的服务器生成的主键值的情况,“insertmanyvalues” 模式将在请求保证 RETURNING 排序时使用 非批处理 模式。
另请参阅
- Microsoft SQL Server 的基本原理
- “使用 SELECT 和 ORDER BY 填充行的 INSERT 查询保证了如何计算标识值,但不能保证插入行的顺序。”
learn.microsoft.com/en-us/sql/t-sql/statements/insert-transact-sql?view=sql-server-ver16#limitations-and-restrictions
- PostgreSQL 批处理 INSERT 讨论
- 2018 年的原始描述
www.postgresql.org/message-id/29386.1528813619@sss.pgh.pa.us
2023 年的后续讨论 -www.postgresql.org/message-id/be108555-da2a-4abc-a46b-acbe8b55bd25%40app.fastmail.com
- MariaDB AUTO_INCREMENT 行为(使用与 MySQL 相同的 InnoDB 引擎):
dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html
dba.stackexchange.com/a/72099
非批处理模式操作
对于没有客户端主键值的Table
配置,并提供由服务器生成的主键值(或没有主键)的数据库无法以确定性或可排序的方式调用多个参数集相对于的情况,当“insertmanyvalues”功能被要求满足Insert.returning.sort_by_parameter_order
对于Insert
语句的要求时,可能会选择使用非批处理模式。
在这种模式下,保持了原始的 SQL 形式的 INSERT,并且“insertmanyvalues”功能将代替为每个参数集单独运行给定的语句,将返回的行组织成完整的结果集。与以前的 SQLAlchemy 版本不同,它在一个紧凑的循环中执行,最大限度地减少了 Python 的开销。在某些情况下,例如在 SQLite 上,“非批处理”模式的性能与“批处理”模式完全一样。
语句执行模型
对于“批处理”和“非批处理”两种模式,该功能将必须使用 DBAPI cursor.execute()
方法调用多个 INSERT 语句,在单个对 Core 级别的Connection.execute()
方法的调用范围内,每个语句包含多达固定数量的参数集。如下所述,此限制可配置为控制批处理大小。对cursor.execute()
的单独调用将被单独记录,并且也单独传递给事件侦听器,例如ConnectionEvents.before_cursor_execute()
(请参阅下面的日志记录和事件)。
配置哨兵列
在典型情况下,为了从给定表的主键提供具有确定性行顺序的 INSERT…RETURNING 功能,将自动确定一个哨兵列,并在无法识别时优雅地降级到“逐行”模式。作为完全可选的功能,为了对具有服务器生成的主键的表提供完整的“insertmanyvalues”批量性能,其默认生成函数与“sentinel”用例不兼容,其他非主键列可以标记为“sentinel”列,假设它们满足一定要求。一个典型的例子是一个具有客户端默认值的非主键 Uuid
列,例如 Python 的 uuid.uuid4()
函数。还有一种构造方法可以创建带有客户端整数计数器的简单整数列,以满足“insertmanyvalues”用例。
可以通过将 Column.insert_sentinel
添加到符合条件的列来指示哨兵列。最基本的“符合条件”列是一个非空、唯一的列,具有客户端默认值,例如以下 UUID 列:
import uuid from sqlalchemy import Column from sqlalchemy import FetchedValue from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import Uuid my_table = Table( "some_table", metadata, # assume some arbitrary server-side function generates # primary key values, so cannot be tracked by a bulk insert Column("id", String(50), server_default=FetchedValue(), primary_key=True), Column("data", String(50)), Column( "uniqueid", Uuid(), default=uuid.uuid4, nullable=False, unique=True, insert_sentinel=True, ), )
当使用 ORM Declarative 模型时,可以使用 mapped_column
结构来使用相同的形式:
import uuid from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column class Base(DeclarativeBase): pass class MyClass(Base): __tablename__ = "my_table" id: Mapped[str] = mapped_column(primary_key=True, server_default=FetchedValue()) data: Mapped[str] = mapped_column(String(50)) uniqueid: Mapped[uuid.UUID] = mapped_column( default=uuid.uuid4, unique=True, insert_sentinel=True )
虽然默认生成器生成的值必须是唯一的,但上述“哨兵”列上的实际 UNIQUE 约束(由 unique=True
参数指示)本身是可选的,如果不需要可以省略。
还有一种特殊形式的“插入哨兵”,它是一个专用的可空整数列,它利用了一个特殊的默认整数计数器,仅在“insertmanyvalues”操作期间使用;作为附加行为,该列将在 SQL 语句和结果集中省略自身,并以基本透明的方式行事。然而,它确实需要在实际数据库表中存在。这种类型的 Column
可以使用函数 insert_sentinel()
构建:
from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import Uuid from sqlalchemy import insert_sentinel Table( "some_table", metadata, Column("id", Integer, primary_key=True), Column("data", String(50)), insert_sentinel("sentinel"), )
当使用 ORM Declarative 时,提供了一个友好的版本 insert_sentinel()
,称为 orm_insert_sentinel()
,它具有在 Base 类或 mixin 上使用的能力;如果使用 declared_attr()
封装,该列将应用于所有表绑定的子类,包括联接继承层次结构内:
from sqlalchemy.orm import declared_attr from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column from sqlalchemy.orm import orm_insert_sentinel class Base(DeclarativeBase): @declared_attr def _sentinel(cls) -> Mapped[int]: return orm_insert_sentinel() class MyClass(Base): __tablename__ = "my_table" id: Mapped[str] = mapped_column(primary_key=True, server_default=FetchedValue()) data: Mapped[str] = mapped_column(String(50)) class MySubClass(MyClass): __tablename__ = "sub_table" id: Mapped[str] = mapped_column(ForeignKey("my_table.id"), primary_key=True) class MySingleInhClass(MyClass): pass
在上面的示例中,“my_table”和“sub_table”都将有一个额外的整数列名为“_sentinel”,可以被“insertmanyvalues”功能使用,以帮助优化 ORM 使用的批量插入。 #### 配置哨兵列
在典型情况下,“insertmanyvalues”功能为了提供具有确定性行顺序的 INSERT…RETURNING 将自动从给定表的主键确定一个哨兵列,如果无法识别,则优雅地降级为“逐行”模式。作为完全可选的功能,为了使具有服务器生成的主键的表获得完整的“insertmanyvalues”批量性能,其默认生成函数与“哨兵”用例不兼容,其他非主键列可以被标记为“哨兵”列,假设它们满足某些要求。一个典型的例子是一个非主键Uuid
列,具有客户端默认值,例如 Python 的uuid.uuid4()
函数。还有一种构造方法,用于创建简单的整数列,具有面向“insertmanyvalues”用例的客户端整数计数器。
可以通过将Column.insert_sentinel
添加到合格的列来指示哨兵列。最基本的“合格”列是一个非空、唯一的列,具有客户端默认值,例如 UUID 列如下所示:
import uuid from sqlalchemy import Column from sqlalchemy import FetchedValue from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import Uuid my_table = Table( "some_table", metadata, # assume some arbitrary server-side function generates # primary key values, so cannot be tracked by a bulk insert Column("id", String(50), server_default=FetchedValue(), primary_key=True), Column("data", String(50)), Column( "uniqueid", Uuid(), default=uuid.uuid4, nullable=False, unique=True, insert_sentinel=True, ), )
在使用 ORM 声明性模型时,可以使用mapped_column
构造相同的形式:
import uuid from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column class Base(DeclarativeBase): pass class MyClass(Base): __tablename__ = "my_table" id: Mapped[str] = mapped_column(primary_key=True, server_default=FetchedValue()) data: Mapped[str] = mapped_column(String(50)) uniqueid: Mapped[uuid.UUID] = mapped_column( default=uuid.uuid4, unique=True, insert_sentinel=True )
尽管默认生成器生成的值必须是唯一的,但上述“哨兵”列上的实际 UNIQUE 约束,由unique=True
参数指示,本身是可选的,如果不需要可以省略。
还有一种特殊形式的“插入哨兵”,它是一个专用的可空整数列,利用一个特殊的默认整数计数器,仅在“insertmanyvalues”操作期间使用;作为额外的行为,该列将在 SQL 语句和结果集中省略自身,并以基本透明的方式行为。但是,它确实需要在实际数据库表中物理存在。可以使用函数insert_sentinel()
构造这种Column
的样式:
from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import Uuid from sqlalchemy import insert_sentinel Table( "some_table", metadata, Column("id", Integer, primary_key=True), Column("data", String(50)), insert_sentinel("sentinel"), )
当使用 ORM 声明时,提供了一种友好的与声明性兼容的insert_sentinel()
版本,称为orm_insert_sentinel()
,它具有在基类或混合类上使用的能力;如果使用declared_attr()
打包,该列将应用于所有绑定到表的子类,包括在连接继承层次结构中的子类:
from sqlalchemy.orm import declared_attr from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column from sqlalchemy.orm import orm_insert_sentinel class Base(DeclarativeBase): @declared_attr def _sentinel(cls) -> Mapped[int]: return orm_insert_sentinel() class MyClass(Base): __tablename__ = "my_table" id: Mapped[str] = mapped_column(primary_key=True, server_default=FetchedValue()) data: Mapped[str] = mapped_column(String(50)) class MySubClass(MyClass): __tablename__ = "sub_table" id: Mapped[str] = mapped_column(ForeignKey("my_table.id"), primary_key=True) class MySingleInhClass(MyClass): pass
在上述示例中,“my_table”和“sub_table”都将有一个名为“_sentinel”的额外整数列,该列可供“insertmanyvalues”功能使用,以帮助优化 ORM 使用的批量插入。
控制批量大小
“insertmanyvalues”的一个关键特点是,INSERT 语句的大小限制为固定的最大“values”子句数以及方言特定的固定总绑定参数数,这些参数可以同时表示在一个 INSERT 语句中。当给定的参数字典数量超过固定限制,或者当要在单个 INSERT 语句中呈现的绑定参数的总数超过固定限制(这两个固定限制是分开的)时,将在单个Connection.execute()
调用的范围内调用多个 INSERT 语句,其中每个 INSERT 语句都容纳一部分参数字典,称为“批量”。然后,每个“批量”中表示的参数字典数量称为“批量大小”。例如,批量大小为 500 意味着每个发出的 INSERT 语句最多会插入 500 行。
能够调整批量大小可能非常重要,因为较大的批量大小对于值集本身相对较小的 INSERT 可能更有效率,而较小的批量大小可能更适用于使用非常大的值集的 INSERT,其中渲染的 SQL 大小以及一次传递的总数据大小可能受益于根据后端行为和内存约束而限制为某个大小。因此,批量大小可以在每个Engine
以及每个语句的基础上进行配置。另一方面,参数限制是根据正在使用的数据库的已知特性固定的。
大多数后端的批处理大小默认为 1000,还有一个每方言的“最大参数数”限制因素,可能会在每个语句的基础上进一步减小批处理大小。最大参数数因方言和服务器版本而异;最大尺寸为 32700(选择与 PostgreSQL 的限制 32767 和 SQLite 的现代限制 32766 相距较远,同时为语句中的其他参数以及 DBAPI 的怪癖留出空间)。旧版本的 SQLite(3.32.0 之前)将此值设置为 999。MariaDB 没有确定的限制,但是 32700 仍然作为 SQL 消息大小的限制因素。
“批处理大小”的值可以通过Engine
的create_engine.insertmanyvalues_page_size
参数影响整个引擎。例如,要影响每个语句中包含最多 100 个参数集的 INSERT 语句:
e = create_engine("sqlite://", insertmanyvalues_page_size=100)
批处理大小也可以根据每个语句使用Connection.execution_options.insertmanyvalues_page_size
执行选项进行设置,例如每次执行:
with e.begin() as conn: result = conn.execute( table.insert().returning(table.c.id), parameterlist, execution_options={"insertmanyvalues_page_size": 100}, )
或者在语句本身上进行配置:
stmt = ( table.insert() .returning(table.c.id) .execution_options(insertmanyvalues_page_size=100) ) with e.begin() as conn: result = conn.execute(stmt, parameterlist)
日志和事件
“insertmanyvalues”功能与 SQLAlchemy 的语句日志记录以及游标事件(如ConnectionEvents.before_cursor_execute()
)完全集成。当参数列表被分成单独的批次时,每个 INSERT 语句都会单独记录并传递给事件处理程序。这与 SQLAlchemy 1.x 系列的以前版本中仅基于 psycopg2 的功能的工作方式相比是一个重大变化,以前的版本中多个 INSERT 语句的生成被隐藏在日志记录和事件之外。日志显示将截断用于可读性的长参数列表,并且还将指示每个语句的特定批次。下面的示例说明了此日志的摘录:
INSERT INTO a (data, x, y) VALUES (?, ?, ?), ... 795 characters truncated ... (?, ?, ?), (?, ?, ?) RETURNING id [generated in 0.00177s (insertmanyvalues) 1/10 (unordered)] ('d0', 0, 0, 'd1', ... INSERT INTO a (data, x, y) VALUES (?, ?, ?), ... 795 characters truncated ... (?, ?, ?), (?, ?, ?) RETURNING id [insertmanyvalues 2/10 (unordered)] ('d100', 100, 1000, 'd101', ... ... INSERT INTO a (data, x, y) VALUES (?, ?, ?), ... 795 characters truncated ... (?, ?, ?), (?, ?, ?) RETURNING id [insertmanyvalues 10/10 (unordered)] ('d900', 900, 9000, 'd901', ...
当非批处理模式发生时,日志将指示此情况以及 insertmanyvalues 消息:
... INSERT INTO a (data, x, y) VALUES (?, ?, ?) RETURNING id [insertmanyvalues 67/78 (ordered; batch not supported)] ('d66', 66, 66) INSERT INTO a (data, x, y) VALUES (?, ?, ?) RETURNING id uuid from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column class Base(DeclarativeBase): pass class MyClass(Base): __tablename__ = "my_table" id: Mapped[str] = mapped_column(primary_key=True, server_default=FetchedValue()) data: Mapped[str] = mapped_column(String(50)) uniqueid: Mapped[uuid.UUID] = mapped_column( default=uuid.uuid4, unique=True, insert_sentinel=True )
虽然默认生成器生成的值必须是唯一的,但上述“哨兵”列上的实际 UNIQUE 约束(由 unique=True
参数指示)本身是可选的,如果不需要可以省略。
还有一种特殊形式的“插入哨兵”,它是一个专用的可空整数列,它利用了一个特殊的默认整数计数器,仅在“insertmanyvalues”操作期间使用;作为附加行为,该列将在 SQL 语句和结果集中省略自身,并以基本透明的方式行事。然而,它确实需要在实际数据库表中存在。这种类型的 Column
可以使用函数 insert_sentinel()
构建:
from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import Uuid from sqlalchemy import insert_sentinel Table( "some_table", metadata, Column("id", Integer, primary_key=True), Column("data", String(50)), insert_sentinel("sentinel"), )
当使用 ORM Declarative 时,提供了一个友好的版本 insert_sentinel()
,称为 orm_insert_sentinel()
,它具有在 Base 类或 mixin 上使用的能力;如果使用 declared_attr()
封装,该列将应用于所有表绑定的子类,包括联接继承层次结构内:
from sqlalchemy.orm import declared_attr from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column from sqlalchemy.orm import orm_insert_sentinel class Base(DeclarativeBase): @declared_attr def _sentinel(cls) -> Mapped[int]: return orm_insert_sentinel() class MyClass(Base): __tablename__ = "my_table" id: Mapped[str] = mapped_column(primary_key=True, server_default=FetchedValue()) data: Mapped[str] = mapped_column(String(50)) class MySubClass(MyClass): __tablename__ = "sub_table" id: Mapped[str] = mapped_column(ForeignKey("my_table.id"), primary_key=True) class MySingleInhClass(MyClass): pass
在上面的示例中,“my_table”和“sub_table”都将有一个额外的整数列名为“_sentinel”,可以被“insertmanyvalues”功能使用,以帮助优化 ORM 使用的批量插入。 #### 配置哨兵列
在典型情况下,“insertmanyvalues”功能为了提供具有确定性行顺序的 INSERT…RETURNING 将自动从给定表的主键确定一个哨兵列,如果无法识别,则优雅地降级为“逐行”模式。作为完全可选的功能,为了使具有服务器生成的主键的表获得完整的“insertmanyvalues”批量性能,其默认生成函数与“哨兵”用例不兼容,其他非主键列可以被标记为“哨兵”列,假设它们满足某些要求。一个典型的例子是一个非主键Uuid
列,具有客户端默认值,例如 Python 的uuid.uuid4()
函数。还有一种构造方法,用于创建简单的整数列,具有面向“insertmanyvalues”用例的客户端整数计数器。
可以通过将Column.insert_sentinel
添加到合格的列来指示哨兵列。最基本的“合格”列是一个非空、唯一的列,具有客户端默认值,例如 UUID 列如下所示:
import uuid from sqlalchemy import Column from sqlalchemy import FetchedValue from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import Uuid my_table = Table( "some_table", metadata, # assume some arbitrary server-side function generates # primary key values, so cannot be tracked by a bulk insert Column("id", String(50), server_default=FetchedValue(), primary_key=True), Column("data", String(50)), Column( "uniqueid", Uuid(), default=uuid.uuid4, nullable=False, unique=True, insert_sentinel=True, ), )
在使用 ORM 声明性模型时,可以使用mapped_column
构造相同的形式:
import uuid from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column class Base(DeclarativeBase): pass class MyClass(Base): __tablename__ = "my_table" id: Mapped[str] = mapped_column(primary_key=True, server_default=FetchedValue()) data: Mapped[str] = mapped_column(String(50)) uniqueid: Mapped[uuid.UUID] = mapped_column( default=uuid.uuid4, unique=True, insert_sentinel=True )
尽管默认生成器生成的值必须是唯一的,但上述“哨兵”列上的实际 UNIQUE 约束,由unique=True
参数指示,本身是可选的,如果不需要可以省略。
还有一种特殊形式的“插入哨兵”,它是一个专用的可空整数列,利用一个特殊的默认整数计数器,仅在“insertmanyvalues”操作期间使用;作为额外的行为,该列将在 SQL 语句和结果集中省略自身,并以基本透明的方式行为。但是,它确实需要在实际数据库表中物理存在。可以使用函数insert_sentinel()
构造这种Column
的样式:
from sqlalchemy import Column from sqlalchemy import Integer from sqlalchemy import String from sqlalchemy import Table from sqlalchemy import Uuid from sqlalchemy import insert_sentinel Table( "some_table", metadata, Column("id", Integer, primary_key=True), Column("data", String(50)), insert_sentinel("sentinel"), )
当使用 ORM 声明时,提供了一种友好的与声明性兼容的insert_sentinel()
版本,称为orm_insert_sentinel()
,它具有在基类或混合类上使用的能力;如果使用declared_attr()
打包,该列将应用于所有绑定到表的子类,包括在连接继承层次结构中的子类:
from sqlalchemy.orm import declared_attr from sqlalchemy.orm import DeclarativeBase from sqlalchemy.orm import Mapped from sqlalchemy.orm import mapped_column from sqlalchemy.orm import orm_insert_sentinel class Base(DeclarativeBase): @declared_attr def _sentinel(cls) -> Mapped[int]: return orm_insert_sentinel() class MyClass(Base): __tablename__ = "my_table" id: Mapped[str] = mapped_column(primary_key=True, server_default=FetchedValue()) data: Mapped[str] = mapped_column(String(50)) class MySubClass(MyClass): __tablename__ = "sub_table" id: Mapped[str] = mapped_column(ForeignKey("my_table.id"), primary_key=True) class MySingleInhClass(MyClass): pass
在上述示例中,“my_table”和“sub_table”都将有一个名为“_sentinel”的额外整数列,该列可供“insertmanyvalues”功能使用,以帮助优化 ORM 使用的批量插入。
控制批量大小
“insertmanyvalues”的一个关键特点是,INSERT 语句的大小限制为固定的最大“values”子句数以及方言特定的固定总绑定参数数,这些参数可以同时表示在一个 INSERT 语句中。当给定的参数字典数量超过固定限制,或者当要在单个 INSERT 语句中呈现的绑定参数的总数超过固定限制(这两个固定限制是分开的)时,将在单个Connection.execute()
调用的范围内调用多个 INSERT 语句,其中每个 INSERT 语句都容纳一部分参数字典,称为“批量”。然后,每个“批量”中表示的参数字典数量称为“批量大小”。例如,批量大小为 500 意味着每个发出的 INSERT 语句最多会插入 500 行。
能够调整批量大小可能非常重要,因为较大的批量大小对于值集本身相对较小的 INSERT 可能更有效率,而较小的批量大小可能更适用于使用非常大的值集的 INSERT,其中渲染的 SQL 大小以及一次传递的总数据大小可能受益于根据后端行为和内存约束而限制为某个大小。因此,批量大小可以在每个Engine
以及每个语句的基础上进行配置。另一方面,参数限制是根据正在使用的数据库的已知特性固定的。
大多数后端的批处理大小默认为 1000,还有一个每方言的“最大参数数”限制因素,可能会在每个语句的基础上进一步减小批处理大小。最大参数数因方言和服务器版本而异;最大尺寸为 32700(选择与 PostgreSQL 的限制 32767 和 SQLite 的现代限制 32766 相距较远,同时为语句中的其他参数以及 DBAPI 的怪癖留出空间)。旧版本的 SQLite(3.32.0 之前)将此值设置为 999。MariaDB 没有确定的限制,但是 32700 仍然作为 SQL 消息大小的限制因素。
“批处理大小”的值可以通过Engine
的create_engine.insertmanyvalues_page_size
参数影响整个引擎。例如,要影响每个语句中包含最多 100 个参数集的 INSERT 语句:
e = create_engine("sqlite://", insertmanyvalues_page_size=100)
批处理大小也可以根据每个语句使用Connection.execution_options.insertmanyvalues_page_size
执行选项进行设置,例如每次执行:
with e.begin() as conn: result = conn.execute( table.insert().returning(table.c.id), parameterlist, execution_options={"insertmanyvalues_page_size": 100}, )
或者在语句本身上进行配置:
stmt = ( table.insert() .returning(table.c.id) .execution_options(insertmanyvalues_page_size=100) ) with e.begin() as conn: result = conn.execute(stmt, parameterlist)
日志和事件
“insertmanyvalues”功能与 SQLAlchemy 的语句日志记录以及游标事件(如ConnectionEvents.before_cursor_execute()
)完全集成。当参数列表被分成单独的批次时,每个 INSERT 语句都会单独记录并传递给事件处理程序。这与 SQLAlchemy 1.x 系列的以前版本中仅基于 psycopg2 的功能的工作方式相比是一个重大变化,以前的版本中多个 INSERT 语句的生成被隐藏在日志记录和事件之外。日志显示将截断用于可读性的长参数列表,并且还将指示每个语句的特定批次。下面的示例说明了此日志的摘录:
INSERT INTO a (data, x, y) VALUES (?, ?, ?), ... 795 characters truncated ... (?, ?, ?), (?, ?, ?) RETURNING id [generated in 0.00177s (insertmanyvalues) 1/10 (unordered)] ('d0', 0, 0, 'd1', ... INSERT INTO a (data, x, y) VALUES (?, ?, ?), ... 795 characters truncated ... (?, ?, ?), (?, ?, ?) RETURNING id [insertmanyvalues 2/10 (unordered)] ('d100', 100, 1000, 'd101', ... ... INSERT INTO a (data, x, y) VALUES (?, ?, ?), ... 795 characters truncated ... (?, ?, ?), (?, ?, ?) RETURNING id [insertmanyvalues 10/10 (unordered)] ('d900', 900, 9000, 'd901', ...
当非批处理模式发生时,日志将指示此情况以及 insertmanyvalues 消息:
... INSERT INTO a (data, x, y) VALUES (?, ?, ?) RETURNING id [insertmanyvalues 67/78 (ordered; batch not supported)] ('d66', 66, 66) INSERT INTO a (data, x, y) VALUES (?, ?, ?) RETURNING id [insertmanyvalues 68/78 (ordered; batch not supported)]