SqlAlchemy 2.0 中文文档(七十九)(2)https://developer.aliyun.com/article/1561103
速度增强
与所有主要 SQLA 版本一样,通过内部进行广泛的遍历以减少开销和调用次数,进一步减少了常见情况下所需的工作量。此版本的亮点包括:
- 刷新过程现在将 INSERT 语句捆绑成批次提供给
cursor.executemany()
,对于主键已经存在的行。特别是这通常适用于连接表继承配置中的“子”表,这意味着对于大量连接表对象的批量插入,可以将cursor.execute
的调用次数减少一半,从而允许针对那些传递给cursor.executemany()
的语句进行本地 DBAPI 优化(例如重用准备好的语句)。 - 当访问已加载的相关对象的多对一引用时,调用的代码路径已经大大简化。直接检查标识映射,无需首先生成一个新的
Query
对象,这在访问成千上万个内存中的多对一时是昂贵的。对于大多数延迟属性加载,也不再使用每次构造的“加载器”对象。 - 重写复合体允许在映射器内部访问在刷新中与映射属性相关的属性时,使用更短的代码路径。
- 新的内联属性访问函数取代了以前在“save-update”和其他级联操作需要在属性的所有数据成员范围内级联时使用“history”的用法。这减少了为这个速度关键操作生成新的
History
对象的开销。 ExecutionContext
的内部,对应于语句执行的对象,已经内联并简化。- 为每个语句执行生成的类型的
bind_processor()
和result_processor()
可调用现在被缓存(小心翼翼地,以避免对临时类型和方言造成内存泄漏),在该类型的生命周期内,进一步减少每个语句调用的开销。 - 特定
Compiled
实例的“绑定处理器”集合也被缓存在Compiled
对象上,进一步利用刷新过程使用的“编译缓存”,以重用相同的 INSERT、UPDATE、DELETE 语句的编译形式。
一个减少调用次数的演示,包括一个示例基准脚本,位于techspot.zzzeek.org/2010/12/12/a-tale-of-three
- profiles/。
复合体重写
“复合”功能已经被重写,就像synonym()
和comparable_property()
一样,使用基于描述符和事件的轻量级实现,而不是构建到 ORM 内部。这允许从映射器/工作单元内部删除一些延迟,并简化复合的工作方式。复合属性现在不再隐藏其构建在其上的基础列,这些列现在保持为常规属性。复合还可以充当relationship()
以及Column()
属性的代理。
复合的主要不兼容变更是,它们不再使用mutable=True
系统来检测原地变异。请使用Mutation Tracking扩展来建立对现有复合使用的原地更改事件。
另请参见
复合列类型
变异跟踪
更简洁的查询.join(target, onclause)形式
向具有显式 onclause 的目标发出query.join()
的默认方法现在是:
query.join(SomeClass, SomeClass.id == ParentClass.some_id)
在 0.6 版本中,这种用法被认为是错误的,因为join()
接受多个参数对应于多个 JOIN 子句 - 两个参数形式需要在元组中以消除单参数和双参数连接目标之间的歧义。在 0.6 的中间,我们添加了检测和针对这种特定调用风格的错误消息,因为这种情况非常普遍。在 0.7 中,由于我们无论如何都在检测确切的模式,并且由于不得不无缘无故地输入元组非常恼人,非元组方法现在成为“正常”做法。与单个连接情况相比,“多个 JOIN”用例极为罕见,而如今多个连接更清晰地表示为多次调用join()
。
元组形式将保留以确保向后兼容性。
请注意,所有其他形式的query.join()
保持不变:
query.join(MyClass.somerelation) query.join("somerelation") query.join(MyTarget) # ... etc
变异事件扩展,取代“mutable=True”
一个新的扩展,变异跟踪,提供了一种机制,通过该机制,用户定义的数据类型可以向拥有的父级或父级提供更改事件。该扩展包括一种用于标量数据库值的方法,例如由PickleType
管理的值,postgresql.ARRAY
或其他自定义MutableType
类,以及一种用于 ORM“复合”配置的方法,这些配置使用composite()
。
另请参见
变异跟踪
NULLS FIRST / NULLS LAST 操作符
这些被实现为asc()
和desc()
运算符的扩展,称为nullsfirst()
和nullslast()
。
另请参阅
nullsfirst()
nullslast()
select.distinct(),query.distinct()接受*args 用于 PostgreSQL DISTINCT ON
通过将表达式列表传递给select()
的distinct
关键字参数,现在select()
和Query
的distinct()
方法接受位置参数,当使用 PostgreSQL 后端时,这些参数将被渲染为 DISTINCT ON。
Index()
可以内联放置在Table
、__table_args__
内
Index()构造可以与 Table 定义内联创建,使用字符串作为列名,作为在 Table 之外创建索引的替代方法。即:
Table( "mytable", metadata, Column("id", Integer, primary_key=True), Column("name", String(50), nullable=False), Index("idx_name", "name"), )
这里的主要原因是为了声明性__table_args__
的好处,特别是在与混合使用时:
class HasNameMixin(object): name = Column("name", String(50), nullable=False) @declared_attr def __table_args__(cls): return (Index("name"), {}) class User(HasNameMixin, Base): __tablename__ = "user" id = Column("id", Integer, primary_key=True)
窗口函数 SQL 构造
“窗口函数”为语句提供了有关生成的结果集的信息。这允许根据诸如“行号”、“排名”等各种条件进行查询。它们至少被已知支持的 PostgreSQL、SQL Server 和 Oracle 支持,可能还有其他数据库。
关于窗口函数的最佳介绍在 PostgreSQL 的网站上,窗口函数自 8.4 版本起就得到支持:
www.postgresql.org/docs/current/static/tutorial-window.html
SQLAlchemy 提供了一个简单的构造,通常通过现有的函数子句调用,使用over()
方法,接受order_by
和partition_by
关键字参数。下面我们复制了 PG 教程中的第一个示例:
from sqlalchemy.sql import table, column, select, func empsalary = table("empsalary", column("depname"), column("empno"), column("salary")) s = select( [ empsalary, func.avg(empsalary.c.salary) .over(partition_by=empsalary.c.depname) .label("avg"), ] ) print(s)
SQL:
SELECT empsalary.depname, empsalary.empno, empsalary.salary, avg(empsalary.salary) OVER (PARTITION BY empsalary.depname) AS avg FROM empsalary
sqlalchemy.sql.expression.over
Connection 上的 execution_options()接受“isolation_level”参数
这为单个Connection
设置了事务隔离级别,直到该Connection
关闭并其底层 DBAPI 资源返回到连接池,此时隔离级别将重置为默认值。默认的隔离级别是使用create_engine()
的isolation_level
参数设置的。
事务隔离支持目前仅由 PostgreSQL 和 SQLite 后端支持。
TypeDecorator
与整数主键列一起使用
可以使用扩展Integer
行为的TypeDecorator
与主键列一起使用。Column
的“autoincrement”特性现在将识别到底层数据库列仍然是整数,以便lastrowid
机制继续正常工作。TypeDecorator
本身的结果值处理器将应用于新生成的主键,包括通过 DBAPI cursor.lastrowid
访问器接收到的主键。
TypeDecorator
存在于“sqlalchemy”导入空间中
不再需要从sqlalchemy.types
导入,现在在sqlalchemy
中有镜像。
新方言
已添加方言:
- 用于 Drizzle 数据库的 MySQLdb 驱动程序:
Drizzle - 支持 pymysql DBAPI:
pymsql Notes - psycopg2 现在与 Python 3 兼容
行为变更(向后兼容)
默认情况下构建 C 扩展
这是从 0.7b4 开始的。如果检测到 cPython 2.xx,则会构建扩展。如果构建失败,例如在 Windows 安装中,会捕获该条件并继续非 C 安装。如果使用 Python 3 或 PyPy,则不会构建 C 扩展。
简化的 Query.count(),几乎总是有效
Query.count()
内部的非常古老的猜测现在已经被现代化,使用.from_self()
。也就是说,query.count()
现在等效于:
query.from_self(func.count(literal_column("1"))).scalar()
以前,内部逻辑尝试重写查询本身的列子句,并在检测到“子查询”条件时,例如可能在其中具有聚合的基于列的查询,或具有 DISTINCT 的查询时,会经历一个繁琐的过程来重写列子句。这种逻辑在复杂条件下失败,特别是涉及联接表继承的条件,并且长期以来已经被更全面的.from_self()
调用所淘汰。
query.count()
生成的 SQL 现在总是形式为:
SELECT count(1) AS count_1 FROM ( SELECT user.id AS user_id, user.name AS user_name from user ) AS anon_1
换句话说,原始查询完全保留在子查询中,不再需要猜测如何应用计数。
发出非子查询形式的 count()
MySQL 用户已经报告说,MyISAM 引擎在这个简单的更改中完全崩溃,这并不奇怪。请注意,对于优化不能处理简单子查询的数据库的简单count()
,应该使用func.count()
:
from sqlalchemy import func session.query(func.count(MyClass.id)).scalar()
或者对于count(*)
:
from sqlalchemy import func, literal_column session.query(func.count(literal_column("*"))).select_from(MyClass).scalar()
LIMIT/OFFSET 子句现在使用绑定参数
LIMIT 和 OFFSET 子句,或其后端等效项(即 TOP,ROW NUMBER OVER 等),对于支持它的所有后端使用绑定参数进行实际值,(除了 Sybase 之外的大多数后端)。这样做可以提高查询优化器的性能,因为具有不同 LIMIT/OFFSET 的多个语句的文本字符串现在是相同的。
日志增强
Vinay Sajip 提供了一个补丁,使我们的日志系统中不再需要在引擎和池的日志语句中嵌入“十六进制字符串”以使echo
标志正常工作。使用过滤日志对象的新系统使我们能够保持echo
仅适用于各个引擎而无需额外的标识字符串。
简化的多态 _on 赋值
在继承场景中使用时,polymorphic_on
列映射属性的填充现在发生在对象构造时,即调用其__init__
方法时,使用 init 事件。然后,该属性的行为与任何其他列映射属性相同。以前,特殊逻辑会在刷新期间触发以填充此列,这会阻止任何用户代码修改其行为。新方法在三个方面改进了这一点:1.多态标识现在在对象构造时立即存在;2.用户代码可以更改多态标识而不会与任何其他列映射属性有任何不同的行为;3.在刷新期间,映射器的内部简化,不再需要对此列进行特殊检查。
跨多个路径(即“all()”)的 contains_eager()链
contains_eager()
修改器现在会把自己链接到一个更长的路径上,而不需要释放独立的contains_eager()
调用。而不是:
session.query(A).options(contains_eager(A.b), contains_eager(A.b, B.c))
你可以说:
session.query(A).options(contains_eager(A.b, B.c))
允许刷新没有父级的孤儿
我们一直有一个长期存在的行为,即在刷新时检查所谓的“孤儿”,即与指定“delete-orphan”级联的relationship()
相关联的对象,已经被新添加到会话中进行 INSERT 操作,但尚未建立父关系。多年前添加了此检查以适应一些测试用例,这些测试用例测试了孤儿行为的一致性。在现代 SQLA 中,这种检查在 Python 端不再需要。通过使对象的外键引用对象的父行为 NOT NULL,数据库会以 SQLA 允许大多数其他操作执行的方式确保数据一致性,从而实现“孤儿检查”的等效行为。如果对象的父外键是可为空的,则可以插入行。当对象与特定父对象一起持久化,然后与该父对象解除关联时,会触发“孤儿”行为,导致为其发出 DELETE 语句。
在收集成员,不是刷新的标量引用时生成的警告
当通过父对象上标记为“脏”的加载relationship()
引用的相关对象在当前Session
中不存在时,现在会发出警告。
当对象被添加到Session
时,或者当对象首次与父对象关联时,save-update
级联生效,以便对象及其所有相关内容通常都存在于同一个Session
中。但是,如果对于特定的relationship()
禁用了save-update
级联,则此行为不会发生,并且刷新过程不会尝试纠正它,而是保持一致到配置的级联行为。以前,在刷新期间检测到这样的对象时,它们会被静默跳过。新行为是发出警告,目的是提醒一个经常是意外行为来源的情况。
设置不再安装 Nose 插件
自从我们转向 nose 以来,我们使用了一个通过 setuptools 安装的插件,这样nosetests
脚本会自动运行 SQLA 的插件代码,这对于我们的测试来说是必要的,以便有一个完整的环境。在 0.6 的中间,我们意识到这里的导入模式意味着 Nose 的“coverage”插件会中断,因为“coverage”要求在导入要覆盖的任何模块之前启动它;所以在 0.6 的中间,我们通过添加一个单独的sqlalchemy-nose
包来克服这一情况,使情况变得更糟。
在 0.7 中,我们已经放弃了尝试让nosetests
自动工作,因为 SQLAlchemy 模块会为所有nosetests
的用法产生大量的 nose 配置选项,而不仅仅是 SQLAlchemy 单元测试本身,而且额外的sqlalchemy-nose
安装甚至更糟,会在 Python 环境中产生一个额外的包。在 0.7 中,sqla_nose.py
脚本现在是使用 nose 运行测试的唯一方法。
非Table
派生的构造可以被映射
一个根本不针对任何Table
的构造,比如一个函数,可以被映射。
from sqlalchemy import select, func from sqlalchemy.orm import mapper class Subset(object): pass selectable = select(["x", "y", "z"]).select_from(func.some_db_function()).alias() mapper(Subset, selectable, primary_key=[selectable.c.x])
aliased()接受FromClause
元素
这是一个方便的辅助工具,以便在传递一个普通的FromClause
,比如一个select
,Table
或join
到orm.aliased()
构造时,它会通过到该 from 构造的.alias()
方法,而不是构造一个 ORM 级别的AliasedClass
。
Session.connection(),Session.execute()接受‘bind’
这是为了允许执行/连接操作明确参与引擎的打开事务。它还允许Session
的自定义子类实现自己的get_bind()
方法和参数,以便在execute()
和connection()
方法中等效地使用这些自定义参数。
Session.connection Session.execute
列子句中的独立绑定参数自动标记。
在选择的“列子句”中存在的绑定参数现在像其他“匿名”子句一样自动标记,这样在获取行时它们的“类型”就有意义,就像结果行处理器一样。
SQLite - 相对文件路径通过 os.path.abspath()进行标准化
这样,更改当前目录的脚本将继续以后续建立的 SQLite 连接的相同位置为目标。
MS-SQL - String
/Unicode
/VARCHAR
/NVARCHAR
/VARBINARY
在未指定长度时发出“max”
在 MS-SQL 后端,String/Unicode 类型及其对应的 VARCHAR/NVARCHAR 类型,以及 VARBINARY (#1833)在未指定长度时发出“max”作为长度。这使其与 PostgreSQL 的 VARCHAR 类型更兼容,当未指定长度时同样是无界的。SQL Server 在未指定长度时将这些类型的长度默认为‘1’。
默认构建 C 扩展
这是从 0.7b4 开始的。如果检测到 cPython 2.xx,则将构建扩展。如果构建失败,例如在 Windows 安装中,将捕获该条件并继续非 C 安装。如果使用 Python 3 或 PyPy,则不会构建 C 扩展。
Query.count()简化,几乎总是有效
Query.count()
内部的非常古老的猜测现已现代化为使用.from_self()
。也就是说,query.count()
现在等效于:
query.from_self(func.count(literal_column("1"))).scalar()
以前,内部逻辑尝试重写查询本身的列子句,并在检测到“子查询”条件时,例如可能在其中具有聚合函数的基于列的查询,或具有 DISTINCT 的查询,将经历一个复杂的过程来重写列子句。这种逻辑在复杂条件下失败,特别是涉及联接表继承的条件,并且已经被更全面的.from_self()
调用长时间废弃。
query.count()
发出的 SQL 现在始终是以下形式:
SELECT count(1) AS count_1 FROM ( SELECT user.id AS user_id, user.name AS user_name from user ) AS anon_1
即原始查询完全保留在子查询中,不再猜测应如何应用计数。
发出非子查询形式的 count()
MySQL 用户已经报告说,MyISAM 引擎不出所料地完全崩溃了这个简单的更改。请注意,对于优化无法处理简单子查询的数据库的简单count()
,应使用func.count()
:
from sqlalchemy import func session.query(func.count(MyClass.id)).scalar()
或对于count(*)
:
from sqlalchemy import func, literal_column session.query(func.count(literal_column("*"))).select_from(MyClass).scalar()
发出非子查询形式的 count()
MySQL 用户已经报告说,MyISAM 引擎不出所料地完全崩溃了这个简单的更改。请注意,对于优化无法处理简单子查询的数据库的简单count()
,应使用func.count()
:
from sqlalchemy import func session.query(func.count(MyClass.id)).scalar()
或对于count(*)
:
from sqlalchemy import func, literal_column session.query(func.count(literal_column("*"))).select_from(MyClass).scalar()
LIMIT/OFFSET 子句现在使用绑定参数
LIMIT 和 OFFSET 子句,或其后端等效项(即 TOP,ROW NUMBER OVER 等),对实际值使用绑定参数,对于支持它的所有后端(除了 Sybase)。这允许更好的查询优化器性能,因为具有不同 LIMIT/OFFSET 的多个语句的文本字符串现在是相同的。
SqlAlchemy 2.0 中文文档(七十九)(4)https://developer.aliyun.com/article/1561107