SqlAlchemy 2.0 中文文档(八十)(1)https://developer.aliyun.com/article/1559878
ORM 变更
从 0.5 升级到 0.6 的 ORM 应用应该几乎不需要更改,因为 ORM 的行为基本保持不变。有一些默认参数和名称更改,以及一些加载行为已经改进。
新工作单元
工作单元的内部,主要是 topological.py
和 unitofwork.py
,已经完全重写并且大大简化。这对使用没有影响,因为所有现有的行为在 flush 过程中都被完全保持了下来(或者至少在我们的测试套件和少数重度测试的生产环境中被保持了下来)。flush() 的性能现在减少了 20-30% 的方法调用,并且应该使用更少的内存。现在,源代码的意图和流程应该相当容易理解,而且 flush 的架构在这一点上相当开放,为潜在的新领域创造了空间。flush 过程不再依赖递归,因此可以刷新任意大小和复杂度的 flush 计划。此外,mapper 的 “save” 过程,发出 INSERT 和 UPDATE 语句,现在缓存了两个语句的 “compiled” 形式,因此在非常大的 flush 过程中进一步大幅减少了调用次数。
与早期版本 0.6 或 0.5 相比,在 flush 与 flush 之间观察到的任何行为变化都应该尽快向我们报告 - 我们将确保不会丢失任何功能。
query.update()
和 query.delete()
的变更
- query.update() 上的 ‘expire’ 选项已更名为 ‘fetch’,因此与 query.delete() 的匹配项相匹配
query.update()
和query.delete()
的 synchronize 策略都默认为 ‘evaluate’。- ‘synchronize’ 策略对 update() 和 delete() 抛出错误时会触发错误。在失败时没有隐式回退到“fetch”。评估的失败基于条件的结构,因此成功/失败是基于代码结构确定性的。
relation()
现在正式命名为 relationship()
这是为了解决长期存在的问题,“relation”在关系代数术语中意味着“表或派生表”。relation()
名称,少打字,将在可预见的未来继续存在,因此这个改变应该完全没有痛苦。
子查询的急切加载
添加了一种新的急切加载方式,称为“subquery”加载。这是一种在第一个 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
参数仍然被接受,行为与以前完全相同。
在关系、joinedload 上设置 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)
,无需重新定义基表的 primaryjoin 条件。[ticket:1186] - 使用声明性列指定外键,即
ForeignKey(MyRelatedClass.id)
不会导致“use_get”条件发生变化 [ticket:1492] - relationship(),joinedload()和 joinedload_all()现在具有一个名为“innerjoin”的选项。指定
True
或False
来控制是否构建内连接或外连接的预加载连接。默认始终为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
具有联接表继承的可变主键
在子表具有外键指向父表主键的联接表继承配置现在可以在像 PostgreSQL 这样支持级联的数据库上更新。mapper()
现在有一个选项passive_updates=True
,表示此外键将自动更新。如果在不支持级联的数据库上,如 SQLite 或 MySQL/MyISAM 上,将此标志设置为False
。未来的功能增强将尝试根据使用的方言/表格样式自动配置此标志。
Beaker 缓存
Beaker 集成的一个有前途的新示例在examples/beaker_caching
中。这是一个简单的示例,它在Query
的结果生成引擎中应用了一个 Beaker 缓存。缓存参数通过query.options()
提供,并允许完全控制缓存内容。SQLAlchemy 0.6 对Session.merge()
方法进行了改进,以支持这种和类似的示例,并在大多数情况下提供了显著改进的性能。
其他更改
- 当选择多个列/实体时,
Query
返回的“行元组”对象现在也是可序列化的,并且性能更高。 query.join()
已经重新设计,以提供更一致的行为和更灵活的功能(包括[ticket:1537])query.select_from()
接受多个子句,以在 FROM 子句中生成多个逗号分隔的条目。在从多个 join()子句中选择时很有用。Session.merge()
上的“dont_load=True”标志已被弃用,现在是“load=False”。- 添加了“make_transient()”辅助函数,将持久化/分离实例转换为瞬态实例(即删除实例键并从任何会话中移除)。[ticket:1052]
- mapper() 上的 allow_null_pks 标志已被废弃,并已重命名为 allow_partial_pks。默认情况下已打开此标志。这意味着对于任何主键列中有非空值的行将被视为标识。通常只有在映射到外连接时才需要此情景。当设置为 False 时,具有 NULL 值的 PK 不会被视为主键 - 特别是这意味着结果行将返回为 None(或不会填充到集合中),并且在 0.6 中还表示 session.merge() 不会为此类 PK 值发出数据库的往返。【票号: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=(“*”, )’ 实现此功能。
- 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
仍然被弃用。请参阅用法配方在 Recipes/SessionAwareMapper- 将
InstanceState
(内部 SQLAlchemy 状态对象)传递给attributes.init_collection()
或attributes.get_history()
已弃用。 这些函数是公共 API,并且通常希望是常规映射对象实例。 declarative_base()
的 “engine” 参数已移除。使用 “bind” 关键字参数。
扩展
SQLSoup
SQLSoup 已现代化并更新以反映常见的 0.5/0.6 功能,包括明确定义的会话集成。请阅读新文档[www.sqlalc
hemy.org/docs/06/reference/ext/sqlsoup.html]。
声明
DeclarativeMeta
(declarative_base
的默认元类)之前允许子类修改 dict_
来添加类属性(例如列)。 这种方式不再有效,DeclarativeMeta
构造函数现在忽略 dict_
。相反,类属性应直接赋值,例如 cls.id=Column(...)
,或者应该使用 MixIn 类 方法而不是元类方法。
平台支持
- cPython 版本从 2.4 开始,在 2.xx 系列中
- Jython 2.5.1 - 使用 Jython 自带的 zxJDBC DBAPI。
- cPython 3.x - 参见[源码:sqlalchemy/trunk/README.py3k] 了解如何构建 Python3 版本。
新方言系统
方言模块现在被分解为单个数据库后端范围内的不同子组件。 方言实现现在在 sqlalchemy.dialects
包中。 sqlalchemy.databases
包仍然存在,作为一个占位符,为简单导入提供一定程度的向后兼容性。
对于每个支持的数据库,在sqlalchemy.dialects
中都存在一个子包,其中包含几个文件。每个包都包含一个名为base.py
的模块,该模块定义了该数据库使用的特定 SQL 方言。它还包含一个或多个“driver”模块,每个模块对应于特定的 DBAPI - 这些文件的命名与 DBAPI 本身相对应,例如pysqlite
、cx_oracle
或pyodbc
。SQLAlchemy 方言使用的类首先在base.py
模块中声明,定义了数据库定义的所有行为特征。这些包括功能映射,例如“支持序列”,“支持返回”等,类型定义和 SQL 编译规则。每个“driver”模块依次提供所需的那些类的子类,这些子类覆盖默认行为以适应该 DBAPI 的附加功能、行为和怪癖。对于支持多个后端的 DBAPI(pyodbc、zxJDBC、mxODBC),方言模块将使用sqlalchemy.connectors
包中的 mixin,这些 mixin 提供了在所有后端上通用的功能,最常见的是处理连接参数。这意味着使用 pyodbc、zxJDBC 或 mxODBC(一旦实现)进行连接在支持的后端上是非常一致的。
create_engine()
使用的 URL 格式已经改进,以处理特定后端的任意数量的 DBAPI,使用了受 JDBC 启发的方案。以前的格式仍然有效,并且将选择一个“默认”的 DBAPI 实现,例如下面将使用 psycopg2 的 PostgreSQL URL:
create_engine("postgresql://scott:tiger@localhost/test")
但是,要指定特定的 DBAPI 后端,例如 pg8000,请在 URL 的“protocol”部分使用加号“+”:
create_engine("postgresql+pg8000://scott:tiger@localhost/test")
重要的方言链接:
- 连接参数的文档:
www.sqlalchemy.org/docs/06/dbengine.html#create
- engine-url-arguments。 - 各个方言的参考文档:
ww
w.sqlalchemy.org/docs/06/reference/dialects/index.html - DatabaseNotes 中的技巧和窍门。
关于方言的其他注意事项:
- SQLAlchemy 0.6 中类型系统发生了巨大变化。这对所有方言的命名约定、行为和实现都产生了影响。请参见下面关于“类型”的部分。
ResultProxy
对象现在在某些情况下提供了 2 倍的速度改进,这要归功于一些重构。RowProxy
,即单个结果行对象,现在可以直接进行 pickle。- 用于定位外部方言的 setuptools entrypoint 现在称为
sqlalchemy.dialects
。针对 0.4 或 0.5 编写的外部方言需要修改以适应 0.6,在任何情况下,因此这一变化并不会增加任何额外的困难。 - 方言现在在初始连接时会接收一个 initialize()事件,以确定连接属性。
- 编译器生成的函数和操作符现在使用(几乎)常规的分发函数形式“visit_”和“visit__fn”来提供定制处理。这取代了在编译器子类中复制“functions”和“operators”字典的需要,改为使用直接的访问者方法,并且还允许编译器子类完全控制渲染,因为完整的 _Function 或 _BinaryExpression 对象被传递进来。
方���导入
方言的导入结构已经改变。每个方言现在通过 sqlalchemy.dialects.
导出其基本的“dialect”类以及该方言支持的完整一组 SQL 类型。例如,要导入一组 PG 类型:
from sqlalchemy.dialects.postgresql import ( INTEGER, BIGINT, SMALLINT, VARCHAR, MACADDR, DATE, BYTEA, )
上面,INTEGER
实际上是 sqlalchemy.types
中的普通 INTEGER
类型,但 PG 方言使其以与那些特定于 PG 的类型相同的方式可用,比如 BYTEA
和 MACADDR
。
方言导入
方言的导入结构已经改变。每个方言现在通过 sqlalchemy.dialects.
导出其基本的“dialect”类以及该方言支持的完整一组 SQL 类型。例如,要导入一组 PG 类型:
from sqlalchemy.dialects.postgresql import ( INTEGER, BIGINT, SMALLINT, VARCHAR, MACADDR, DATE, BYTEA, )
上面,INTEGER
实际上是 sqlalchemy.types
中的普通 INTEGER
类型,但 PG 方言使其以与那些特定于 PG 的类型相同的方式可用,比如 BYTEA
和 MACADDR
。
表达式语言变化
一个重要的表达式语言陷阱
表达式语言有一个相当重要的行为变化,可能会影响一些应用程序。Python 布尔表达式的布尔值,即 ==
、!=
等,现在在与被比较的两个子句对象相关时会准确求值。
正如我们所知,将 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:
的比较现在可以这样写 - 现在正确哈希
ClauseElement
对象的支持也适用于其他平台,比如 Jython。直到这一点,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/服务器端默认值的行为不一致将是不一致的。 (从 0.5 系列开始,基于 SQL 表达式的默认值被嵌入到行内,以最小化大量参数集的影响)。
因此,SQLAlchemy 0.6 通过禁止任何后续参数集留下任何字段空白来建立可预测的一致性。这样,Python 端默认值和函数不再默默失败,此外,它们允许保持与 SQL 和服务器端默认值一致的行为。
UNION 和其他“复合”结构一致地加括号。
为了帮助 SQLite 而设计的规则已被移除,即在另一个复合元素内的第一个复合元素(例如,在 except_()
中的 union()
)不会被括号括起来。这是不一致的,并且在 PostgreSQL 上产生错误的结果,因为它有关于 INTERSECTION 的优先规则,通常会让人感到惊讶。在使用 SQLite 的复杂组合时,现在需要将第一个元素转换为子查询(这也与 PG 兼容)。在[www.sqlalchemy.org/docs/06/sqlexpression.html
#unions-and-other-set-operations]的 SQL 表达式教程的末尾有一个新的示例。查看#1665和 r6690 以获取更多背景信息。
一个重要的表达语言陷阱
表达语言中有一个相当重要的行为变化,可能会影响一些应用程序。Python 布尔表达式的布尔值,即==
,!=
等,现在在比较两个子句对象时会准确评估。
我们知道,将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:
的比较。 - 对
ClauseElement
对象进行正确哈希的支持现在在其他平台上也能正常工作,即 Jython。直到这一点,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/服务器端默认值的行为不一致是不合理的(从 0.5 系列开始,基于 SQL 表达式的默认值被嵌入到内联,以最小化大量参数集的影响)。
SQLAlchemy 0.6 因此通过禁止任何后续参数集留空字段来确立可预测的一致性。这样,Python 端默认值和函数不再默默失败,而且它们的行为与 SQL 和服务器端默认值保持一致。
UNION 和其他“复合”结构一致地加括号
为了帮助 SQLite 而设计的规则已被移除,即另一个复合元素内的第一个复合元素(例如,在except_()
内部的union()
)不会被括号括起来。这是不一致的,并且在 PostgreSQL 上产生错误的结果,因为它有关于 INTERSECTION 的优先规则,通常会让人感到惊讶。在与 SQLite 一起使用复杂的复合时,现在需要将第一个元素转换为子查询(这也在 PG 上兼容)。在[www.sqlalchemy.org/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.6 版本的性能比 0.5 版本有所提高。使用 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
在上述例子中,ORM 比 0.5 版本快 33%获取行,这归功于 Python 内部性能的提升。使用 C 扩展我们可以再获得 20%的提升。然而,ResultProxy
使用 C 扩展比不使用提升了 67%。其他测试报告显示在某些情况下,例如发生大量字符串转换的情况下,速度提高了高达 200%。
SqlAlchemy 2.0 中文文档(八十)(3)https://developer.aliyun.com/article/1559881