SQLAlchemy 2.0 文档
变更和迁移
- SQLAlchemy 2.0 - 主要迁移指南
- SQLAlchemy 2.0 的新功能?
- 2.0 更新日志
- 1.4 更新日志
- 1.3 更新日志
- 1.2 更新日志
- 1.1 更新日志
- 1.0 更新日志
- 0.9 更新日志
- 0.8 更新日志
- 0.7 更新日志
- 0.6 更新日志
- 0.5 更新日志
- 0.4 更新日志
- 0.3 更新日志
- 0.2 更新日志
- 0.1 更新日志
- SQLAlchemy 1.4 的新功能?
- SQLAlchemy 1.3 的新功能?
- SQLAlchemy 1.2 的新功能?
- SQLAlchemy 1.1 的新功能?
- SQLAlchemy 1.0 的新功能?
- SQLAlchemy 0.9 的新功能?
- SQLAlchemy 0.8 的新功能?
- SQLAlchemy 0.7 的新功能?
- SQLAlchemy 0.6 的新功能?¶
- 平台支持
- 新的方言系统
- 表达式语言变更
- 用于结果获取的 C 扩展
- 新的 Schema 功能
- 日志记录开放
- 反射/Inspector API
- RETURNING 支持
- 类型系统变更
- ORM 变更
- 新的 Unit of Work
query.update()
和query.delete()
的变更relation()
正式命名为relationship()
- 子查询急切加载
`eagerload()``
,``eagerload_all()``
现在是``joinedload()``
,``joinedload_all()`
`lazy=False|None|True|'dynamic'``
现在接受``lazy='noload'|'joined'|'subquery'|'select'|'dynamic'`
- relation, joinedload 上的 innerjoin=True
- 多对一增强
- 具有连接表继承的可变主键
- Beaker 缓存
- 其他变更
- 已弃用/移除的 ORM 元素
- 扩展
- SQLAlchemy 0.5 的新功能?
- SQLAlchemy 0.4 的新功能?
项目版本
- 上一篇: SQLAlchemy 0.7 的新功能?
- 下一篇: SQLAlchemy 0.5 的新功能?
- 上一级: 首页
- 在此页
- SQLAlchemy 0.6 的新功能?
- 平台支持
- 新的方言系统
- 表达式语言变更
- 用于结果获取的 C 扩展
- 新的 Schema 功能
- 日志记录开放
- 反射/Inspector API
- RETURNING 支持
- 类型系统变更
- ORM 变更
- 新的 Unit of Work
query.update()
和query.delete()
的变更relation()
正式命名为relationship()
- 子查询急切加载
`eagerload()``
,``eagerload_all()``
现在是``joinedload()``
,``joinedload_all()`
`lazy=False|None|True|'dynamic'``
现在接受``lazy='noload'|'joined'|'subquery'|'select'|'dynamic'`
- relation, joinedload 上的 innerjoin=True
- 多对一增强
- 具有连接表继承的可变主键
- Beaker 缓存
- 其他变更
- 已弃用/移除的 ORM 元素
- 扩展
SQLAlchemy 0.6 的新功能?¶
关于本文档
本文档描述了 SQLAlchemy 版本 0.5(上次发布于 2010 年 1 月 16 日)和 SQLAlchemy 版本 0.6(上次发布于 2012 年 5 月 5 日)之间的变更。
文档日期:2010 年 6 月 6 日
本指南记录了影响用户将其应用程序从 SQLAlchemy 0.5 系列迁移到 0.6 的 API 变更。请注意,SQLAlchemy 0.6 移除了 0.5 系列整个期间已弃用的一些行为,并且还弃用了更多特定于 0.5 的行为。
平台支持¶
cPython 版本 2.4 及以上整个 2.xx 系列
Jython 2.5.1 - 使用 Jython 自带的 zxJDBC DBAPI。
cPython 3.x - 有关如何为 python3 构建,请参阅 [source:sqlalchemy/trunk/README.py3k]。
新的方言系统¶
方言模块现在被分解为不同的子组件,在单个数据库后端的作用域内。方言实现现在位于 sqlalchemy.dialects
包中。sqlalchemy.databases
包仍然作为占位符存在,以提供一定程度的向后兼容性以进行简单导入。
对于每个受支持的数据库,在 sqlalchemy.dialects
中都存在一个子包,其中包含多个文件。每个包都包含一个名为 base.py
的模块,该模块定义了该数据库使用的特定 SQL 方言。它还包含一个或多个 “驱动程序” 模块,每个模块对应于一个特定的 DBAPI - 这些文件根据 DBAPI 本身命名,例如 pysqlite
, cx_oracle
, 或 pyodbc
。SQLAlchemy 方言使用的类首先在 base.py
模块中声明,定义数据库定义的所有行为特征。这些包括功能映射,例如 “支持序列”、“支持返回” 等、类型定义和 SQL 编译规则。每个 “驱动程序” 模块又根据需要提供这些类的子类,这些子类覆盖默认行为,以适应该 DBAPI 的附加功能、行为和怪癖。对于支持多个后端的 DBAPI (pyodbc, zxJDBC, mxODBC),方言模块将使用来自 sqlalchemy.connectors
包的 mixin,这些 mixin 提供了该 DBAPI 在所有后端通用的功能,最常见的是处理连接参数。这意味着使用 pyodbc、zxJDBC 或 mxODBC(如果已实现)进行连接在受支持的后端之间非常一致。
create_engine()
使用的 URL 格式已得到增强,可以使用受 JDBC 启发的方案来处理特定后端的任意数量的 DBAPI。之前的格式仍然有效,并且将选择 “默认” DBAPI 实现,例如下面的 PostgreSQL URL 将使用 psycopg2
create_engine("postgresql://scott:tiger@localhost/test")
但是,要指定特定的 DBAPI 后端(如 pg8000),请使用加号 “+” 将其添加到 URL 的 “协议” 部分
create_engine("postgresql+pg8000://scott:tiger@localhost/test")
重要的方言链接
有关连接参数的文档:https://sqlalchemy.org.cn/docs/06/dbengine.html#create-engine-url-arguments。
各个方言的参考文档:https://sqlalchemy.org.cn/docs/06/reference/dialects/index.html。
DatabaseNotes 中的提示和技巧。
关于方言的其他说明
SQLAlchemy 0.6 中的类型系统已发生巨大变化。这对方言在命名约定、行为和实现方面都有影响。请参阅下面关于 “类型” 的部分。
由于一些重构,
ResultProxy
对象现在在某些情况下提供了 2 倍的速度提升。RowProxy
,即单个结果行对象,现在可以直接 pickle 化。用于定位外部方言的 setuptools 入口点现在称为
sqlalchemy.dialects
。针对 0.4 或 0.5 编写的外部方言无论如何都需要修改才能与 0.6 一起使用,因此此更改不会增加任何额外的困难。方言现在在初始连接时接收 initialize() 事件,以确定连接属性。
编译器生成的功能和运算符现在使用(几乎)常规的调度函数,形式为 “visit_<opname>” 和 “visit_<funcname>_fn” 以提供自定义处理。这取代了在编译器子类中复制 “functions” 和 “operators” 字典的需要,而是使用直接的访问者方法,并且还允许编译器子类完全控制渲染,因为传入了完整的 _Function 或 _BinaryExpression 对象。
方言导入¶
方言的导入结构已更改。每个方言现在都导出其基本 “方言” 类以及通过 sqlalchemy.dialects.<name>
在该方言上支持的完整 SQL 类型集。例如,要导入一组 PG 类型
from sqlalchemy.dialects.postgresql import (
INTEGER,
BIGINT,
SMALLINT,
VARCHAR,
MACADDR,
DATE,
BYTEA,
)
在上面,INTEGER
实际上是来自 sqlalchemy.types
的普通 INTEGER
类型,但 PG 方言使其以与那些特定于 PG 的类型(如 BYTEA
和 MACADDR
)相同的方式可用。
表达式语言变更¶
一个重要的表达式语言陷阱¶
表达式语言有一个非常重要的行为变更,可能会影响某些应用程序。Python 布尔表达式(即 ==
, !=
, 和类似的)的布尔值现在可以准确地评估被比较的两个 clause 对象。
我们知道,将 ClauseElement
与任何其他对象进行比较都会返回另一个 ClauseElement
>>> from sqlalchemy.sql import column
>>> column("foo") == 5
<sqlalchemy.sql.expression._BinaryExpression object at 0x1252490>
这样,Python 表达式在转换为字符串时会生成 SQL 表达式
>>> str(column("foo") == 5)
'foo = :foo_1'
但是,如果我们这样说会发生什么?
>>> if column("foo") == 5:
... print("yes")
在以前版本的 SQLAlchemy 中,返回的 _BinaryExpression
是一个普通的 Python 对象,其评估结果为 True
。现在,它评估实际的 ClauseElement
是否应具有与被比较对象相同的哈希值。意思是
>>> bool(column("foo") == 5)
False
>>> bool(column("foo") == column("foo"))
False
>>> c = column("foo")
>>> bool(c == c)
True
>>>
这意味着像下面这样的代码
if expression:
print("the expression is:", expression)
如果 expression
是二进制子句,则不会评估。由于永远不应使用上述模式,因此基本 ClauseElement
现在在布尔上下文中调用时会引发异常
>>> bool(c)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
...
raise TypeError("Boolean value of this clause is not defined")
TypeError: Boolean value of this clause is not defined
想要检查 ClauseElement
表达式是否存在的代码应改为说
if expression is not None:
print("the expression is:", expression)
请记住,这也适用于 Table 和 Column 对象。
更改的基本原理是双重的
现在可以实际编写
if c1 == c2: <do something>
形式的比较现在可以在备用平台(即 Jython)上正确哈希
ClauseElement
对象。到目前为止,SQLAlchemy 在这方面严重依赖 cPython 的特定行为(并且仍然偶尔遇到问题)。
更严格的 “executemany” 行为¶
SQLAlchemy 中的 “executemany” 对应于对 execute()
的调用,并传递绑定参数集的集合
connection.execute(table.insert(), {"data": "row1"}, {"data": "row2"}, {"data": "row3"})
当 Connection
对象发送给定的 insert()
构造进行编译时,它会将传递给编译器的第一个绑定集中存在的键名,以确定语句的 VALUES 子句的构造。熟悉此构造的用户将知道,其余字典中存在的其他键没有任何影响。现在的不同之处在于,所有后续字典都需要至少包含每个在第一个字典中存在的键。这意味着像这样的调用不再起作用
connection.execute(
table.insert(),
{"timestamp": today, "data": "row1"},
{"timestamp": today, "data": "row2"},
{"data": "row3"},
)
因为第三行未指定 ‘timestamp’ 列。以前版本的 SQLAlchemy 只会为这些缺失的列插入 NULL。但是,如果上面示例中的 timestamp
列包含 Python 端默认值或函数,则不会使用它。这是因为 “executemany” 操作针对大量参数集的最大性能进行了优化,并且不尝试为那些缺失的键评估 Python 端默认值。由于默认值通常实现为嵌入在 INSERT 语句中的 SQL 表达式,或者是服务器端表达式,它们同样根据 INSERT 字符串的结构触发,根据定义,它们不能根据每个参数集有条件地触发,因此 Python 端默认值与 SQL/服务器端默认值的行为不一致。(SQL 表达式基于默认值从 0.5 系列开始内联嵌入,同样是为了最大限度地减少大量参数集的影响)。
因此,SQLAlchemy 0.6 通过禁止任何后续参数集留空任何字段来建立可预测的一致性。这样,Python 端默认值和函数不再有静默失败,此外,它们在行为上与 SQL 和服务器端默认值保持一致。
UNION 和其他 “复合” 构造一致地使用括号¶
删除了一项旨在帮助 SQLite 的规则,即另一个复合元素(例如,except_()
内的 union()
)中的第一个复合元素不会使用括号。这是不一致的,并且在 PostgreSQL 上产生错误的结果,PostgreSQL 具有关于 INTERSECTION 的优先级规则,并且通常令人惊讶。当将复杂复合与 SQLite 一起使用时,现在需要将第一个元素转换为子查询(这也与 PG 兼容)。在 [https://sqlalchemy.org.cn/docs/06/sqlexpression.html #unions-and-other-set-operations] 末尾的 SQL 表达式教程中有一个新示例。有关更多背景信息,请参阅 #1665 和 r6690。
用于结果获取的 C 扩展¶
ResultProxy
和相关元素,包括最常见的 “行处理” 函数(如 Unicode 转换、数值/布尔转换和日期解析)已重新实现为可选的 C 扩展,以提高性能。这代表了 SQLAlchemy 走向 “黑暗面” 的开始,我们希望通过在 C 中重新实现关键部分来继续提高性能。可以通过指定 --with-cextensions
来构建扩展,即 python setup.py --with- cextensions install
。
扩展对使用直接 ResultProxy
访问的结果获取具有最显着的影响,即由 engine.execute()
, connection.execute()
, 或 session.execute()
返回的访问。在 ORM Query
对象返回的结果中,结果获取不是开销的很大一部分,因此 ORM 性能的提升更适度,并且主要是在获取大型结果集方面。性能改进在很大程度上取决于正在使用的 dbapi 以及用于访问每行列的语法(例如 row['name']
比 row.name
快得多)。当前的扩展对插入/更新/删除的速度没有影响,也不会提高 SQL 执行的延迟,也就是说,花费大部分时间执行许多结果集非常小的语句的应用程序不会看到太多改进。
与 0.5 相比,0.6 中的性能得到了提高,而与扩展无关。快速概述一下使用 SQLite 连接和获取 50,000 行的情况,主要使用直接 SQLite 访问、ResultProxy
和简单的映射 ORM 对象
sqlite select/native: 0.260s
0.6 / C extension
sqlalchemy.sql select: 0.360s
sqlalchemy.orm fetch: 2.500s
0.6 / Pure Python
sqlalchemy.sql select: 0.600s
sqlalchemy.orm fetch: 3.000s
0.5 / Pure Python
sqlalchemy.sql select: 0.790s
sqlalchemy.orm fetch: 4.030s
在上面,由于 Python 内部性能增强,ORM 获取行的速度比 0.5 快 33%。使用 C 扩展,我们又获得了 20%。但是,使用 C 扩展而不是不使用 C 扩展时,ResultProxy
获取速度提高了 67%。其他测试报告说,在某些情况下,速度提高了 200%,例如发生大量字符串转换的情况。
新的 Schema 功能¶
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 对象现在都像任何其他 SQLAlchemy 表达式对象一样是常规的 ClauseElement
对象
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 元素¶
schema 包也已大大简化。许多在 0.5 整个期间已弃用的选项和方法已被删除。其他鲜为人知的访问器和方法也已被删除。
“owner” 关键字参数已从
Table
中删除。使用 “schema” 来表示要添加到表名称前面的任何命名空间。已弃用的
MetaData.connect()
和ThreadLocalMetaData.connect()
已被删除 - 发送 “bind” 属性以绑定元数据。已弃用的 metadata.table_iterator() 方法已删除(使用 sorted_tables)
“metadata” 参数已从
DefaultGenerator
及其子类中删除,但仍本地存在于Sequence
上,后者是 DDL 中的独立构造。已弃用的
PassiveDefault
- 使用DefaultClause
。从
Index
和Constraint
对象中删除了公共可变性ForeignKeyConstraint.append_element()
Index.append_column()
UniqueConstraint.append_column()
PrimaryKeyConstraint.add()
PrimaryKeyConstraint.remove()
这些应该以声明方式构造(即在一个构造中)。
其他删除的内容
Table.key
(不知道这是做什么用的)Column.bind
(通过 column.table.bind 获取)Column.metadata
(通过 column.table.metadata 获取)Column.sequence
(使用 column.default)
其他行为变更¶
UniqueConstraint
,Index
,PrimaryKeyConstraint
都接受列名或列对象列表作为参数。ForeignKey
上的use_alter
标志现在是可以使用DDL()
事件系统手动构造的操作的快捷方式选项。此重构的副作用是,在 SQLite 上将不会发出具有use_alter=True
的ForeignKeyConstraint
对象,SQLite 不支持用于外键的 ALTER。这对 SQLite 的行为没有影响,因为 SQLite 实际上并不遵守 FOREIGN KEY 约束。Table.primary_key
不可赋值 - 使用table.append_constraint(PrimaryKeyConstraint(...))
具有
ForeignKey
且没有类型的Column
定义,例如Column(name, ForeignKey(sometable.c.somecol))
曾经获取引用列的类型。现在,对该自动类型推断的支持是部分的,并且可能并非在所有情况下都有效。
日志记录开放¶
以这里和那里的一些额外方法调用为代价,您可以在创建引擎、连接池或映射器之后设置 INFO 和 DEBUG 的日志级别,并且日志记录将开始。isEnabledFor(INFO)
方法现在对每个 Connection
调用,如果已在父连接上启用,则对每个 ResultProxy
调用 isEnabledFor(DEBUG)
。连接池日志发送到 log.info()
和 log.debug()
而不进行检查 - 请注意,连接池检入/检出通常每个事务发生一次。
反射/Inspector API¶
反射系统允许通过 Table('sometable', metadata, autoload=True)
反射表列,该系统已开放到其自己的细粒度 API 中,该 API 允许直接检查数据库元素,例如表、列、约束、索引等。此 API 将返回值表示为简单的字符串列表、字典和 TypeEngine
对象。autoload=True
的内部结构现在构建在此系统之上,这样将原始数据库信息转换为 sqlalchemy.schema
构造的过程集中化,并且大大简化了各个方言的约定,从而大大减少了不同后端之间的错误和不一致性。
要使用 inspector
from sqlalchemy.engine.reflection import Inspector
insp = Inspector.from_engine(my_engine)
print(insp.get_schema_names())
from_engine()
方法在某些情况下将提供具有附加功能的后端特定 inspector,例如 PostgreSQL 的 inspector 提供 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 引发)。
当可用且未被显式 returning()
调用另行指定时,SQLAlchemy 也会自动使用 RETURNING 来获取单行 INSERT 语句新生成的主键值的值。这意味着不再需要为需要主键值的 insert 语句进行 “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/sqlalc hemy/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 警告过滤器来抑制此警告或将其转换为异常,文档位于:https://docs.pythonlang.cn/library/warnings.html
通用枚举类型¶
我们现在在 types
模块中有一个 Enum
。这是一种字符串类型,它被赋予一组“标签”,这些标签约束了给定给这些标签的可能值。默认情况下,此类型使用最大标签的大小生成一个 VARCHAR
,并在 CREATE TABLE 语句中将 CHECK 约束应用于表。使用 MySQL 时,该类型默认使用 MySQL 的 ENUM 类型,而使用 PostgreSQL 时,该类型将使用 CREATE TYPE <mytype> AS ENUM
生成用户定义的类型。为了使用 PostgreSQL 创建类型,必须为构造函数指定 name
参数。该类型还接受 native_enum=False
选项,该选项将为所有数据库发出 VARCHAR/CHECK 策略。请注意,PostgreSQL ENUM 类型目前不适用于 pg8000 或 zxjdbc。
反射返回方言特定类型¶
反射现在从数据库返回最具体的类型。也就是说,如果您使用 String
创建表,然后将其反射回来,则反射的列很可能是 VARCHAR
。对于支持更具体类型形式的方言,您将获得该类型。因此,Text
类型在 Oracle 上将返回为 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
类型(尽管当要求渲染没有长度的 VARCHAR 时,MySQL 方言会抢先引发错误)。不假设任何默认值,如果在 CREATE TABLE 语句中使用它们,如果底层数据库不允许这些类型的非长度版本,则会引发错误。Binary
类型已重命名为LargeBinary
,用于 BLOB/BYTEA/类似类型。对于BINARY
和VARBINARY
,它们直接作为types.BINARY
、types.VARBINARY
存在,以及在 MySQL 和 MS-SQL 方言中。现在,当 mutable=True 时,
PickleType
使用 == 进行值比较,除非为类型指定了带有比较函数的“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
,已完全重写,并且大大简化。这应该对使用没有影响,因为在 flush 期间的所有现有行为都已完全保持(或者至少,就我们的测试套件和少量已对其进行大量测试的生产环境而言)。flush() 的性能现在减少了 20-30% 的方法调用,并且还应使用更少的内存。现在应该可以很容易地理解源代码的意图和流程,并且 flush 的架构在这一点上相当开放,为潜在的新复杂领域创造了空间。flush 过程不再依赖于递归,因此可以 flush 任意大小和复杂性的 flush 计划。此外,mapper 的“save”过程(发出 INSERT 和 UPDATE 语句)现在缓存了这两个语句的“编译”形式,因此在非常大的 flush 中,调用计数进一步显着减少。
如果观察到 flush 与早期版本的 0.6 或 0.5 相比有任何行为变化,应尽快报告给我们 - 我们将确保不会丢失任何功能。
query.update()
和 query.delete()
的更改¶
query.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()``
选项以及 ``lazy='subquery'``
设置在 ``relationship()`
上。子查询加载通常对于加载许多更大的集合更有效,因为它无条件地使用 INNER JOIN,并且也不重新加载父行。
`eagerload()``
, ``eagerload_all()``
现在是 ``joinedload()``
, ``joinedload_all()`
¶
为了给新的子查询加载功能腾出空间,现有的 `eagerload()``
/``eagerload_all()``
选项现在被 ``joinedload()``
和 ``joinedload_all()``
取代。旧名称将在可预见的未来继续存在,就像 ``relation()`
一样。
`lazy=False|None|True|'dynamic'``
现在接受 ``lazy='noload'|'joined'|'subquery'|'select'|'dynamic'`
¶
继续关于打开的加载器策略的主题,``relationship()``
上 `lazy``
选项的标准关键字现在是 ``select``
(用于延迟加载,通过在属性访问时发出的 SELECT)、``joined``
(用于连接预先加载)、``subquery``
(用于子查询预先加载)、``noload``
(用于不应发生加载),以及 ``dynamic``
(用于“动态”关系)。旧的 ``True``
、``False``
、``None`
参数仍然被接受,其行为与以前相同。
relation, joinedload 上的 innerjoin=True¶
现在可以指示连接预先加载的标量和集合使用 INNER JOIN 而不是 OUTER JOIN。在 PostgreSQL 上,观察到这在某些查询上提供了 300-600% 的加速。为任何 NOT NULLable 外键上的多对一设置此标志,对于任何保证存在相关项的集合也是如此。
在 mapper 级别
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)``,而无需根据基表重新定义 primaryjoin 条件。[ticket:1186]使用声明性列指定外键,即
ForeignKey(MyRelatedClass.id)
不会破坏“use_get”条件的发生 [ticket:1492]relationship()、joinedload() 和 joinedload_all() 现在具有一个名为“innerjoin”的选项。指定
True
或False
以控制 eager join 是构造为 INNER join 还是 OUTER join。默认值始终为False
。通常应为多对一、不可为空的外键关系设置,以提高连接性能。[ticket:1544]当存在 LIMIT/OFFSET 时,连接预先加载的行为(主查询包装在子查询中)现在对所有 eager load 都是多对一连接的情况例外。在这些情况下,eager join 直接针对父表以及 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
这是因为任何 eager loader 的存在都表明它们中的一些或全部可能与多行集合相关,这将需要将任何类型的行计数敏感修饰符(如 LIMIT)包装在子查询内部。
在 0.6 中,该逻辑更敏感,可以检测到是否所有 eager loader 都代表多对一,在这种情况下,eager join 不会影响行计数
SELECT * FROM addresses LEFT OUTER JOIN users AS users_1 ON users_1.id = addresses.user_id LIMIT 10
具有连接表继承的可变主键¶
子表具有外键到父 PK 的 PK 的连接表继承配置现在可以在像 PostgreSQL 这样的支持 CASCADE 的数据库上更新。mapper()
现在有一个选项 passive_updates=True
,指示此外键是自动更新的。如果在像 SQLite 或 MySQL/MyISAM 这样的非级联数据库上,请将此标志设置为 False
。未来的功能增强将尝试使此标志根据正在使用的方言/表样式自动配置。
Beaker 缓存¶
在 examples/beaker_caching
中有一个有希望的 Beaker 集成新示例。这是一个简单的配方,它在 Query
的结果生成引擎中应用 Beaker 缓存。缓存参数通过 query.options()
提供,并允许完全控制缓存的内容。SQLAlchemy 0.6 包括对 Session.merge()
方法的改进,以支持此配方和类似配方,并在大多数情况下提供显着提高的性能。
其他更改¶
当选择多个列/实体时,
Query
返回的“行元组”对象现在是可 pickle 的,并且性能更高。query.join()
已被重写以提供更一致的行为和更大的灵活性(包括 [ticket:1537])query.select_from()
接受多个子句,以在 FROM 子句中生成多个逗号分隔的条目。在从多宿主 join() 子句中选择时很有用。Session.merge()
上的“dont_load=True”标志已弃用,现在为“load=False”。添加了“make_transient()”帮助函数,该函数将持久/分离的实例转换为瞬态实例(即删除 instance_key 并从任何会话中删除。)[ticket:1052]
mapper() 上的 allow_null_pks 标志已弃用,并已重命名为 allow_partial_pks。默认情况下它处于“开启”状态。这意味着对于任何主键列具有非空值的行,都将被视为标识。这种情况的需求通常仅在映射到外连接时才会发生。当设置为 False 时,PK 中包含 NULL 将不被视为主键 - 特别是这意味着结果行将返回为 None(或未填充到集合中),并且 0.6 中的新功能还表明 session.merge() 不会为此类 PK 值发出往返数据库的请求。[ticket:1680]
“backref”的机制已完全合并到更精细的“back_populates”系统中,并且完全发生在
RelationProperty
的_generate_backref()
方法中。这简化了RelationProperty
的初始化过程,并允许更轻松地将设置(例如从RelationProperty
的子类)传播到反向引用。内部BackRef()
已消失,backref()
返回一个纯元组,该元组可以被RelationProperty
理解。ResultProxy
的 keys 属性现在是一个方法,因此对其的引用 (result.keys
) 必须更改为方法调用 (result.keys()
)ResultProxy.last_inserted_ids
现已弃用,请改用ResultProxy.inserted_primary_key
。
已弃用/删除的 ORM 元素¶
在 0.5 中弃用并引发弃用警告的大多数元素已被删除(少数例外)。所有标记为“待弃用”的元素现在都已弃用,并在使用时引发警告。
sessionmaker() 和其他项上的 'transactional' 标志已删除。使用 'autocommit=True' 表示 'transactional=False'。
mapper() 上的 'polymorphic_fetch' 参数已删除。可以使用 'with_polymorphic' 选项控制加载。
mapper() 上的 'select_table' 参数已删除。为此功能使用 'with_polymorphic=("*", <some selectable>)'。
synonym() 上的 'proxy' 参数已删除。此标志在 0.5 中没有任何作用,因为“代理生成”行为现在是自动的。
将元素列表传递给 joinedload()、joinedload_all()、contains_eager()、lazyload()、defer() 和 undefer() 而不是多个位置 *args 已弃用。
将元素列表传递给 query.order_by()、query.group_by()、query.join() 或 query.outerjoin() 而不是多个位置 *args 已弃用。
query.iterate_instances()
已删除。使用query.instances()
。Query.query_from_parent()
已删除。使用 sqlalchemy.orm.with_parent() 函数生成“parent”子句,或者使用query.with_parent()
。query._from_self()
已删除,请改用query.from_self()
。composite() 的 “comparator” 参数已删除。使用 “comparator_factory”。
RelationProperty._get_join()
已删除。Session 上的 'echo_uow' 标志已删除。在 “sqlalchemy.orm.unitofwork” 名称上使用日志记录。
session.clear()
已删除。使用session.expunge_all()
。session.save()
、session.update()
、session.save_or_update()
已删除。使用session.add()
和session.add_all()
。session.flush() 上的 “objects” 标志仍然已弃用。
session.merge() 上的 “dont_load=True” 标志已弃用,建议使用 “load=False”。
ScopedSession.mapper
仍然已弃用。请参阅 https://sqlalchemy.org.cn/trac/wiki/Usag eRecipes/SessionAwareMapper 上的用法示例将
InstanceState
(内部 SQLAlchemy 状态对象)传递给attributes.init_collection()
或attributes.get_history()
已弃用。这些函数是公共 API,通常期望一个常规的映射对象实例。declarative_base()
的 ‘engine’ 参数已删除。使用 ‘bind’ 关键字参数。
扩展¶
SQLSoup¶
SQLSoup 已经现代化并更新以反映常见的 0.5/0.6 功能,包括定义良好的会话集成。请阅读 [https://sqlalchemy.org.cn/docs/06/reference/ext/sqlsoup.html] 中的新文档。
声明式¶
以前,DeclarativeMeta
(declarative_base
的默认元类)允许子类修改 dict_
以添加类属性(例如列)。这不再起作用,DeclarativeMeta
构造函数现在忽略 dict_
。相反,类属性应直接分配,例如 cls.id=Column(...)
,或者应使用 MixIn 类 方法而不是元类方法。
flambé! 龙和 The Alchemist 图像设计由 Rotem Yaari 创建并慷慨捐赠。
使用 Sphinx 7.2.6 创建。文档上次生成时间:美国东部夏令时间 2025 年 3 月 11 日星期二下午 02:40:17