SqlAlchemy 2.0 中文文档(八十)(2)https://developer.aliyun.com/article/1559880
新的模式功能
sqlalchemy.schema
包得到了一些长期需要的关注。最显著的变化是新扩展的 DDL 系统。在 SQLAlchemy 中,自 0.5 版本以来,可以创建自定义 DDL 字符串并将其与表或元数据对象关联:
from sqlalchemy.schema import DDL DDL("CREATE TRIGGER users_trigger ...").execute_at("after-create", metadata)
现在完整的 DDL 构造都在同一系统下可用,包括用于 CREATE TABLE、ADD CONSTRAINT 等的构造:
from sqlalchemy.schema import Constraint, AddConstraint AddContraint(CheckConstraint("value > 5")).execute_at("after-create", mytable)
此外,所有 DDL 对象现在都是常规的ClauseElement
对象,就像任何其他 SQLAlchemy 表达式对象一样:
from sqlalchemy.schema import CreateTable create = CreateTable(mytable) # dumps the CREATE TABLE as a string print(create) # executes the CREATE TABLE statement engine.execute(create)
并且使用sqlalchemy.ext.compiler
扩展,您可以制作自己的:
from sqlalchemy.schema import DDLElement from sqlalchemy.ext.compiler import compiles class AlterColumn(DDLElement): def __init__(self, column, cmd): self.column = column self.cmd = cmd @compiles(AlterColumn) def visit_alter_column(element, compiler, **kw): return "ALTER TABLE %s ALTER COLUMN %s %s ..." % ( element.column.table.name, element.column.name, element.cmd, ) engine.execute(AlterColumn(table.c.mycolumn, "SET DEFAULT 'test'"))
废弃/移除的模式元素
schema 包也得到了极大简化。许多在 0.5 版本中被废弃的选项和方法已被移除。其他鲜为人知的访问器和方法也已被移除。
- “owner”关键字参数已从
Table
中移除。使用“schema”表示要预先添加到表名的任何命名空间。 - 废弃的
MetaData.connect()
和ThreadLocalMetaData.connect()
已被移除 - 将“bind”属性发送到绑定元数据。 - 废弃的 metadata.table_iterator()方法已被移除(使用 sorted_tables)。
- 从
DefaultGenerator
和子类中移除了“metadata”参数,但仍然在Sequence
上本地存在,这是 DDL��的一个独立构造。 - 废弃的
PassiveDefault
- 使用DefaultClause
。 - 从
Index
和Constraint
对象中移除了公共可变性:
ForeignKeyConstraint.append_element()
Index.append_column()
UniqueConstraint.append_column()
PrimaryKeyConstraint.add()
PrimaryKeyConstraint.remove()
这些应该以声明性的方式构造(即一次性构造)。
- 其他已移除的内容:
Table.key
(不知道用于什么)Column.bind
(通过列的table.bind
获取)Column.metadata
(通过列的table.metadata
获取)Column.sequence
(使用列的默认值column.default
)
其他行为变化
UniqueConstraint
、Index
、PrimaryKeyConstraint
都接受列名或列对象的列表作为参数。ForeignKey
上的use_alter
标志现在是一个快捷选项,用于可以手动构造使用DDL()
事件系统的操作。此重构的一个副作用是,具有use_alter=True
的ForeignKeyConstraint
对象将 不会 在 SQLite 上发出,因为 SQLite 不支持外键的 ALTER。这对 SQLite 的行为没有影响,因为 SQLite 实际上不遵守外键约束。Table.primary_key
不可分配 - 使用table.append_constraint(PrimaryKeyConstraint(...))
- 带有
ForeignKey
但没有类型定义的Column
定义,例如Column(name, ForeignKey(sometable.c.somecol))
曾用于获取引用列的类型。现在,对于该自动类型推断的支持是部分的,可能并不适用于所有情况。
废弃/移除的模式元素
模式包也已经大大简化。在 0.5 版本中已弃用的许多选项和方法已被移除。其他不太常用的访问器和方法也已被移除。
- 从
Table
中移除了“owner”关键字参数。使用“schema”表示要预先添加到表名的任何命名空间。 - 废弃的
MetaData.connect()
和ThreadLocalMetaData.connect()
已被移除 - 发送“bind”属性以绑定元数据。 - 已移除的废弃的
metadata.table_iterator()
方法(使用sorted_tables
) - 从
DefaultGenerator
和子类中移除了“metadata”参数,但在Sequence
中仍然局部存在,Sequence
是 DDL 中的一个独立构造。 - 废弃的
PassiveDefault
- 使用DefaultClause
。 - 从
Index
和Constraint
对象中移除了公共可变性:
ForeignKeyConstraint.append_element()
Index.append_column()
UniqueConstraint.append_column()
PrimaryKeyConstraint.add()
PrimaryKeyConstraint.remove()
这些应该以声明性的方式构造(即一次性构造)。
- 其他已移除的内容:
Table.key
(不知道用于什么)Column.bind
(通过列的table.bind
获取)Column.metadata
(通过列的table.metadata
获取)Column.sequence
(使用列的默认值column.default
)
其他行为变化
UniqueConstraint
、Index
、PrimaryKeyConstraint
都接受列名或列对象的列表作为参数。ForeignKey
上的use_alter
标志现在是手动构造使用DDL()
事件系统的操作的快捷选项。这个重构的副作用是,带有use_alter=True
的ForeignKeyConstraint
对象将不会在 SQLite 上发出,因为 SQLite 不支持外键的 ALTER。这对 SQLite 的行为没有影响,因为 SQLite 实际上不遵守 FOREIGN KEY 约束。Table.primary_key
不可分配 - 使用table.append_constraint(PrimaryKeyConstraint(...))
Column
定义中有一个ForeignKey
而没有类型,例如Column(name, ForeignKey(sometable.c.somecol))
用于获取引用列的类型。现在对于这种自动类型推断的支持是部分的,并且可能不适用于所有情况。
日志开放
通过多次额外的方法调用,你可以在创建引擎、池或映射器后设置 INFO 和 DEBUG 的日志级别,日志将开始记录。isEnabledFor(INFO)
方法现在每个 Connection
调用一次,如果已在父连接上启用,则每个 ResultProxy
调用一次 isEnabledFor(DEBUG)
。池日志发送到 log.info()
和 log.debug()
,没有检查 - 请注意,池的检出/归还通常是每个事务一次。
反射/检查器 API
反射系统,允许通过 Table('sometable', metadata, autoload=True)
反射表列已被开放到其自己的细粒度 API 中,该 API 允许直接检查数据库元素,如表、列、约束、索引等等。此 API 将返回值表示为简单的字符串、字典和 TypeEngine
对象列表。现在 autoload=True
的内部构建在此系统之上,将原始数据库信息转换为 sqlalchemy.schema
构造的过程集中化,并且各个方言的契约大大简化,极大地减少了不同后端之间的错误和不一致性。
使用检查器:
from sqlalchemy.engine.reflection import Inspector insp = Inspector.from_engine(my_engine) print(insp.get_schema_names())
from_engine()
方法在某些情况下将提供一个具有额外功能的特定于后端的检查器,例如 PostgreSQL 提供一个 get_table_oid()
方法:
my_engine = create_engine("postgresql://...") pg_insp = Inspector.from_engine(my_engine) print(pg_insp.get_table_oid("my_table"))
RETURNING 支持
insert()
、update()
和 delete()
构造现在支持一个 returning()
方法,该方法对应于 PostgreSQL、Oracle、MS-SQL 和 Firebird 支持的 SQL RETURNING 子句。目前其他后端不支持。
给定一个与 select()
构造方式相同的列表达式列表,这些列的值将作为常规结果集返回:
result = connection.execute( table.insert().values(data="some data").returning(table.c.id, table.c.timestamp) ) row = result.first() print("ID:", row["id"], "Timestamp:", row["timestamp"])
在四个支持的后端中,RETURNING 的实现差异很大,在 Oracle 的情况下,需要复杂地使用 OUT 参数,这些参数被重新路由到一个“模拟”结果集中,在 MS-SQL 的情况下使用笨拙的 SQL 语法。RETURNING 的使用受到限制:
- 它不适用于任何“executemany()”风格的执行。这是所有支持的 DBAPI 的限制。
- 某些后端,如 Oracle,仅支持返回单行的 RETURNING - 这包括 UPDATE 和 DELETE 语句,意味着 update()或 delete()构造必须仅匹配单行,否则会引发错误(由 Oracle 而不是 SQLAlchemy 引发)。
当单行 INSERT 语句需要获取新生成的主键值时,SQLAlchemy 也会自动使用 RETURNING,当其他地方没有通过显式的returning()
调用指定时。这意味着对于需要主键值的插入语句,不再需要“SELECT nextval(sequence)”预执行。说实话,隐式的 RETURNING 特性确实比旧的“select nextval()”系统多产生了更多的方法开销,后者使用了一个快速而肮脏的 cursor.execute()来获取序列值,并且在 Oracle 的情况下需要额外绑定 out 参数。因此,如果方法/协议开销比额外的数据库往返开销更昂贵,则可以通过向create_engine()
指定implicit_returning=False
来禁用该特性。
类型系统更改
新架构
在幕后,类型系统已经完全重构,以实现两个目标:
- 将绑定参数和结果行值的处理分开,通常是 DBAPI 的要求,与类型本身的 SQL 规范分开,这是数据库的要求。这与将数据库 SQL 行为与 DBAPI 分离的总体方言重构一致。
- 为从
TypeEngine
对象生成 DDL 和基于列反射构造TypeEngine
对象建立清晰一致的合同。
这些变化的亮点包括:
- 方言中类型的构造已彻底改写。方言现在将公开可用的类型定义为仅大写名称,并使用下划线标识符(即私有)进行内部实现类型。类型在 SQL 和 DDL 中的表达方式已移至编译器系统。这样做的效果是大多数方言中几乎没有类型对象。关于此架构的详细文档可供方言作者使用,在[source:/lib/sqlalchemy/dialects/type_migration_guidelines.txt]中。
- 现在类型的反射将返回 types.py 中的确切大写类型,或者如果该类型不是标准 SQL 类型,则在方言本身中返回大写类型。这意味着反射现在返回更准确的反射类型信息。
- 用户定义的类型,其子类为
TypeEngine
且希望提供get_col_spec()
,现在应该将其子类化为UserDefinedType
。 - 所有类型类上的
result_processor()
方法现在接受一个额外的参数coltype
。这是附加到 cursor.description 的 DBAPI 类型对象,并且在适用时应该使用它来做出更好的决定,以确定应返回什么类型的结果处理可调用对象。理想情况下,结果处理函数永远不应该使用isinstance()
,因为这是一个在这个级别上昂贵的调用。
本地 Unicode 模式
随着更多的 DBAPI 支持直接返回 Python unicode 对象,基本方言现在在建立第一个连接时执行检查,以确定 DBAPI 是否为基本 VARCHAR 值的基本选择返回 Python unicode 对象。如果是这样,String
类型及其所有子类(即Text
,Unicode
等)在接收到结果行时将跳过“unicode”检查/转换步骤。对于大型结果集,这将大幅提高性能。目前已知“unicode 模式”可以与以下内容配合使用:
- sqlite3 / pysqlite
- psycopg2 - SQLA 0.6 现在在每个 psycopg2 连接对象上默认使用“UNICODE”类型扩展
- pg8000
- cx_oracle(我们使用输出处理器 - 很好的功能!)
其他类型可以根据需要选择禁用 unicode 处理,例如与 MS-SQL 一起使用时的NVARCHAR
类型。
特别是,如果基于以前返回非 unicode 字符串的 DBAPI 的应用程序,则“本地 unicode”模式具有明显不同的默认行为 - 声明为String
或VARCHAR
的列现在默认返回 unicode,而以前则返回字符串。这可能会破坏期望非 unicode 字符串的代码。可以通过将use_native_unicode=False
传递给create_engine()
来禁用 psycopg2 的“本地 unicode”模式。
对于明确不希望使用 unicode 对象的字符串列,更一般的解决方案是使用TypeDecorator
将 unicode 转换回 utf-8,或者任何所需的格式:
class UTF8Encoded(TypeDecorator): """Unicode type which coerces to utf-8.""" impl = sa.VARCHAR def process_result_value(self, value, dialect): if isinstance(value, unicode): value = value.encode("utf-8") return value
请注意,assert_unicode
标志现已弃用。SQLAlchemy 允许 DBAPI 和正在使用的后端数据库在可用时处理 Unicode 参数,并且通过检查传入类型来增加操作开销;像 sqlite 和 PostgreSQL 这样的现代系统将在其端口上引发编码错误,如果传递的数据无效。在 SQLAlchemy 确实需要将绑定参数从 Python Unicode 强制转换为编码字符串时,或者当显式使用 Unicode 类型时,如果对象是字节串,则会发出警告。可以使用 Python 警告过滤器抑制或将此警告转换为异常,该过滤器的文档在:docs.python.org/library/warnings.html
通用枚举类型
现在我们在 types
模块中有一个 Enum
。这是一个字符串类型,给定一组“标签”,限制给这些标签的可能值。默认情况下,此类型生成一个 VARCHAR
,其大小为最大标签的大小,并在 CREATE TABLE 语句中对表施加 CHECK 约束。当使用 MySQL 时,默认情况下该类型使用 MySQL 的 ENUM 类型;当使用 PostgreSQL 时,该类型将使用 CREATE TYPE AS ENUM
生成用户定义类型。为了在 PostgreSQL 中创建该类型,必须在构造函数中指定 name
参数。该类型还接受一个 native_enum=False
选项,它将为所有数据库使用 VARCHAR/CHECK 策略。请注意,PostgreSQL 的 ENUM 类型目前无法与 pg8000 或 zxjdbc 一起使用。
反射返回方言特定类型
反射现在从数据库返回最具体的类型。也就是说,如果你使用 String
创建一个表,然后将其反射回来,反射的列可能是 VARCHAR
。对于支持更具体类型形式的方言,你会得到相应的类型。因此,在 Oracle 上,Text
类型会返回 oracle.CLOB
,LargeBinary
可能是 mysql.MEDIUMBLOB
等等。这里的明显优势在于反射尽可能保留来自数据库的信息。
一些处理表元数据的应用程序可能希望在反射表和/或非反射表之间比较类型。TypeEngine
上有一个半私有访问器叫做 _type_affinity
,以及一个相关的比较辅助函数 _compare_type_affinity
。该访问器返回与类型对应的“通用” types
类:
>>> String(50)._compare_type_affinity(postgresql.VARCHAR(50)) True >>> Integer()._compare_type_affinity(mysql.REAL) False
杂项 API 变更
通常的“通用”类型仍然是通用系统中使用的一般类型,即 String
、Float
、DateTime
。这里有一些变化:
- 类型不再猜测默认参数。特别是
Numeric
、Float
,以及 NUMERIC、FLOAT、DECIMAL 的子类,除非指定,否则不会生成任何长度或比例。这也包括有争议的String
和VARCHAR
类型(尽管 MySQL 方言在要求不带长度的 VARCHAR 时会预先引发错误)。不假设任何默认值,如果它们在 CREATE TABLE 语句中使用,并且底层数据库不允许这些类型的非长度版本,则会引发错误。 Binary
类型已经改名为LargeBinary
,用于 BLOB/BYTEA/类似类型。对于BINARY
和VARBINARY
,直接使用types.BINARY
、types.VARBINARY
,以及在 MySQL 和 MS-SQL 方言中。- 当
PickleType
的mutable=True
时,现在使用==
进行值的比较,除非指定了带有比较函数的 “comparator” 参数给该类型。如果您要 pickle 一个自定义对象,应该实现一个__eq__()
方法,以确保基于值的比较准确。 - Numeric 和 Float 的默认 “precision” 和 “scale” 参数已移除,现在默认为 None。NUMERIC 和 FLOAT 将默认不带数字参数呈现,除非提供这些值。
- SQLite 上的 DATE、TIME 和 DATETIME 类型现在可以使用可选的 “storage_format” 和 “regexp” 参数。“storage_format” 可用于使用自定义字符串格式存储这些类型。“regexp” 允许使用自定义正则表达式来匹配来自数据库的字符串值。
__legacy_microseconds__
在 SQLite 的Time
和DateTime
类型上不再受支持。您应该使用新的 “storage_format” 参数代替。- SQLite 上的
DateTime
类型现在默认使用更严格的正则表达式来匹配来自数据库的字符串。如果您使用存储在遗留格式中的数据,请使用新的 “regexp” 参数。
新架构
类型系统已在幕后完全重做,以实现两个目标:
- 将绑定参数和结果行值的处理分开,通常是 DBAPI 的要求,与类型本身的 SQL 规范分开,这是数据库的要求。这与将数据库 SQL 行为与 DBAPI 分开的整体方言重构保持一致。
- 为从
TypeEngine
对象生成 DDL 和基于列反射构造TypeEngine
对象建立清晰一致的合同。
这些变更的亮点包括:
- 方言中类型的构造已完全重构。方言现在专门使用大写名称定义公开可用的类型,并使用下划线标识符(即私有)定义内部实现类型。用于在 SQL 和 DDL 中表达类型的系统已移至编译器系统。这意味着大多数方言中的类型对象大大减少。有关此架构的详细文档,供方言作者参考在 [source:/lib/sqlalchemy/dialects/type_migration_guidelines.txt]。
- 现在,类型的反射返回 types.py 中的确切大写类型,或者如果类型不是标准 SQL 类型,则返回方言本身的大写类型。这意味着反射现在返回有关反射类型的更准确信息。
- 子类化
TypeEngine
并希望提供get_col_spec()
的用户定义类型现在应该子类化UserDefinedType
。 - 所有类型类上的
result_processor()
方法现在接受附加参数coltype
。这是附加到 cursor.description 的 DBAPI 类型对象,并且应该在适用时使用,以便更好地决定返回何种类型的结果处理可调用函数。理想情况下,结果处理函数永远不应该使用isinstance()
,因为这是在此级别的一个昂贵的调用。
本地 Unicode 模式
随着越来越多的 DBAPI 支持直接返回 Python Unicode 对象,基本方言现在在第一次连接时执行检查,以确定 DBAPI 是否为 VARCHAR 值的基本选择返回 Python Unicode 对象。如果是这样,String
类型和所有子类(即 Text
,Unicode
等)在接收到结果行时将跳过“unicode”检查/转换步骤。这为大型结果集提供了显著的性能提升。目前“unicode 模式”已知可与以下一起使用:
- sqlite3 / pysqlite
- psycopg2 - SQLA 0.6 现在默认在每个 psycopg2 连接对象上使用“UNICODE” 类型扩展
- pg8000
- cx_oracle(我们使用输出处理器 - 很好的功能!)
其他类型可能会根据需要禁用 Unicode 处理,例如在与 MS-SQL 一起使用时的 NVARCHAR
类型。
特别是,如果迁移基于以前返回非 Unicode 字符串的 DBAPI 的应用程序,则“本机 Unicode” 模式具有明显不同的默认行为 - 声明为 String
或 VARCHAR
的列现在默认返回 Unicode,而以前会返回字符串。这可能会破坏期望非 Unicode 字符串的代码。可以通过向 create_engine()
传递 use_native_unicode=False
来禁用 psycopg2 的“本机 Unicode” 模式。
对于明确不希望使用 Unicode 对象的字符串列的更一般解决方案是使用一个 TypeDecorator
,将 Unicode 转换回 utf-8,或者其他所需的格式:
class UTF8Encoded(TypeDecorator): """Unicode type which coerces to utf-8.""" impl = sa.VARCHAR def process_result_value(self, value, dialect): if isinstance(value, unicode): value = value.encode("utf-8") return value
请注意,assert_unicode
标志现已弃用。SQLAlchemy 允许 DBAPI 和后端数据库在可用时处理 Unicode 参数,并且不通过检查传入类型增加操作开销;现代系统如 sqlite 和 PostgreSQL 将在其端引发编码错误,如果传递了无效数据。在 SQLAlchemy 需要将绑定参数从 Python Unicode 强制转换为编码字符串时,或者显式使用 Unicode 类型时,如果对象是字节字符串,则会发出警告。可以使用 Python 警告过滤器文档中记录的警告过滤器将此警告抑制或转换为异常:docs.python.org/library/warnings.html
通用枚举类型
现在在 types
模块中有一个 Enum
。这是一个字符串类型,给定一组“标签”,这些标签限制了给定给这些标签的可能值。默认情况下,此类型生成一个使用最大标签大小的 VARCHAR
,并在 CREATE TABLE 语句中对表应用 CHECK 约束。在使用 MySQL 时,默认情况下,该类型使用 MySQL 的 ENUM 类型,而在使用 PostgreSQL 时,该类型将生成一个使用 CREATE TYPE AS ENUM
的用户定义类型。为了在 PostgreSQL 中创建类型,必须在构造函数中指定 name
参数。该类型还接受一个 native_enum=False
选项,该选项将为所有数据库发出 VARCHAR/CHECK 策略。请注意,PostgreSQL ENUM 类型目前无法与 pg8000 或 zxjdbc 一起使用。
反射返回方言特定类型
反射现在从数据库返回尽可能具体的类型。也就是说,如果使用 String
创建表,然后反射它,反射的列可能是 VARCHAR
。对于支持更具体形式的类型的方言,您将得到该类型。因此,在 Oracle 上,Text
类型将返回为 oracle.CLOB
,在 MySQL 上,LargeBinary
可能是 mysql.MEDIUMBLOB
等。这里的明显优势是反射尽可能保留数据库所说的信息。
一些处理表元数据的应用程序可能希望比较反射表和/或非反射表上的类型。TypeEngine
上有一个半私有访问器叫做 _type_affinity
,以及一个相关的比较助手 _compare_type_affinity
。此访问器返回类型对应的“通用” types
类:
>>> String(50)._compare_type_affinity(postgresql.VARCHAR(50)) True >>> Integer()._compare_type_affinity(mysql.REAL) False
杂项 API 更改
通常的“通用”类型仍然是使用的一般系统,即 String
、Float
、DateTime
。在那里有一些变化:
- 类型不再对默认参数进行任何猜测。特别是,
Numeric
、Float
,以及子类 NUMERIC、FLOAT、DECIMAL 不会生成任何长度或精度,除非指定。这也包括有争议的String
和VARCHAR
类型(尽管 MySQL 方言在要求渲染没有长度的 VARCHAR 时会预先引发错误)。不会假设任何默认值,如果它们在 CREATE TABLE 语句中使用,如果底层数据库不允许这些类型的无长度版本,则会引发错误。 Binary
类型已更名为LargeBinary
,用于 BLOB/BYTEA/类似类型。对于BINARY
和VARBINARY
,它们直接存在于types.BINARY
、types.VARBINARY
,以及 MySQL 和 MS-SQL 方言中。- 当
PickleType
的 mutable=True 时,现在使用 == 进行值比较,除非为该类型指定了带有比较函数的 “comparator” 参数。如果要对自定义对象进行 pickle,应实现一个__eq__()
方法,以确保基于值的比较准确。 - Numeric 和 Float 的默认“precision” 和 “scale” 参数已被移除,现在默认为 None。NUMERIC 和 FLOAT 现在默认不带任何数字参数呈现,除非提供这些值。
- SQLite 上的 DATE、TIME 和 DATETIME 类型现在可以使用可选的 “storage_format” 和 “regexp” 参数。“storage_format” 可以用于使用自定义字符串格式存储这些类型。“regexp” 允许使用自定义正则表达式来匹配数据库中的字符串值。
- 在 SQLite 的
Time
和DateTime
类型上不再支持__legacy_microseconds__
。您应该使用新的“storage_format”参数。 - SQLite 上的
DateTime
类型现在默认使用更严格的正则表达式来匹配来自数据库的字符串。如果使用存储在传统格式中的数据,则使用新的“regexp”参数。
ORM 更改
将 ORM 应用程序从 0.5 升级到 0.6 应该几乎不需要任何更改,因为 ORM 的行为几乎保持不变。有一些默认参数和名称更改,以及一些加载行为已经得到改进。
新的工作单元
工作单元的内部,主要是 topological.py
和 unitofwork.py
,已完全重写并大大简化。这不应对使用产生任何影响,因为所有现有的刷新行为都已完全保持不变(或者至少在我们的测试套件和少数经过大量测试的生产环境中被使用)。刷新() 的性能现在使用 20-30% 更少的方法调用,并且还应该使用更少的内存。源代码的意图和流程现在应该相当容易理解,刷新的架构在这一点上相当开放,为潜在的新领域提供了空间。刷新过程不再依赖递归,因此可以刷新任意大小和复杂度的刷新计划。此外,映射器的“保存”过程,发出 INSERT 和 UPDATE 语句,现在缓存了这两个语句的“编译”形式,因此在非常大的刷新中进一步大幅减少了调用次数。
与 0.6 或 0.5 早期版本相比,刷新的任何行为变化都应尽快向我们报告 - 我们将确保不会丢失任何功能。
对 query.update()
和 query.delete()
的更改
- 查询.update() 上的 ‘expire’ 选项已更名为 ‘fetch’,与 query.delete() 的匹配方式相同。
query.update()
和query.delete()
的同步策略默认为 ‘evaluate’。- update() 和 delete() 的 ‘synchronize’ 策略在失败时会引发错误。没有隐式回退到 “fetch”。评估的失败基于条件的结构,因此成功/失败是基于代码结构的确定性的。
relation()
现在正式更名为 relationship()
这是为了解决长期存在的问题,“relation”在关系代数术语中意味着“表或派生表”。relation()
名称,输入较少,将会持续存在可预见的未来,因此此更改应完全无痛。
子查询急切加载
添加了一种称为“子查询”加载的新型急切加载。这是一种在第一个 SQL 查询之后立即发出第二个 SQL 查询的加载,该查询为第一个查询中的所有父项加载完整集合,使用 INNER JOIN 向上连接到父项。子查询加载类似于当前的连接急切加载,使用subqueryload()
、subqueryload_all()
选项,以及设置在relationship()
上的lazy='subquery'
。子查询加载通常对加载许多较大的集合更有效,因为它无条件地使用 INNER JOIN,并且还不会重新加载父行。
eagerload()
, eagerload_all()
现在是joinedload()
, joinedload_all()
为了为新的子查询加载功能腾出空间,现有的eagerload()
/eagerload_all()
options are now superseded by joinedload()
and joinedload_all()
. The old names will hang around for the foreseeable future just like relation()
将会改变。
lazy=False|None|True|'dynamic'
现在接受lazy='noload'|'joined'|'subquery'|'select'|'dynamic'
继续开放加载器策略的主题,relationship()
上的标准关键字lazy
选项现在是,用于延迟加载的select
(通过属性访问时发出的 SELECT),用于急切连接加载的joined
,用于急切子查询加载的subquery
,不应出现任何负载的noload
,以及用于“动态”关系的dynamic
。旧的True
, False
, None
参数仍然被接受,行为与以前完全相同。
在关系、连接加载上的innerjoin=True
现在可以指示连接急切加载的标量和集合使用 INNER JOIN 而不是 OUTER JOIN。在 PostgreSQL 上,观察到这可以在某些查询中提供 300-600%的加速。为任何在 NOT NULLable 外键上的多对一关系设置此标志,类似地,为任何保证存在相关项的集合设置此标志。
在映射器级别:
mapper(Child, child) mapper( Parent, parent, properties={"child": relationship(Child, lazy="joined", innerjoin=True)}, )
在查询时级别:
session.query(Parent).options(joinedload(Parent.child, innerjoin=True)).all()
在relationship()
级别使用innerjoin=True
标志也将影响任何不覆盖该值的joinedload()
选项。
多对一增强
- 多对一关系现在在更少的情况下会触发惰性加载,包括在大多数情况下当新值替换旧值时不会获取“旧”值。
- 与连接表子类的多对一关系现在使用
get()
进行简单加载(称为“use_get”条件),即Related
->Sub(Base)
,无需重新定义基表的主连接条件。[ticket:1186] - 使用声明性列指定外键,即
ForeignKey(MyRelatedClass.id)
不会破坏“use_get”条件的发生。[ticket:1492] - relationship()、joinedload() 和 joinedload_all() 现在具有一个名为“innerjoin”的选项。指定
True
或False
来控制急切连接是构造为 INNER 还是 OUTER 连接。默认始终为False
。映射器选项将覆盖 relationship() 上指定的任何设置。通常应该为一对多、非空外键关系设置此选项,以允许改进的连接性能。[ticket:1544] - 联接急切加载的行为,当存在 LIMIT/OFFSET 时,使主查询包装在子查询中的情况现在除了所有急切加载都是一对多连接时有一个例外。在这些情况下,急切连接直接针对父表,同时限制/偏移量没有子查询的额外开销,因为一对多连接不会将行添加到结果中。
例如,在 0.5 版本中这个查询:
session.query(Address).options(eagerload(Address.user)).limit(10)
- 将生成如下 SQL 语句:
SELECT * FROM (SELECT * FROM addresses LIMIT 10) AS anon_1 LEFT OUTER JOIN users AS users_1 ON users_1.id = anon_1.addresses_user_id
- 这是因为任何急切的加载程序的存在都表明它们中的一部分或全部可能与多行集合相关,这将需要将任何种类的行数敏感修改器,如 LIMIT,包装在子查询中。
在 0.6 版本中,该逻辑更加敏感,并且可以检测到所有急切加载是否表示一对多关系,在这种情况下,急切连接不会影响行数:
SELECT * FROM addresses LEFT OUTER JOIN users AS users_1 ON users_1.id = addresses.user_id LIMIT 10
SqlAlchemy 2.0 中文文档(八十)(4)https://developer.aliyun.com/article/1559882