SQLAlchemy 1.0 中的新功能?
关于本文档
本文档描述了 SQLAlchemy 版本 0.9 与 2014 年 5 月维护发布的版本之间的更改,以及于 2015 年 4 月发布的版本 1.0 之间的更改。
文档最后更新日期:2015 年 6 月 9 日
介绍
本指南介绍了 SQLAlchemy 版本 1.0 中的新功能,并记录了影响用户将其应用程序从 SQLAlchemy 0.9 系列迁移到 1.0 的更改。
请仔细查看行为变化部分,可能会有不兼容的行为变化。
新功能和改进 - ORM
新会话批量插入/更新 API
创建了一系列新的 Session
方法,直接提供钩子进入工作单元的发出 INSERT 和 UPDATE 语句的功能。当正确使用时,这个面向专家的系统可以允许使用 ORM 映射生成批量插入和更新语句批量执行,使语句以与直接使用 Core 相媲美的速度进行。
另请参阅
批量操作 - 介绍和完整文档
新性能示例套件
受到批量操作功能以及 FAQ 中的如何对 SQLAlchemy 驱动的应用程序进行性能分析?部分进行的基准测试的启发,添加了一个新的示例部分,其中包含几个旨在说明各种核心和 ORM 技术的相对性能特征的脚本。这些脚本按用例组织,并打包在一个单一的控制台界面下,以便可以运行任何组合的演示,输出时间、Python 分析结果和/或 RunSnake 分析显示。
另请参阅
性能
“烘焙”查询
“烘焙”查询功能是一种不同寻常的新方法,允许使用缓存直接构建和调用 Query
对象,通过连续调用大大减少了 Python 函数调用开销(超过 75%)。通过将一个 Query
对象指定为一系列仅调用一次的 lambda,查询作为一个预编译单元开始变得可行:
from sqlalchemy.ext import baked from sqlalchemy import bindparam bakery = baked.bakery() def search_for_user(session, username, email=None): baked_query = bakery(lambda session: session.query(User)) baked_query += lambda q: q.filter(User.name == bindparam("username")) baked_query += lambda q: q.order_by(User.id) if email: baked_query += lambda q: q.filter(User.email == bindparam("email")) result = baked_query(session).params(username=username, email=email).all() return result
另请参阅
烘焙查询
改进声明性混合,@declared_attr
和相关功能
声明式系统与declared_attr
结合进行了大幅改进,以支持新的功能。
用declared_attr
修饰的函数现在仅在生成基于混合的列副本之后才被调用。这意味着该函数可以调用混合建立的列,并将接收到正确的Column
对象的引用:
class HasFooBar(object): foobar = Column(Integer) @declared_attr def foobar_prop(cls): return column_property("foobar: " + cls.foobar) class SomeClass(HasFooBar, Base): __tablename__ = "some_table" id = Column(Integer, primary_key=True)
在上述示例中,SomeClass.foobar_prop
将针对SomeClass
调用,并且SomeClass.foobar
将是要映射到SomeClass
的最终Column
对象,而不是直接出现在HasFooBar
上的非副本对象,即使列尚未映射。
declared_attr
函数现在会根据每个类缓存返回的值,这样对同一属性的重复调用将返回相同的值。我们可以修改示例来说明这一点:
class HasFooBar(object): @declared_attr def foobar(cls): return Column(Integer) @declared_attr def foobar_prop(cls): return column_property("foobar: " + cls.foobar) class SomeClass(HasFooBar, Base): __tablename__ = "some_table" id = Column(Integer, primary_key=True)
以前,SomeClass
将使用foobar
列的特定副本进行映射,但通过第二次调用foobar
来调用foobar_prop
将会产生不同的列。在声明性设置时间内,SomeClass.foobar
的值现在被记忆,因此即使在属性由映射器映射之前,每次调用declared_attr
时,中间列值都将保持一致。
上述两种行为应该极大地帮助声明式定义许多从其他属性派生的映射器属性类型,其中declared_attr
函数是从其他本地declared_attr
函数调用的,这些函数在类实际映射之前出现。
对于一个相当特殊的边缘情况,其中希望构建一个声明性混合类,为每个子类建立不同的列,添加了一个新的修饰符 declared_attr.cascading
。使用此修饰符,装饰的函数将为映射继承层次结构中的每个类单独调用。虽然对于特殊属性如 __table_args__
和 __mapper_args__
,这已经是行为,但对于列和其他属性,默认情况下假定该属性仅附加到基类,并且仅从子类继承。使用 declared_attr.cascading
,可以应用个别行为:
class HasIdMixin(object): @declared_attr.cascading def id(cls): if has_inherited_table(cls): return Column(ForeignKey("myclass.id"), primary_key=True) else: return Column(Integer, primary_key=True) class MyClass(HasIdMixin, Base): __tablename__ = "myclass" # ... class MySubClass(MyClass): """ """ # ...
另请参见
使用 _orm.declared_attr() 生成特定表继承列
最后,AbstractConcreteBase
类已经重新设计,以便在抽象基类上内联设置关系或其他映射器属性:
from sqlalchemy import Column, Integer, ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import ( declarative_base, declared_attr, AbstractConcreteBase, ) Base = declarative_base() class Something(Base): __tablename__ = "something" id = Column(Integer, primary_key=True) class Abstract(AbstractConcreteBase, Base): id = Column(Integer, primary_key=True) @declared_attr def something_id(cls): return Column(ForeignKey(Something.id)) @declared_attr def something(cls): return relationship(Something) class Concrete(Abstract): __tablename__ = "cca" __mapper_args__ = {"polymorphic_identity": "cca", "concrete": True}
上述映射将设置一个带有 id
和 something_id
列的表 cca
,并且 Concrete
还将具有一个名为 something
的关系。新功能是 Abstract
也将具有一个独立配置的关系 something
,该关系构建在基类的多态联合上。
ORM 完整对象获取速度提高 25%
loading.py
模块的机制以及标识映射已经经历了几次内联、重构和修剪,因此现在原始行的加载速度大约快了 25%。假设有一个包含 100 万行的表,下面的脚本演示了改进最多的加载类型:
import time from sqlalchemy import Integer, Column, create_engine, Table from sqlalchemy.orm import Session from sqlalchemy.ext.declarative import declarative_base Base = declarative_base() class Foo(Base): __table__ = Table( "foo", Base.metadata, Column("id", Integer, primary_key=True), Column("a", Integer(), nullable=False), Column("b", Integer(), nullable=False), Column("c", Integer(), nullable=False), ) engine = create_engine("mysql+mysqldb://scott:tiger@localhost/test", echo=True) sess = Session(engine) now = time.time() # avoid using all() so that we don't have the overhead of building # a large list of full objects in memory for obj in sess.query(Foo).yield_per(100).limit(1000000): pass print("Total time: %d" % (time.time() - now))
本地 MacBookPro 结果从 0.9 秒降至 1.0 秒的时间为 19 秒,降至 14 秒。在批量处理大量行时,Query.yield_per()
的调用总是一个好主意,因为它可以防止 Python 解释器一次性为所有对象及其仪器分配大量内存。没有 Query.yield_per()
,在 MacBookPro 上的上述脚本在 0.9 上需要 31 秒,在 1.0 上需要 26 秒,额外的时间用于设置非常大的内存缓冲区。
新的 KeyedTuple 实现速度显著提高
我们研究了 KeyedTuple
实现,希望改进这样的查询:
rows = sess.query(Foo.a, Foo.b, Foo.c).all()
使用 KeyedTuple
类而不是 Python 的 collections.namedtuple()
,因为后者具有一个非常复杂的类型创建例程,比 KeyedTuple
慢得多。然而,当获取数十万行时,collections.namedtuple()
很快超过 KeyedTuple
,随着实例调用次数的增加,KeyedTuple
的速度会急剧变慢。怎么办?一种新类型,介于两者之间的方法。对于“size”(返回的行数)和“num”(不同查询的数量)对所有三种类型进行测试,新的“轻量级键值元组”要么优于两者,要么略逊于更快的对象,具体取决于情况。在“甜蜜点”上,我们既创建了大量新类型,又获取了大量行,轻量级对象完全超过了 namedtuple 和 KeyedTuple:
----------------- size=10 num=10000 # few rows, lots of queries namedtuple: 3.60302400589 # namedtuple falls over keyedtuple: 0.255059957504 # KeyedTuple very fast lw keyed tuple: 0.582715034485 # lw keyed trails right on KeyedTuple ----------------- size=100 num=1000 # <--- sweet spot namedtuple: 0.365247011185 keyedtuple: 0.24896979332 lw keyed tuple: 0.0889317989349 # lw keyed blows both away! ----------------- size=10000 num=100 namedtuple: 0.572599887848 keyedtuple: 2.54251694679 lw keyed tuple: 0.613876104355 ----------------- size=1000000 num=10 # few queries, lots of rows namedtuple: 5.79669594765 # namedtuple very fast keyedtuple: 28.856498003 # KeyedTuple falls over lw keyed tuple: 6.74346804619 # lw keyed trails right on namedtuple
#3176### 结构化内存使用方面的显著改进
通过对许多内部对象更显著地使用 __slots__
,改进了结构化内存使用。这种优化特别针对具有大量表和列的大型应用程序的基本内存大小,并减少了各种高容量对象的内存大小,包括事件监听内部、比较器对象以及 ORM 属性和加载器策略系统的部分。
一个使用 heapy 测量 Nova 启动大小的工作台展示了 SQLAlchemy 对象、相关字典以及弱引用在“nova.db.sqlalchemy.models”基本导入中占用的空间减少了约 3.7 兆字节,或者说减少了 46%:
# reported by heapy, summation of SQLAlchemy objects + # associated dicts + weakref-related objects with core of Nova imported: Before: total count 26477 total bytes 7975712 After: total count 18181 total bytes 4236456 # reported for the Python module space overall with the # core of Nova imported: Before: Partition of a set of 355558 objects. Total size = 61661760 bytes. After: Partition of a set of 346034 objects. Total size = 57808016 bytes. ```### UPDATE 语句现在在刷新中与 executemany() 批处理 现在可以将 UPDATE 语句批处理到 ORM 刷新中,以更高效的 executemany() 调用执行,类似于 INSERT 语句可以批处理;这将根据以下标准在刷新中调用: + 两个或更多连续的 UPDATE 语句涉及相同的要修改的列集。 + 语句在 SET 子句中没有嵌入的 SQL 表达式。 + 映射不使用 `mapper.version_id_col`,或者后端方言支持 executemany() 操作的“合理”行数;大多数 DBAPI 现在正确支持这一点。### Session.get_bind() 处理更广泛的继承场景 每当查询或工作单元刷新过程寻找与特定类对应的数据库引擎时,都会调用 `Session.get_bind()` 方法。该方法已经改进,以处理各种继承导向的场景,包括: + 绑定到一个 Mixin 或抽象类: ```py class MyClass(SomeMixin, Base): __tablename__ = "my_table" # ... session = Session(binds={SomeMixin: some_engine}) ``` + 基于表格,分别绑定到继承的具体子类: ```py class BaseClass(Base): __tablename__ = "base" # ... class ConcreteSubClass(BaseClass): __tablename__ = "concrete" # ... __mapper_args__ = {"concrete": True} session = Session(binds={base_table: some_engine, concrete_table: some_other_engine}) ``` [#3035](https://www.sqlalchemy.org/trac/ticket/3035) ### 在所有相关的查询情况下,Session.get_bind()将接收到 Mapper 修复了一系列问题,其中`Session.get_bind()`未接收到`Query`的主要`Mapper`,尽管此映射器是 readily available 的(主映射器是与`Query`对象关联的单个映射器,或者替代是与查询关联的第一个映射器)。 当传递给`Session.get_bind()`的`Mapper`对象通常由使用`Session.binds`参数的会话使用,以将映射器与一系列引擎关联(虽然在这种用例中,通常情况下“工作”,因为绑定通常会通过映射的表对象找到),或者更具体地实现一个用户定义的`Session.get_bind()`方法,该方法基于映射器提供一些选择引擎的模式,例如水平分片或所谓的“路由”会话,将查询路由到不同的后端。 这些场景包括: + `Query.count()`: ```py session.query(User).count() ``` + `Query.update()` 和 `Query.delete()`,用于 UPDATE/DELETE 语句以及“fetch”策略所使用的 SELECT: ```py session.query(User).filter(User.id == 15).update( {"name": "foob"}, synchronize_session="fetch" ) session.query(User).filter(User.id == 15).delete(synchronize_session="fetch") ``` + 对个别列的查询: ```py session.query(User.id, User.name).all() ``` + 对间接映射(例如`column_property`)的 SQL 函数和其他表达式: ```py class User(Base): ... score = column_property(func.coalesce(self.tables.users.c.name, None)) session.query(func.max(User.score)).scalar() ``` [#3227](https://www.sqlalchemy.org/trac/ticket/3227) [#3242](https://www.sqlalchemy.org/trac/ticket/3242) [#1326](https://www.sqlalchemy.org/trac/ticket/1326) ### .info 字典改进 `InspectionAttr.info` 集合现在可用于从`Mapper.all_orm_descriptors`集合中检索到的每种对象。这包括`hybrid_property`和`association_proxy()`。然而,由于这些对象是类绑定的描述符,必须**分开**从它们附加到的类中访问以获取属性。以下是使用`Mapper.all_orm_descriptors`命名空间进行说明: ```py class SomeObject(Base): # ... @hybrid_property def some_prop(self): return self.value + 5 inspect(SomeObject).all_orm_descriptors.some_prop.info["foo"] = "bar"
它还可作为所有SchemaItem
对象(例如ForeignKey
,UniqueConstraint
等)的构造函数参数,以及剩余的 ORM 构造,如synonym()
。
#2963 ### ColumnProperty 构造与别名,order_by 配合效果更好
关于column_property()
的各种问题已得到解决,特别是关于aliased()
构造以及在 0.9 版本中引入的“按标签排序”逻辑(参见标签构造现在可以单独作为其名称在 ORDER BY 中呈现)。
给定如下映射:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) class B(Base): __tablename__ = "b" id = Column(Integer, primary_key=True) a_id = Column(ForeignKey("a.id")) A.b = column_property(select([func.max(B.id)]).where(B.a_id == A.id).correlate(A))
简单的场景中,包含两次“A.b”将无法正确渲染:
print(sess.query(A, a1).order_by(a1.b))
这会导致错误的列排序:
SELECT a.id AS a_id, (SELECT max(b.id) AS max_1 FROM b WHERE b.a_id = a.id) AS anon_1, a_1.id AS a_1_id, (SELECT max(b.id) AS max_2 FROM b WHERE b.a_id = a_1.id) AS anon_2 FROM a, a AS a_1 ORDER BY anon_1
新的输出:
SELECT a.id AS a_id, (SELECT max(b.id) AS max_1 FROM b WHERE b.a_id = a.id) AS anon_1, a_1.id AS a_1_id, (SELECT max(b.id) AS max_2 FROM b WHERE b.a_id = a_1.id) AS anon_2 FROM a, a AS a_1 ORDER BY anon_2
还有许多情况下,“order by”逻辑会无法按标签排序,例如如果映射为“多态”:
class A(Base): __tablename__ = "a" id = Column(Integer, primary_key=True) type = Column(String) __mapper_args__ = {"polymorphic_on": type, "with_polymorphic": "*"}
order_by 将无法使用标签,因为由于多态加载,标签将被匿名化:
SELECT a.id AS a_id, a.type AS a_type, (SELECT max(b.id) AS max_1 FROM b WHERE b.a_id = a.id) AS anon_1 FROM a ORDER BY (SELECT max(b.id) AS max_2 FROM b WHERE b.a_id = a.id)
现在,由于按标签排序跟踪了匿名化标签,这现在有效:
SELECT a.id AS a_id, a.type AS a_type, (SELECT max(b.id) AS max_1 FROM b WHERE b.a_id = a.id) AS anon_1 FROM a ORDER BY anon_1
这些修复中还包括了一系列能够破坏aliased()
构造状态的 heisenbugs,从而导致标签逻辑再次失败;这些问题也已得到解决。
新功能和改进 - 核心
选择/查询 LIMIT / OFFSET 可以指定为任意 SQL 表达式
Select.limit()
和 Select.offset()
方法现在接受任何 SQL 表达式作为参数,而不仅仅是整数值。ORM Query
对象也会将任何表达式传递给底层的 Select
对象。通常情况下,这用于允许传递绑定参数,稍后可以用值替换:
sel = select([table]).limit(bindparam("mylimit")).offset(bindparam("myoffset"))
不支持非整数 LIMIT 或 OFFSET 表达式的方言可能会继续不支持此行为;第三方方言可能还需要修改以利用新行为。当前使用 ._limit
或 ._offset
属性的方言将继续对指定为简单整数值的限制/偏移的情况进行处理。但是,当指定 SQL 表达式时,这两个属性在访问时将引发 CompileError
。希望支持新功能的第三方方言现在应调用 ._limit_clause
和 ._offset_clause
属性以接收完整的 SQL 表达式,而不是整数值。### ForeignKeyConstraint
上的 use_alter
标志(通常)不再需要
MetaData.create_all()
和 MetaData.drop_all()
方法现在将使用一个系统,自动为涉及表之间相互依赖循环的外键约束生成 ALTER 语句,无需指定 ForeignKeyConstraint.use_alter
。此外,外键约束现在不再需要具有名称才能通过 ALTER 创建;只有 DROP 操作需要名称。在 DROP 的情况下,该功能将确保只有具有显式名称的约束实际上包含在 ALTER 语句中。在 DROP 中存在无法解决的循环的情况下,如果无法继续执行 DROP,系统现在会发出简洁明了的错误消息。
ForeignKeyConstraint.use_alter
和 ForeignKey.use_alter
标志仍然存在,并且继续具有相同的效果,用于在 CREATE/DROP 场景中需要 ALTER 的约束条件的建立。
从版本 1.0.1 开始,在 SQLite 的情况下,特殊逻辑接管,在 DROP 过程中,给定表存在无法解决的循环;在这种情况下会发出警告,并且表将以无顺序删除,这在 SQLite 上通常是可以接受的,除非启用了约束。要解决警告并在 SQLite 数据库上至少进行部分排序,特别是在启用了约束的情况下,重新应用“use_alter”标志到那些应明确从排序中省略的 ForeignKey
和 ForeignKeyConstraint
对象。
另请参见
通过 ALTER 创建/删除外键约束 - 新行为的完整描述。
#3282 ### ResultProxy “auto close” 现在是“soft” close
在许多版本中,ResultProxy
对象一直在获取所有结果行后自动关闭。这是为了允许在不需要显式调用 ResultProxy.close()
的情况下使用对象;因为所有的 DBAPI 资源都已被释放,对象可以安全丢弃。然而,对象保持了严格的“closed”行为,这意味着任何后续对 ResultProxy.fetchone()
、ResultProxy.fetchmany()
或 ResultProxy.fetchall()
的调用现在会引发 ResourceClosedError
:
>>> result = connection.execute(stmt) >>> result.fetchone() (1, 'x') >>> result.fetchone() None # indicates no more rows >>> result.fetchone() exception: ResourceClosedError
这种行为与 pep-249 所述的不一致,pep-249 表明,即使结果已经耗尽,也可以重复调用获取方法。它还干扰了某些结果代理的行为,例如 cx_oracle 方言用于某些数据类型的 BufferedColumnResultProxy
。
为了解决这个问题,ResultProxy
的“closed”状态被分为两个状态;一个“soft close”执行了“close”大部分功能,释放了 DBAPI 游标,并且在“close with result”对象的情况下还会释放连接,另一个“closed”状态包括了“soft close”的所有内容以及将获取方法设为“closed”。ResultProxy.close()
方法现在不会隐式调用,只会调用非公开的 ResultProxy._soft_close()
方法:
>>> result = connection.execute(stmt) >>> result.fetchone() (1, 'x') >>> result.fetchone() None # indicates no more rows >>> result.fetchone() None # still None >>> result.fetchall() [] >>> result.close() >>> result.fetchone() exception: ResourceClosedError # *now* it raises
CHECK 约束现在支持命名约定中的%(column_0_name)s
标记
%(column_0_name)s
将从CheckConstraint
表达式中找到的第一列派生:
metadata = MetaData(naming_convention={"ck": "ck_%(table_name)s_%(column_0_name)s"}) foo = Table("foo", metadata, Column("value", Integer)) CheckConstraint(foo.c.value > 5)
将呈现:
CREATE TABLE foo ( value INTEGER, CONSTRAINT ck_foo_value CHECK (value > 5) )
命名约束与由SchemaType
生成的约束的组合,例如Boolean
或Enum
,现在也将使用所有 CHECK 约束约定。
另请参阅
命名 CHECK 约束
为布尔值、枚举和其他模式类型配置命名
当引用的列未附加到表时,约束条件可以在其引用的列附加到表时自动附加
自至少版本 0.8 以来,Constraint
已经具有根据传递的与表关联的列“自动附加”到Table
的能力:
from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint m = MetaData() t = Table("t", m, Column("a", Integer), Column("b", Integer)) uq = UniqueConstraint(t.c.a, t.c.b) # will auto-attach to Table assert uq in t.constraints
为了帮助处理在声明性中经常出现的一些情况,即使Column
对象尚未与Table
关联,这种自动附加逻辑现在也可以运行;建立了额外的事件,以便当这些Column
对象关联时,Constraint
也被添加:
from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint m = MetaData() a = Column("a", Integer) b = Column("b", Integer) uq = UniqueConstraint(a, b) t = Table("t", m, a, b) assert uq in t.constraints # constraint auto-attached
以上功能是在版本 1.0.0b3 中作为一个晚期添加的。截至版本 1.0.4 的修复#3411确保如果Constraint
引用了Column
对象和字符串列名的混合,则不会发生此逻辑;因为我们尚未跟踪将名称添加到Table
的操作:
from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint m = MetaData() a = Column("a", Integer) b = Column("b", Integer) uq = UniqueConstraint(a, "b") t = Table("t", m, a, b) # constraint *not* auto-attached, as we do not have tracking # to locate when a name 'b' becomes available on the table assert uq not in t.constraints
在上面的示例中,将列“a”附加到表“t”的附件事件将在附加列“b”之前触发(因为在“b”之前在Table
构造函数中声明了“a”),如果尝试附加约束,则约束将无法找到“b”。为了保持一致性,如果约束引用任何字符串名称,则会跳过在列附加时自动附加的逻辑。
当Constraint
构造时,如果Table
已经包含所有目标Column
对象,则原始的自动附加逻辑仍然存在:
from sqlalchemy import Table, Column, MetaData, Integer, UniqueConstraint m = MetaData() a = Column("a", Integer) b = Column("b", Integer) t = Table("t", m, a, b) uq = UniqueConstraint(a, "b") # constraint auto-attached normally as in older versions assert uq in t.constraints
#3341 #3411 ### INSERT FROM SELECT 现在包括 Python 和 SQL 表达式默认值
如果未指定,默认情况下,Insert.from_select()
现在包括 Python 和 SQL 表达式默认值;现在解除了不包括非服务器列默认值在 INSERT FROM SELECT 中的限制,并将这些表达式呈现为常量插入 SELECT 语句中:
from sqlalchemy import Table, Column, MetaData, Integer, select, func m = MetaData() t = Table( "t", m, Column("x", Integer), Column("y", Integer, default=func.somefunction()) ) stmt = select([t.c.x]) print(t.insert().from_select(["x"], stmt))
将呈现为:
INSERT INTO t (x, y) SELECT t.x, somefunction() AS somefunction_1 FROM t
可以使用Insert.from_select.include_defaults
来禁用该功能。### 现在列服务器默认值呈现为字面值
当由Column.server_default
设置为 SQL 表达式的DefaultClause
存在时,将打开“literal binds”编译器标志。这允许在 SQL 中嵌入的字面值正确呈现,例如:
from sqlalchemy import Table, Column, MetaData, Text from sqlalchemy.schema import CreateTable from sqlalchemy.dialects.postgresql import ARRAY, array from sqlalchemy.dialects import postgresql metadata = MetaData() tbl = Table( "derp", metadata, Column("arr", ARRAY(Text), server_default=array(["foo", "bar", "baz"])), ) print(CreateTable(tbl).compile(dialect=postgresql.dialect()))
现在呈现为:
CREATE TABLE derp ( arr TEXT[] DEFAULT ARRAY['foo', 'bar', 'baz'] )
以前,字面值"foo", "bar", "baz"
会呈现为绑定参数,在 DDL 中无用。
#3087 ### UniqueConstraint 现在是表反射过程的一部分
使用autoload=True
填充的Table
对象现在将包括UniqueConstraint
构造以及Index
构造。对于 PostgreSQL 和 MySQL,这种逻辑有一些注意事项:
PostgreSQL
当创建唯一约束时,PostgreSQL 的行为是隐式地创建一个与该约束对应的唯一索引。Inspector.get_indexes()
和 Inspector.get_unique_constraints()
方法将继续分别返回这些条目,其中 Inspector.get_indexes()
现在在索引条目中特征上带有一个标记 duplicates_constraint
,指示检测到的相应约束。然而,当使用 Table(..., autoload=True)
执行完整的表反射时,Index
构造被检测为与 UniqueConstraint
关联,并且不会出现在 Table.indexes
集合中;只有 UniqueConstraint
会出现在 Table.constraints
集合中。这种去重逻辑通过在查询 pg_index
时连接到 pg_constraint
表来实现,以查看这两个结构是否关联。
MySQL
MySQL 没有单独的 UNIQUE INDEX 和 UNIQUE 约束概念。虽然在创建表和索引时支持两种语法,但在存储时并没有任何不同。Inspector.get_indexes()
和Inspector.get_unique_constraints()
方法将继续同时返回 MySQL 中 UNIQUE 索引的条目,其中Inspector.get_unique_constraints()
在约束条目中具有一个新的标记duplicates_index
,指示这是与该索引对应的重复条目。然而,在使用Table(..., autoload=True)
执行完整表反射时,UniqueConstraint
构造在任何情况下都不是完全反映的Table
构造的一部分;这个构造始终由在Table.indexes
集合中具有unique=True
设置的Index
表示。
另请参阅
PostgreSQL Index Reflection
MySQL / MariaDB Unique Constraints and Reflection
安全地发出参数化警告的新系统
长期以来,存在一个限制,即警告消息不能引用数据元素,这样一个特定函数可能会发出无限数量的唯一警告。这种情况最常见的地方是在Unicode type received non-unicode bind param value
警告中。将数据值放入此消息中意味着该模块的 Python __warningregistry__
,或在某些情况下是 Python 全局的warnings.onceregistry
,将无限增长,因为在大多数警告场景中,这两个集合中的一个会填充每个不同的警告消息。
通过使用特殊的string
类型,有意改变字符串的哈希方式,我们可以控制大量参数化消息仅在一小组可能的哈希值上进行哈希,这样一个警告,比如Unicode type received non-unicode bind param value
,可以被定制为仅发出特定次数;此后,Python 警告注册表将开始记录它们为重复项。
为了说明,以下测试脚本将仅显示对于 1000 个参数集中的十个参数集发出的十个警告:
from sqlalchemy import create_engine, Unicode, select, cast import random import warnings e = create_engine("sqlite://") # Use the "once" filter (which is also the default for Python # warnings). Exactly ten of these warnings will # be emitted; beyond that, the Python warnings registry will accumulate # new values as dupes of one of the ten existing. warnings.filterwarnings("once") for i in range(1000): e.execute( select([cast(("foo_%d" % random.randint(0, 1000000)).encode("ascii"), Unicode)]) )
这里的警告格式是:
/path/lib/sqlalchemy/sql/sqltypes.py:186: SAWarning: Unicode type received non-unicode bind param value 'foo_4852'. (this warning may be suppressed after 10 occurrences)
SqlAlchemy 2.0 中文文档(七十六)(2)https://developer.aliyun.com/article/1561129