SQLAlchemy 2.0 文档
SQLAlchemy 2.0 文档
- 概述
- SQLAlchemy 统一教程
- SQLAlchemy ORM
- SQLAlchemy Core
- 方言
- 常见问题解答
- 错误消息¶
- 连接和事务
- DBAPI 错误
- SQL 表达式语言
- 对象关系映射
- IllegalStateChangeError 和并发异常
- 父实例 <x> 未绑定到 Session;无法继续执行(延迟加载/延迟刷新/刷新/等)操作
- 由于先前在刷新期间发生的异常,此 Session 的事务已回滚
- 对于关系 <relationship>,delete-orphan 级联通常仅在“一对多”关系的“一”侧配置,而不是在“多对一”或“多对多”关系的“多”侧配置。
- 实例 <instance> 已经通过其 <attribute> 属性与 <instance> 的实例关联,并且只允许有一个父级。
- 关系 X 会将列 Q 复制到列 P,这与关系 ‘Y’ 冲突
- 对象无法转换为“持久”状态,因为此标识映射不再有效。
- 类型注解无法为 Annotated Declarative Table 形式解释
- 当将 <cls> 转换为数据类时,属性源自超类 <cls>,而超类 <cls> 不是数据类。
- 为 <classname> 创建数据类时遇到 Python 数据类错误
- 按主键逐行 ORM 批量更新需要记录包含主键值
- AsyncIO 异常
- 核心异常类
- ORM 异常类
- 旧版异常
- 更改和迁移
项目版本
- 上一篇: 第三方集成问题
- 下一篇: 更改和迁移
- 向上: 首页
- 在此页上
- 错误消息
- 连接和事务
- DBAPI 错误
- SQL 表达式语言
- 对象关系映射
- IllegalStateChangeError 和并发异常
- 父实例 <x> 未绑定到 Session;无法继续执行(延迟加载/延迟刷新/刷新/等)操作
- 由于先前在刷新期间发生的异常,此 Session 的事务已回滚
- 对于关系 <relationship>,delete-orphan 级联通常仅在“一对多”关系的“一”侧配置,而不是在“多对一”或“多对多”关系的“多”侧配置。
- 实例 <instance> 已经通过其 <attribute> 属性与 <instance> 的实例关联,并且只允许有一个父级。
- 关系 X 会将列 Q 复制到列 P,这与关系 ‘Y’ 冲突
- 对象无法转换为“持久”状态,因为此标识映射不再有效。
- 类型注解无法为 Annotated Declarative Table 形式解释
- 当将 <cls> 转换为数据类时,属性源自超类 <cls>,而超类 <cls> 不是数据类。
- 为 <classname> 创建数据类时遇到 Python 数据类错误
- 按主键逐行 ORM 批量更新需要记录包含主键值
- AsyncIO 异常
- 核心异常类
- ORM 异常类
- 旧版异常
错误消息¶
本节列出了 SQLAlchemy 引发或发出的常见错误消息和警告的描述和背景信息。
SQLAlchemy 通常在 SQLAlchemy 特定的异常类的上下文中引发错误。有关这些类的详细信息,请参阅 核心异常 和 ORM 异常。
SQLAlchemy 错误大致可以分为两类:编程时错误 和 运行时错误。编程时错误是由于使用不正确的参数调用函数或方法,或者由于其他面向配置的方法(例如无法解析的映射器配置)而引发的。编程时错误通常是立即的和确定性的。另一方面,运行时错误表示程序运行以响应任意发生的某些条件(例如数据库连接耗尽或发生某些数据相关问题)时发生的故障。运行时错误更可能在运行应用程序的日志中看到,因为程序在响应负载和遇到的数据时遇到这些状态。
由于运行时错误不易重现,并且通常在程序运行时响应某些任意条件而发生,因此它们更难以调试,并且还会影响已投入生产的程序。
在本节中,目标是尝试提供一些最常见的运行时错误以及编程时错误的背景信息。
连接和事务¶
QueuePool 限制大小 <x>,溢出 <y> 已达到,连接超时,超时时间 <z>¶
这可能是最常见的运行时错误,因为它直接涉及到应用程序的工作负载超过了配置的限制,而该限制通常适用于几乎所有 SQLAlchemy 应用程序。
以下几点总结了此错误消息的含义,从大多数 SQLAlchemy 用户应该已经熟悉的最基本要点开始。
SQLAlchemy Engine 对象默认使用连接池 - 这意味着当使用
Engine
对象的 SQL 数据库连接资源时,然后 释放 该资源,数据库连接本身仍然连接到数据库,并返回到内部队列,以便再次使用。即使代码可能看起来正在结束与数据库的对话,但在许多情况下,应用程序仍将维护固定数量的数据库连接,这些连接会一直存在,直到应用程序结束或显式处置池。由于池的存在,当应用程序使用 SQL 数据库连接时,最常见的是通过使用
Engine.connect()
或在使用 ORMSession
进行查询时,此活动不一定会在获取连接对象时立即建立与数据库的新连接;而是查询连接池以获取连接,连接池通常会检索池中现有的连接以供重用。如果没有可用的连接,池将创建一个新的数据库连接,但前提是池尚未超过配置的容量。大多数情况下使用的默认池称为
QueuePool
。当您要求此池为您提供连接但没有可用连接时,它将创建一个新连接,如果正在使用的连接总数小于配置的值。此值等于 池大小加上最大溢出。这意味着如果您将引擎配置为engine = create_engine("mysql+mysqldb://u:p@host/db", pool_size=10, max_overflow=20)
上面的
Engine
将允许 最多 30 个连接 在任何时候都在使用中,不包括从引擎分离或失效的连接。如果新的连接请求到达,并且应用程序的其他部分已在使用 30 个连接,则连接池将阻止一段时间,然后超时并引发此错误消息。为了允许同时使用更多连接,可以使用传递给
create_engine()
函数的create_engine.pool_size
和create_engine.max_overflow
参数调整池。等待连接可用的超时时间使用create_engine.pool_timeout
参数配置。可以通过将
create_engine.max_overflow
设置为值“-1”来配置池以具有无限溢出。使用此设置,池仍将维护固定数量的连接池,但是当请求新连接时,它永远不会阻塞;如果没有可用连接,它将无条件地建立新连接。但是,以这种方式运行时,如果应用程序存在耗尽所有可用连接资源的问题,它最终将达到数据库本身配置的可用连接限制,这将再次返回错误。更严重的是,当应用程序耗尽数据库连接时,通常会在失败之前导致大量资源被耗尽,并且还会干扰依赖于能够连接到数据库的其他应用程序和数据库状态机制。
鉴于以上所述,连接池可以被视为 连接使用的安全阀,为防止恶意应用程序导致整个数据库对所有其他应用程序不可用提供了关键的保护层。当收到此错误消息时,最好修复使用过多连接的问题和/或适当地配置限制,而不是允许无限溢出,这实际上并没有解决根本问题。
是什么原因导致应用程序耗尽其所有可用连接?
应用程序正在处理过多的并发请求,以基于池的配置值执行工作 - 这是最直接的原因。如果您有一个在线程池中运行的应用程序,该线程池允许 30 个并发线程,每个线程使用一个连接,如果您的池未配置为允许一次至少检出 30 个连接,那么一旦您的应用程序收到足够的并发请求,您将收到此错误。解决方案是提高池的限制或降低并发线程的数量。
应用程序未将连接返回到池 - 这是下一个最常见的原因,即应用程序正在使用连接池,但程序未能 释放 这些连接,而是将它们保持打开状态。连接池以及 ORM
Session
确实具有逻辑,使得当会话和/或连接对象被垃圾回收时,会导致底层连接资源被释放,但是不能依赖此行为来及时释放资源。发生这种情况的一个常见原因是应用程序使用 ORM 会话,并且在完成涉及该会话的工作后,不调用
Session.close()
。解决方案是确保 ORM 会话(如果使用 ORM)或引擎绑定的Connection
对象(如果使用 Core)在完成工作结束时显式关闭,可以通过适当的.close()
方法,或者通过使用可用的上下文管理器之一(例如“with:”语句)来正确释放资源。应用程序正在尝试运行长时间运行的事务 - 数据库事务是非常昂贵的资源,绝不应让其空闲等待某些事件发生。如果应用程序正在等待用户按下按钮,或者等待长时间运行的作业队列的结果返回,或者保持与浏览器的持久连接打开,请勿在整个时间内保持数据库事务打开。当应用程序需要与数据库交互并与事件交互时,请在该点打开一个短生命周期的事务,然后关闭它。
应用程序正在死锁 - 这也是导致此错误的常见原因,并且更难理解,如果应用程序由于应用程序端或数据库端死锁而无法完成其连接的使用,则应用程序可能会耗尽所有可用连接,从而导致其他请求收到此错误。死锁的原因包括
使用隐式异步系统(例如 gevent 或 eventlet)而未正确 monkeypatch 所有套接字库和驱动程序,或者在未完全覆盖所有 monkeypatch 驱动程序方法时存在错误,或者在异步系统用于 CPU 密集型工作负载且使用数据库资源的 greenlet 等待时间过长而无法处理它们时(不太常见)。对于绝大多数关系数据库操作,通常不需要或不适合使用隐式或显式异步编程框架;如果应用程序必须在某些功能区域使用异步系统,则最好将面向数据库的业务方法在传统线程中运行,这些线程将消息传递给应用程序的异步部分。
数据库端死锁,例如行相互死锁
线程错误,例如互斥锁中的互死锁,或在同一线程中调用已锁定的互斥锁
请记住,除了使用池之外,另一种选择是完全关闭池。有关此背景信息,请参阅 切换池实现 部分。但是,请注意,当发生此错误消息时,始终 是由于应用程序本身中存在更大的问题;池只是有助于更快地揭示问题。
池类不能与 asyncio 引擎一起使用(反之亦然)¶
QueuePool
池类在内部使用 thread.Lock
对象,并且与 asyncio 不兼容。如果使用 create_async_engine()
函数创建 AsyncEngine
,则适当的队列池类是 AsyncAdaptedQueuePool
,它会自动使用,无需指定。
除了 AsyncAdaptedQueuePool
之外,NullPool
和 StaticPool
池类不使用锁,也适合与异步引擎一起使用。
在不太可能的情况下,如果 AsyncAdaptedQueuePool
池类通过 create_engine()
函数显式指示,也会反向引发此错误。
另请参阅
在无效事务回滚之前无法重新连接。请先完全 rollback()¶
此错误情况指的是 Connection
失效的情况,可能是由于数据库断开连接检测或由于显式调用 Connection.invalidate()
,但仍然存在事务,该事务是由 Connection.begin()
方法显式启动的,或者由于连接自动开始事务(这在 SQLAlchemy 2.x 系列中发出任何 SQL 语句时发生)。当连接失效时,任何正在进行的 Transaction
现在都处于无效状态,必须显式回滚才能将其从 Connection
中删除。
DBAPI 错误¶
Python 数据库 API 或 DBAPI 是数据库驱动程序的规范,可以在 Pep-249 中找到。此 API 指定了一组异常类,用于容纳数据库的全部故障模式。
SQLAlchemy 不会直接生成这些异常。相反,它们是从数据库驱动程序拦截的,并由 SQLAlchemy 提供的异常 DBAPIError
包装,但是异常中的消息 由驱动程序生成,而不是 SQLAlchemy。
InterfaceError¶
为与数据库接口而不是数据库本身相关的错误引发的异常。
此错误是 DBAPI 错误,源自数据库驱动程序 (DBAPI),而不是 SQLAlchemy 本身。
InterfaceError
有时由驱动程序在数据库连接断开或无法连接到数据库的上下文中引发。有关如何处理此问题的提示,请参阅 处理断开连接 部分。
DatabaseError¶
为与数据库本身相关的错误(而不是接口或传递的数据)引发的异常。
此错误是 DBAPI 错误,源自数据库驱动程序 (DBAPI),而不是 SQLAlchemy 本身。
DataError¶
为由于处理数据的问题(如除以零、数值超出范围等)而导致的错误引发的异常。
此错误是 DBAPI 错误,源自数据库驱动程序 (DBAPI),而不是 SQLAlchemy 本身。
OperationalError¶
为与数据库操作相关的错误(不一定在程序员的控制范围内)引发的异常,例如,发生意外断开连接、找不到数据源名称、无法处理事务、处理期间发生内存分配错误等。
此错误是 DBAPI 错误,源自数据库驱动程序 (DBAPI),而不是 SQLAlchemy 本身。
OperationalError
是驱动程序在数据库连接断开或无法连接到数据库的上下文中使用的最常见(但不是唯一)错误类。有关如何处理此问题的提示,请参阅 处理断开连接 部分。
IntegrityError¶
当数据库的关系完整性受到影响时引发的异常,例如,外键检查失败。
此错误是 DBAPI 错误,源自数据库驱动程序 (DBAPI),而不是 SQLAlchemy 本身。
InternalError¶
当数据库遇到内部错误时引发的异常,例如,游标不再有效、事务不同步等。
此错误是 DBAPI 错误,源自数据库驱动程序 (DBAPI),而不是 SQLAlchemy 本身。
InternalError
有时由驱动程序在数据库连接断开或无法连接到数据库的上下文中引发。有关如何处理此问题的提示,请参阅 处理断开连接 部分。
ProgrammingError¶
为编程错误引发的异常,例如,找不到表或表已存在、SQL 语句中存在语法错误、指定的参数数量错误等。
此错误是 DBAPI 错误,源自数据库驱动程序 (DBAPI),而不是 SQLAlchemy 本身。
ProgrammingError
有时由驱动程序在数据库连接断开或无法连接到数据库的上下文中引发。有关如何处理此问题的提示,请参阅 处理断开连接 部分。
NotSupportedError¶
在使用了数据库不支持的方法或数据库 API 的情况下引发的异常,例如,在不支持事务或已关闭事务的连接上请求 .rollback()。
此错误是 DBAPI 错误,源自数据库驱动程序 (DBAPI),而不是 SQLAlchemy 本身。
SQL 表达式语言¶
对象不会生成缓存键,性能影响¶
SQLAlchemy 从 1.4 版本开始包含 SQL 编译缓存机制,该机制允许 Core 和 ORM SQL 构造缓存其字符串化形式,以及用于从语句中获取结果的其他结构信息,从而允许在下次使用结构等效的构造时跳过相对昂贵的字符串编译过程。此系统依赖于为所有 SQL 构造实现的功能,包括 Column
、select()
和 TypeEngine
对象等对象,以生成 缓存键,该缓存键充分表示了它们的状态,达到影响 SQL 编译过程的程度。
如果所讨论的警告指的是广泛使用的对象(例如 Column
对象),并且显示正在影响发出的大多数 SQL 构造(使用 使用日志记录估计缓存性能 中描述的估计技术),以至于应用程序通常未启用缓存,这将对性能产生负面影响,并且在某些情况下可能会有效地产生与先前 SQLAlchemy 版本相比的 性能下降。为什么在升级到 1.4 和/或 2.x 后我的应用程序速度变慢? 中的 FAQ 更详细地介绍了这一点。
如果存在任何疑问,缓存将禁用自身¶
缓存依赖于能够生成准确表示语句 完整结构 的 一致 方式的缓存键。如果特定的 SQL 构造(或类型)没有适当的指令来允许其生成正确的缓存键,则无法安全地启用缓存
缓存键必须表示 完整结构:如果使用该构造的两个单独实例可能会导致呈现不同的 SQL,则使用未捕获第一个和第二个元素之间明显差异的缓存键缓存针对第一个元素实例的 SQL 将导致为第二个实例缓存和呈现不正确的 SQL。
缓存键必须是 一致的:如果构造表示每次都更改的状态(例如文字值),则为其每个实例生成唯一的 SQL,则此构造也不适合缓存,因为重复使用该构造将很快用可能不会再次使用的唯一 SQL 字符串填充语句缓存,从而破坏缓存的目的。
由于以上两个原因,SQLAlchemy 的缓存系统在决定缓存与对象对应的 SQL 时 极其保守。
用于缓存的断言属性¶
警告是根据以下标准发出的。有关每个标准的更多详细信息,请参阅 为什么在升级到 1.4 和/或 2.x 后我的应用程序速度变慢? 部分。
Dialect
本身(即由我们传递给create_engine()
的 URL 的第一部分指定的模块,例如postgresql+psycopg2://
)必须表明它已审查和测试以正确支持缓存,这由设置为True
的Dialect.supports_statement_cache
属性指示。使用第三方方言时,请咨询方言的维护者,以便他们可以遵循 确保可以启用缓存的步骤 在他们的方言中并发布新版本。继承自
TypeDecorator
或UserDefinedType
的第三方或用户自定义类型,必须在其定义中包含ExternalType.cache_ok
属性,包括所有派生子类,并遵循ExternalType.cache_ok
的文档字符串中描述的指南。 和以前一样,如果这些数据类型是从第三方库导入的,请咨询该库的维护者,以便他们可以对其库进行必要的更改并发布新版本。继承自诸如
ClauseElement
,Column
,Insert
等类的第三方或用户自定义 SQL 构造,包括简单的子类以及那些旨在与 自定义 SQL 构造和编译扩展 一起使用的子类,通常应包含设置为True
或False
的HasCacheKey.inherit_cache
属性,具体取决于构造的设计,并遵循 为自定义构造启用缓存支持 中描述的指南。
编译器 StrSQLCompiler 无法渲染 <element type> 类型的元素¶
当尝试字符串化包含不属于默认编译一部分的元素的 SQL 表达式构造时,通常会发生此错误;在这种情况下,错误将针对 StrSQLCompiler
类。 在不太常见的情况下,当特定类型的数据库后端使用了错误的 SQL 表达式类型时,也可能发生这种情况;在这些情况下,将命名其他类型的 SQL 编译器类,例如 SQLCompiler
或 sqlalchemy.dialects.postgresql.PGCompiler
。 以下指南更具体地针对“字符串化”用例,但也描述了一般背景。
通常,Core SQL 构造或 ORM Query
对象可以直接字符串化,例如当我们使用 print()
时
>>> from sqlalchemy import column
>>> print(column("x") == 5)
x = :x_1
当上述 SQL 表达式被字符串化时,将使用 StrSQLCompiler
编译器类,这是一种特殊的语句编译器,当构造在没有任何特定于方言的信息的情况下被字符串化时,它会被调用。
但是,有许多构造特定于某种数据库方言,StrSQLCompiler
不知道如何将其转换为字符串,例如 PostgreSQL 的 INSERT…ON CONFLICT (Upsert) 构造
>>> from sqlalchemy.dialects.postgresql import insert
>>> from sqlalchemy import table, column
>>> my_table = table("my_table", column("x"), column("y"))
>>> insert_stmt = insert(my_table).values(x="foo")
>>> insert_stmt = insert_stmt.on_conflict_do_nothing(index_elements=["y"])
>>> print(insert_stmt)
Traceback (most recent call last):
...
sqlalchemy.exc.UnsupportedCompilationError:
Compiler <sqlalchemy.sql.compiler.StrSQLCompiler object at 0x7f04fc17e320>
can't render element of type
<class 'sqlalchemy.dialects.postgresql.dml.OnConflictDoNothing'>
为了字符串化特定于特定后端的构造,必须使用 ClauseElement.compile()
方法,并传递 Engine
或 Dialect
对象,这将调用正确的编译器。 下面我们使用 PostgreSQL 方言
>>> from sqlalchemy.dialects import postgresql
>>> print(insert_stmt.compile(dialect=postgresql.dialect()))
INSERT INTO my_table (x) VALUES (%(x)s) ON CONFLICT (y) DO NOTHING
对于 ORM Query
对象,可以使用 Query.statement
访问器访问该语句
statement = query.statement
print(statement.compile(dialect=postgresql.dialect()))
请参阅下面的 FAQ 链接,以获取有关 SQL 元素的直接字符串化/编译的更多详细信息。
TypeError: 不支持在 ‘ColumnProperty’ 实例和 <something> 之间使用 <operator>¶
当尝试在 SQL 表达式的上下文中(通常在声明式中)使用 column_property()
或 deferred()
对象时,通常会发生这种情况,例如
class Bar(Base):
__tablename__ = "bar"
id = Column(Integer, primary_key=True)
cprop = deferred(Column(Integer))
__table_args__ = (CheckConstraint(cprop > 5),)
在上面,cprop
属性在映射之前以内联方式使用,但是此 cprop
属性不是 Column
,它是 ColumnProperty
,它是一个中间对象,因此不具有 Column
对象或 InstrumentedAttribute
对象的完整功能,一旦声明过程完成,它将被映射到 Bar
类。
虽然 ColumnProperty
确实有一个 __clause_element__()
方法,这使其可以在某些面向列的上下文中使用,但它不能在如上所示的开放式比较上下文中使用,因为它没有 Python __eq__()
方法,该方法允许它将与数字 “5” 的比较解释为 SQL 表达式,而不是常规的 Python 比较。
解决方案是使用 ColumnProperty.expression
属性直接访问 Column
class Bar(Base):
__tablename__ = "bar"
id = Column(Integer, primary_key=True)
cprop = deferred(Column(Integer))
__table_args__ = (CheckConstraint(cprop.expression > 5),)
绑定参数 <x> (在参数组 <y> 中)需要一个值¶
当语句隐式或显式地使用 bindparam()
,并且在执行语句时未提供值时,会发生此错误
stmt = select(table.c.column).where(table.c.id == bindparam("my_param"))
result = conn.execute(stmt)
在上面,没有为参数 “my_param” 提供值。 正确的方法是提供一个值
result = conn.execute(stmt, {"my_param": 12})
当消息采用 “绑定参数 <x> (在参数组 <y> 中)需要一个值” 的形式时,该消息是指 “executemany” 样式的执行。 在这种情况下,该语句通常是 INSERT、UPDATE 或 DELETE,并且正在传递参数列表。 在此格式中,可以动态生成语句以包含参数列表中给出的每个参数的参数位置,其中它将使用**第一组参数**来确定这些参数应该是什么。
例如,下面的语句是基于第一组参数计算的,需要参数 “a”、“b” 和 “c” - 这些名称确定了语句的最终字符串格式,该格式将用于列表中的每组参数。 由于第二个条目不包含 “b”,因此会生成此错误
m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))
e.execute(
t.insert(),
[
{"a": 1, "b": 2, "c": 3},
{"a": 2, "c": 4},
{"a": 3, "b": 4, "c": 5},
],
)
sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError)
A value is required for bind parameter 'b', in parameter group 1
[SQL: u'INSERT INTO t (a, b, c) VALUES (?, ?, ?)']
[parameters: [{'a': 1, 'c': 3, 'b': 2}, {'a': 2, 'c': 4}, {'a': 3, 'c': 5, 'b': 4}]]
由于 “b” 是必需的,因此将其作为 None
传递,以便 INSERT 可以继续
e.execute(
t.insert(),
[
{"a": 1, "b": 2, "c": 3},
{"a": 2, "b": None, "c": 4},
{"a": 3, "b": 4, "c": 5},
],
)
另请参阅
预期 FROM 子句,但得到 Select。 要创建 FROM 子句,请使用 .subquery() 方法¶
这指的是 SQLAlchemy 1.4 中所做的更改,其中由诸如 select()
之类的函数生成的 SELECT 语句,以及包括联合和文本 SELECT 表达式在内的语句,不再被视为 FromClause
对象,并且不能直接放置在另一个 SELECT 语句的 FROM 子句中,除非它们首先包装在 Subquery
中。 这是 Core 中的一个重大概念性更改,完整的理由在 SELECT 语句不再被隐式视为 FROM 子句 中讨论。
给定一个示例,如
m = MetaData()
t = Table("t", m, Column("a", Integer), Column("b", Integer), Column("c", Integer))
stmt = select(t)
在上面,stmt
表示一个 SELECT 语句。 当我们想直接将 stmt
用作另一个 SELECT 中的 FROM 子句时,例如,如果我们尝试从中选择,则会产生错误
new_stmt_1 = select(stmt)
或者如果我们想在 FROM 子句中使用它,例如在 JOIN 中
new_stmt_2 = select(some_table).select_from(some_table.join(stmt))
在以前版本的 SQLAlchemy 中,在另一个 SELECT 中使用 SELECT 会生成带括号的未命名子查询。 在大多数情况下,这种形式的 SQL 不是很有用,因为像 MySQL 和 PostgreSQL 这样的数据库要求 FROM 子句中的子查询具有命名别名,这意味着使用 SelectBase.alias()
方法,或者从 1.4 开始使用 SelectBase.subquery()
方法来生成它。 在其他数据库上,子查询有一个名称来解决将来对子查询内部列名称的任何歧义仍然更清晰。
除了上述实际原因之外,还有许多其他面向 SQLAlchemy 的原因导致进行此更改。 因此,上述两个语句的正确形式要求使用 SelectBase.subquery()
subq = stmt.subquery()
new_stmt_1 = select(subq)
new_stmt_2 = select(some_table).select_from(some_table.join(subq))
正在为原始 clauseelement 自动生成别名¶
1.4.26 版本新增。
此弃用警告指的是一个非常古老且可能不为人所知的模式,该模式适用于旧版 Query.join()
方法以及 2.0 风格 Select.join()
方法,其中可以根据 relationship()
声明 join,但目标是映射类的 Table
或其他 Core selectable 对象,而不是 ORM 实体(例如映射类或 aliased()
构造)
a1 = Address.__table__
q = (
s.query(User)
.join(a1, User.addresses)
.filter(Address.email_address == "ed@foo.com")
.all()
)
上述模式还允许任意 selectable 对象,例如 Core Join
或 Alias
对象,但是,没有对此元素进行自动调整,这意味着需要直接引用 Core 元素
a1 = Address.__table__.alias()
q = (
s.query(User)
.join(a1, User.addresses)
.filter(a1.c.email_address == "ed@foo.com")
.all()
)
指定 join 目标的正确方法始终是使用映射类本身或 aliased
对象,在后一种情况下,使用 PropComparator.of_type()
修饰符来设置别名
# normal join to relationship entity
q = s.query(User).join(User.addresses).filter(Address.email_address == "ed@foo.com")
# name Address target explicitly, not necessary but legal
q = (
s.query(User)
.join(Address, User.addresses)
.filter(Address.email_address == "ed@foo.com")
)
Join 到别名
from sqlalchemy.orm import aliased
a1 = aliased(Address)
# of_type() form; recommended
q = (
s.query(User)
.join(User.addresses.of_type(a1))
.filter(a1.email_address == "ed@foo.com")
)
# target, onclause form
q = s.query(User).join(a1, User.addresses).filter(a1.email_address == "ed@foo.com")
由于表重叠,正在自动生成别名¶
1.4.26 版本新增。
当使用 Select.join()
方法或旧版 Query.join()
方法查询涉及连接表继承的映射时,通常会生成此警告。 问题在于,当在两个共享公共基表的连接继承模型之间进行 join 时,如果不将别名应用于一侧或另一侧,则无法在两个实体之间形成正确的 SQL JOIN; SQLAlchemy 将别名应用于 join 的右侧。 例如,给定一个连接继承映射,如
class Employee(Base):
__tablename__ = "employee"
id = Column(Integer, primary_key=True)
manager_id = Column(ForeignKey("manager.id"))
name = Column(String(50))
type = Column(String(50))
reports_to = relationship("Manager", foreign_keys=manager_id)
__mapper_args__ = {
"polymorphic_identity": "employee",
"polymorphic_on": type,
}
class Manager(Employee):
__tablename__ = "manager"
id = Column(Integer, ForeignKey("employee.id"), primary_key=True)
__mapper_args__ = {
"polymorphic_identity": "manager",
"inherit_condition": id == Employee.id,
}
上述映射包括 Employee
和 Manager
类之间的关系。 由于这两个类都使用 “employee” 数据库表,因此从 SQL 的角度来看,这是一个 自引用关系。 如果我们想使用 join 从 Employee
和 Manager
模型中查询,则在 SQL 级别,“employee” 表需要在查询中包含两次,这意味着它必须被别名化。 当我们使用 SQLAlchemy ORM 创建这样的 join 时,我们得到的 SQL 如下所示
>>> stmt = select(Employee, Manager).join(Employee.reports_to)
>>> print(stmt)
SELECT employee.id, employee.manager_id, employee.name,
employee.type, manager_1.id AS id_1, employee_1.id AS id_2,
employee_1.manager_id AS manager_id_1, employee_1.name AS name_1,
employee_1.type AS type_1
FROM employee JOIN
(employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id)
ON manager_1.id = employee.manager_id
在上面,SQL 从 employee
表中选择,表示查询中的 Employee
实体。 然后,它 join 到 employee AS employee_1 JOIN manager AS manager_1
的右嵌套 join,其中 employee
表再次声明,除了作为匿名别名 employee_1
。 这就是警告消息所指的“自动生成别名”。
当 SQLAlchemy 加载每个都包含 Employee
和 Manager
对象的 ORM 行时,ORM 必须将上面 employee_1
和 manager_1
表别名中的行调整为未别名的 Manager
类的行。 此过程在内部很复杂,并且不适用于所有 API 功能,尤其是在尝试将预先加载功能(例如 contains_eager()
)与比此处显示的更深层嵌套的查询一起使用时。 由于该模式对于更复杂的场景不可靠,并且涉及难以预测和遵循的隐式决策,因此会发出警告,并且此模式可能被视为旧版功能。 编写此查询的更好方法是使用适用于任何其他自引用关系的相同模式,即显式使用 aliased()
构造。 对于连接继承和其他面向 join 的映射,通常希望添加 aliased.flat
参数的使用,这将允许通过将别名应用于 join 中的各个表,而不是将 join 嵌入到新的子查询中,来别名化两个或多个表的 JOIN
>>> from sqlalchemy.orm import aliased
>>> manager_alias = aliased(Manager, flat=True)
>>> stmt = select(Employee, manager_alias).join(Employee.reports_to.of_type(manager_alias))
>>> print(stmt)
SELECT employee.id, employee.manager_id, employee.name,
employee.type, manager_1.id AS id_1, employee_1.id AS id_2,
employee_1.manager_id AS manager_id_1, employee_1.name AS name_1,
employee_1.type AS type_1
FROM employee JOIN
(employee AS employee_1 JOIN manager AS manager_1 ON manager_1.id = employee_1.id)
ON manager_1.id = employee.manager_id
如果我们随后想使用 contains_eager()
来填充 reports_to
属性,我们引用别名
>>> stmt = (
... select(Employee)
... .join(Employee.reports_to.of_type(manager_alias))
... .options(contains_eager(Employee.reports_to.of_type(manager_alias)))
... )
如果不使用显式的 aliased()
对象,在某些更嵌套的情况下,contains_eager()
选项没有足够的上下文来知道从哪里获取其数据,在这种情况下,ORM 在非常嵌套的上下文中“自动别名化”。 因此,最好不要依赖此功能,而是尽可能保持 SQL 构造的显式性。
对象关系映射¶
IllegalStateChangeError 和并发异常¶
SQLAlchemy 2.0 引入了一个新系统,在 检测到非法并发或重入访问时,Session 主动引发异常 中进行了描述,该系统主动检测在 Session
对象的单个实例以及扩展的 AsyncSession
代理对象上调用的并发方法。 这些并发访问调用通常(但不完全)发生在 Session
的单个实例在多个并发线程之间共享而没有同步此类访问时,或者类似地,当 AsyncSession
的单个实例在多个并发任务之间共享时(例如,当使用像 asyncio.gather()
这样的函数时)。 这些使用模式不是这些对象的适当用法,如果没有 SQLAlchemy 实现的主动警告系统,仍然会在对象内部产生无效状态,从而产生难以调试的错误,包括数据库连接本身的驱动程序级错误。
Session
和 AsyncSession
的实例是**可变的、有状态的对象,方法调用没有内置同步**,并且表示在特定 Engine
或 AsyncEngine
的单个数据库连接上一次一个的**单个正在进行的数据库事务**,对象绑定到该引擎(请注意,这些对象都支持一次绑定到多个引擎,但是,在这种情况下,在事务范围内,每个引擎仍然只有一个连接在起作用)。 单个数据库事务不是并发 SQL 命令的适当目标;相反,运行并发数据库操作的应用程序应使用并发事务。 对于这些对象,适当的模式是每个线程一个 Session
,或每个任务一个 AsyncSession
。
有关并发的更多背景信息,请参阅 Session 是线程安全的吗? AsyncSession 在并发任务中共享是否安全? 部分。
父实例 <x> 未绑定到 Session;(延迟加载/延迟加载/刷新/等)操作无法继续¶
这可能是处理 ORM 时最常见的错误消息,它的发生是由于 ORM 广泛使用的一种称为 延迟加载 的技术的性质造成的。 延迟加载是一种常见的对象关系模式,其中由 ORM 持久化的对象维护数据库本身的代理,这样,当访问对象上的各种属性时,可以延迟地从数据库检索它们的值。 这种方法的优点是可以从数据库检索对象,而无需一次加载其所有属性或相关数据,而只能在该时间传递请求的数据。 主要缺点基本上是优点的镜像,即如果要加载许多已知在所有情况下都需要特定数据集的对象,则分批加载这些额外数据是浪费的。
除了通常的效率问题之外,延迟加载的另一个注意事项是,为了使延迟加载继续进行,对象必须**保持与 Session 的关联**,以便能够检索其状态。 此错误消息意味着对象已与其 Session
解除关联,并且正在被要求从数据库延迟加载数据。
对象与其 Session
分离的最常见原因是会话本身已关闭,通常是通过 Session.close()
方法。 然后,这些对象将继续存在以供进一步访问,通常在 Web 应用程序中,它们被传递到服务器端模板引擎并被要求提供它们无法加载的更多属性。
缓解此错误的方法是通过以下技术
**尽量不要有分离的对象;不要过早关闭会话** - 通常,应用程序会在将相关对象传递到其他系统之前关闭事务,然后由于此错误而失败。 有时事务不需要这么快关闭;一个例子是 Web 应用程序在视图呈现之前关闭事务。 这通常以 “正确性” 的名义完成,但可能被视为对 “封装” 的误用,因为此术语指的是代码组织,而不是实际操作。 使用 ORM 对象的模板正在使用 代理模式,该模式使数据库逻辑与调用者隔离。 如果
Session
可以保持打开状态直到对象的生命周期结束,这是最好的方法。**否则,预先加载所有需要的** - 通常不可能保持事务打开,尤其是在更复杂的应用程序中,这些应用程序需要将对象传递到即使它们在同一进程中也无法在同一上下文中运行的其他系统。 在这种情况下,应用程序应准备好处理 分离的 对象,并应尝试适当使用 预先加载 以确保对象预先拥有他们需要的东西。
**重要的是,将 expire_on_commit 设置为 False** - 当使用分离的对象时,对象需要重新加载数据的最常见原因是它们因上次调用
Session.commit()
而过期。 当处理分离的对象时不应使用此过期;因此,应将Session.expire_on_commit
参数设置为False
。 通过防止对象在事务外部过期,加载的数据将保持存在,并且在访问该数据时不会产生额外的延迟加载。另请注意,
Session.rollback()
方法无条件地使Session
中的所有内容过期,并且在非错误情况下也应避免使用。
此 Session 的事务由于先前在 flush 期间发生的异常而回滚¶
Session
的 flush 过程(在 Flushing 中描述)将在遇到错误时回滚数据库事务,以保持内部一致性。 但是,一旦发生这种情况,会话的事务现在变为 “非活动” 状态,并且必须由调用应用程序显式回滚,就像在没有发生故障的情况下,它需要显式提交一样。
这是使用 ORM 时的常见错误,通常适用于其 Session
操作周围尚未具有正确 “框架” 的应用程序。 更多详细信息在 “此 Session 的事务由于先前在 flush 期间发生的异常而回滚。”(或类似情况) 中的 FAQ 中进行了描述。
对于关系 <relationship>,“delete-orphan” 级联通常仅在一对多关系的 “一” 侧配置,而不是在多对一或多对多关系的 “多” 侧配置。¶
当在多对一或多对多关系上设置 “delete-orphan” 级联 时,会出现此错误,例如
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
bs = relationship("B", back_populates="a")
class B(Base):
__tablename__ = "b"
id = Column(Integer, primary_key=True)
a_id = Column(ForeignKey("a.id"))
# this will emit the error message when the mapper
# configuration step occurs
a = relationship("A", back_populates="bs", cascade="all, delete-orphan")
configure_mappers()
如上所示,在 B.a
上的 “delete-orphan” 设置表明,当每个引用特定 A
对象的 B
对象都被删除时,A
对象也应该被删除。也就是说,它表达了被删除的“孤儿”将是一个 A
对象,并且当每个引用它的 B
对象都被删除时,它就变成了“孤儿”。
“delete-orphan” 级联模型不支持此功能。“孤儿”的考虑仅在删除单个对象的上下文中进行,该单个对象将引用零个或多个对象,这些对象现在因这单个删除而成为“孤儿”,这将导致这些对象也被删除。换句话说,它仅设计用于跟踪基于每个孤儿删除一个且仅一个“父”对象而创建的“孤儿”,这在“一对多”关系中是自然情况,其中删除“一”侧的对象会导致随后删除“多”侧的相关项。
上述为支持此功能的映射将级联设置放在一对多侧,如下所示
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
bs = relationship("B", back_populates="a", cascade="all, delete-orphan")
class B(Base):
__tablename__ = "b"
id = Column(Integer, primary_key=True)
a_id = Column(ForeignKey("a.id"))
a = relationship("A", back_populates="bs")
这里表达的意图是,当 A
被删除时,所有它引用的 B
对象也会被删除。
然后,错误消息继续建议使用 relationship.single_parent
标志。此标志可用于强制关系(该关系能够让多个对象引用特定对象)实际上每次只有一个对象引用它。它用于遗留或不太理想的数据库模式,其中外键关系表明是“多”集合,但实际上每次只有一个对象会引用给定的目标对象。这种不常见的情况可以用上面的例子来说明,如下所示
class A(Base):
__tablename__ = "a"
id = Column(Integer, primary_key=True)
bs = relationship("B", back_populates="a")
class B(Base):
__tablename__ = "b"
id = Column(Integer, primary_key=True)
a_id = Column(ForeignKey("a.id"))
a = relationship(
"A",
back_populates="bs",
single_parent=True,
cascade="all, delete-orphan",
)
上述配置将安装一个验证器,该验证器将强制在 B.a
关系的范围内,每次只能有一个 B
与一个 A
关联
>>> b1 = B()
>>> b2 = B()
>>> a1 = A()
>>> b1.a = a1
>>> b2.a = a1
sqlalchemy.exc.InvalidRequestError: Instance <A at 0x7eff44359350> is
already associated with an instance of <class '__main__.B'> via its
B.a attribute, and is only allowed a single parent.
请注意,此验证器的范围有限,不会阻止通过另一个方向创建多个“父”对象。例如,它不会检测到 A.bs
方面的相同设置
>>> a1.bs = [b1, b2]
>>> session.add_all([a1, b1, b2])
>>> session.commit()
INSERT INTO a DEFAULT VALUES
()
INSERT INTO b (a_id) VALUES (?)
(1,)
INSERT INTO b (a_id) VALUES (?)
(1,)
但是,事情后来的发展将不如预期,因为 “delete-orphan” 级联将继续以单个引导对象的形式工作,这意味着如果我们删除任一 B
对象,则 A
对象将被删除。另一个 B
对象会保留下来,ORM 通常会足够智能地将外键属性设置为 NULL,但这通常不是期望的结果
>>> session.delete(b1)
>>> session.commit()
UPDATE b SET a_id=? WHERE b.id = ?
(None, 2)
DELETE FROM b WHERE b.id = ?
(1,)
DELETE FROM a WHERE a.id = ?
(1,)
COMMIT
对于以上所有示例,类似的逻辑适用于多对多关系的计算;如果多对多关系在一侧设置了 single_parent=True,则该侧可以使用 “delete-orphan” 级联,但这很可能不是人们真正想要的,因为多对多关系的重点在于可以有多个对象在任一方向上引用一个对象。
总的来说,“delete-orphan” 级联通常应用于一对多关系的“一”侧,以便它删除“多”侧的对象,而不是反过来。
在版本 1.3.18 中更改:当在多对一或多对多关系中使用时,“delete-orphan” 错误消息的文本已更新,更具描述性。
实例 <instance> 已经通过其 <attribute> 属性与 <instance> 的实例关联,并且仅允许单个父对象。¶
当使用 relationship.single_parent
标志,并且一次将多个对象分配为对象的“父”对象时,会发出此错误。
给定以下映射
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 = relationship(
"A",
single_parent=True,
cascade="all, delete-orphan",
)
意图表明,一次只能有一个 B
对象引用特定的 A
对象
>>> b1 = B()
>>> b2 = B()
>>> a1 = A()
>>> b1.a = a1
>>> b2.a = a1
sqlalchemy.exc.InvalidRequestError: Instance <A at 0x7eff44359350> is
already associated with an instance of <class '__main__.B'> via its
B.a attribute, and is only allowed a single parent.
当意外发生此错误时,通常是因为 relationship.single_parent
标志是响应 For relationship <relationship>, delete-orphan cascade is normally configured only on the “one” side of a one-to-many relationship, and not on the “many” side of a many-to-one or many-to-many relationship. 中描述的错误消息而应用的,并且问题实际上是对 “delete-orphan” 级联设置的误解。有关详细信息,请参阅该消息。
关系 X 将把列 Q 复制到列 P,这与关系 ‘Y’ 冲突¶
此警告指的是当两个或多个关系在刷新时将数据写入同一列,但 ORM 没有任何方法来协调这些关系的情况。根据具体情况,解决方案可能是两个关系需要使用 relationship.back_populates
相互引用,或者一个或多个关系应配置为 relationship.viewonly
以防止冲突写入,或者有时配置是完全有意的,应配置 relationship.overlaps
以消除每个警告。
对于缺少 relationship.back_populates
的典型示例,给定以下映射
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("Child")
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
parent_id = Column(ForeignKey("parent.id"))
parent = relationship("Parent")
上面的映射将生成警告
SAWarning: relationship 'Child.parent' will copy column parent.id to column child.parent_id,
which conflicts with relationship(s): 'Parent.children' (copies parent.id to child.parent_id).
关系 Child.parent
和 Parent.children
似乎存在冲突。解决方案是应用 relationship.back_populates
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
children = relationship("Child", back_populates="parent")
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
parent_id = Column(ForeignKey("parent.id"))
parent = relationship("Parent", back_populates="children")
对于更自定义的关系,其中 “重叠” 情况可能是故意的且无法解决,relationship.overlaps
参数可以指定不应生效警告的关系名称。这通常发生在与同一底层表的两个或多个关系中,这些关系包括自定义的 relationship.primaryjoin
条件,这些条件限制了每种情况下的相关项
class Parent(Base):
__tablename__ = "parent"
id = Column(Integer, primary_key=True)
c1 = relationship(
"Child",
primaryjoin="and_(Parent.id == Child.parent_id, Child.flag == 0)",
backref="parent",
overlaps="c2, parent",
)
c2 = relationship(
"Child",
primaryjoin="and_(Parent.id == Child.parent_id, Child.flag == 1)",
overlaps="c1, parent",
)
class Child(Base):
__tablename__ = "child"
id = Column(Integer, primary_key=True)
parent_id = Column(ForeignKey("parent.id"))
flag = Column(Integer)
如上所示,ORM 将知道 Parent.c1
、Parent.c2
和 Child.parent
之间的重叠是故意的。
对象无法转换为 ‘persistent’ 状态,因为此身份映射不再有效。¶
1.4.26 版本新增。
添加此消息是为了适应以下情况:在原始 Session
已关闭或调用了其 Session.expunge_all()
方法之后,迭代了将产生 ORM 对象的 Result
对象。当 Session
一次性清除所有对象时,该 Session
使用的内部 identity map 将被新的替换,而原始的将被丢弃。未消耗且未缓冲的 Result
对象将在内部维护对该现在已丢弃的身份映射的引用。因此,当 Result
被消耗时,将产生的对象无法与该 Session
关联。此安排是按设计进行的,因为通常不建议在创建未缓冲的 Result
对象的事务上下文之外对其进行迭代
# context manager creates new Session
with Session(engine) as session_obj:
result = sess.execute(select(User).where(User.id == 7))
# context manager is closed, so session_obj above is closed, identity
# map is replaced
# iterating the result object can't associate the object with the
# Session, raises this error.
user = result.first()
当使用 asyncio
ORM 扩展时,通常不会发生上述情况,因为当 AsyncSession
返回同步样式的 Result
时,结果在语句执行时已被预先缓冲。这是为了允许辅助的迫切加载器在不需要额外的 await
调用的情况下调用。
为了在上述情况下使用常规 Session
预先缓冲结果,就像 asyncio
扩展所做的那样,可以使用 prebuffer_rows
执行选项,如下所示
# context manager creates new Session
with Session(engine) as session_obj:
# result internally pre-fetches all objects
result = sess.execute(
select(User).where(User.id == 7), execution_options={"prebuffer_rows": True}
)
# context manager is closed, so session_obj above is closed, identity
# map is replaced
# pre-buffered objects are returned
user = result.first()
# however they are detached from the session, which has been closed
assert inspect(user).detached
assert inspect(user).session is None
如上所示,所选的 ORM 对象在 session_obj
块内完全生成,与 session_obj
关联,并缓冲在 Result
对象中以进行迭代。在块外部,session_obj
被关闭并清除这些 ORM 对象。迭代 Result
对象将产生这些 ORM 对象,但是由于它们的原始 Session
已清除它们,它们将以 detached 状态交付。
注意
上面对 “预缓冲” 与 “未缓冲” Result
对象的引用指的是 ORM 将来自 DBAPI 的传入原始数据库行转换为 ORM 对象的过程。它并不意味着表示来自 DBAPI 的待处理结果的底层 cursor
对象本身是否被缓冲或未缓冲,因为这本质上是较低层的缓冲。有关 cursor
结果本身缓冲的背景信息,请参阅 Using Server Side Cursors (a.k.a. stream results) 部分。
无法为 Annotated Declarative Table 形式解释类型注释¶
SQLAlchemy 2.0 引入了一个新的 Annotated Declarative Table 声明式系统,该系统从运行时类定义中的 PEP 484 注释派生 ORM 映射属性信息。这种形式的要求是,所有 ORM 注释都必须使用名为 Mapped
的通用容器才能正确注释。遗留的 SQLAlchemy 映射(包括显式的 PEP 484 类型注释,例如那些使用 legacy Mypy extension 进行类型支持的映射)可能包括诸如 relationship()
之类的指令,这些指令不包括此通用容器。
要解决此问题,可以在类完全迁移到 2.0 语法之前,用 __allow_unmapped__
布尔属性标记这些类。有关示例,请参阅 Migration to 2.0 Step Six - Add __allow_unmapped__ to explicitly typed ORM models 中的迁移说明。
当将 <cls> 转换为 dataclass 时,属性源自不是 dataclass 的超类 <cls>。¶
当结合任何未声明为 dataclass 的 mixin 类或抽象基类使用 Declarative Dataclass Mapping 中描述的 SQLAlchemy ORM Mapped Dataclasses 功能时,会发生此警告,如下例所示
from __future__ import annotations
import inspect
from typing import Optional
from uuid import uuid4
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import MappedAsDataclass
class Mixin:
create_user: Mapped[int] = mapped_column()
update_user: Mapped[Optional[int]] = mapped_column(default=None, init=False)
class Base(DeclarativeBase, MappedAsDataclass):
pass
class User(Base, Mixin):
__tablename__ = "sys_user"
uid: Mapped[str] = mapped_column(
String(50), init=False, default_factory=uuid4, primary_key=True
)
username: Mapped[str] = mapped_column()
email: Mapped[str] = mapped_column()
如上所示,由于 Mixin
本身不从 MappedAsDataclass
扩展,因此会生成以下警告
SADeprecationWarning: When transforming <class '__main__.User'> to a
dataclass, attribute(s) "create_user", "update_user" originates from
superclass <class
'__main__.Mixin'>, which is not a dataclass. This usage is deprecated and
will raise an error in SQLAlchemy 2.1. When declaring SQLAlchemy
Declarative Dataclasses, ensure that all mixin classes and other
superclasses which include attributes are also a subclass of
MappedAsDataclass.
修复方法是将 MappedAsDataclass
添加到 Mixin
的签名中
class Mixin(MappedAsDataclass):
create_user: Mapped[int] = mapped_column()
update_user: Mapped[Optional[int]] = mapped_column(default=None, init=False)
Python 的 PEP 681 规范不适用于在不是 dataclass 的 dataclass 超类上声明的属性;根据 Python dataclass 的行为,此类字段将被忽略,如下例所示
from dataclasses import dataclass
from dataclasses import field
import inspect
from typing import Optional
from uuid import uuid4
class Mixin:
create_user: int
update_user: Optional[int] = field(default=None)
@dataclass
class User(Mixin):
uid: str = field(init=False, default_factory=lambda: str(uuid4()))
username: str
password: str
email: str
如上所示,User
类不会在其构造函数中包含 create_user
,也不会尝试将 update_user
解释为 dataclass 属性。这是因为 Mixin
不是 dataclass。
SQLAlchemy 的 2.0 系列中的 dataclass 功能无法正确遵守此行为;相反,非 dataclass mixin 和超类上的属性被视为最终 dataclass 配置的一部分。但是,Pyright 和 Mypy 等类型检查器不会将这些字段视为 dataclass 构造函数的一部分,因为根据 PEP 681,这些字段将被忽略。由于它们的出现方式在其他方面是模棱两可的,因此 SQLAlchemy 2.1 将要求在 dataclass 层次结构中具有 SQLAlchemy 映射属性的 mixin 类本身必须是 dataclass。
为 <classname> 创建 dataclass 时遇到 Python dataclass 错误¶
当使用 MappedAsDataclass
mixin 类或 registry.mapped_as_dataclass()
装饰器时,SQLAlchemy 会使用 Python 标准库中的实际 Python dataclasses 模块,以便将 dataclass 行为应用于目标类。此 API 有其自身的错误情况,其中大多数涉及在用户定义的类上构造 __init__()
方法;在类上声明的属性顺序以及 在超类上 声明的属性顺序决定了 __init__()
方法的构造方式,并且在属性的组织方式以及它们应如何使用诸如 init=False
、kw_only=True
等参数方面存在特定规则。SQLAlchemy 不控制或实施这些规则。因此,对于此类错误,请查阅 Python dataclasses 文档,并特别注意应用于 继承 的规则。
另请参阅
Declarative Dataclass Mapping - SQLAlchemy dataclass 文档
Python dataclasses - 在 python.org 网站上
inheritance - 在 python.org 网站上
按主键逐行 ORM 批量更新要求记录包含主键值¶
当使用 ORM Bulk UPDATE by Primary Key 功能,但在给定记录中未提供主键值时,会发生此错误,例如
>>> session.execute(
... update(User).where(User.name == bindparam("u_name")),
... [
... {"u_name": "spongebob", "fullname": "Spongebob Squarepants"},
... {"u_name": "patrick", "fullname": "Patrick Star"},
... ],
... )
如上所示,参数字典列表的存在以及使用 Session
执行启用 ORM 的 UPDATE 语句将自动使用按主键 ORM 批量更新,这要求参数字典包含主键值,例如
>>> session.execute(
... update(User),
... [
... {"id": 1, "fullname": "Spongebob Squarepants"},
... {"id": 3, "fullname": "Patrick Star"},
... {"id": 5, "fullname": "Eugene H. Krabs"},
... ],
... )
要在不提供每条记录主键值的情况下调用 UPDATE 语句,请使用 Session.connection()
获取当前的 Connection
,然后使用它调用
>>> session.connection().execute(
... update(User).where(User.name == bindparam("u_name")),
... [
... {"u_name": "spongebob", "fullname": "Spongebob Squarepants"},
... {"u_name": "patrick", "fullname": "Patrick Star"},
... ],
... )
AsyncIO 异常¶
AwaitRequired¶
SQLAlchemy 异步模式需要使用异步驱动程序来连接数据库。当尝试将 SQLAlchemy 的异步版本与不兼容的 DBAPI 一起使用时,通常会引发此错误。
另请参阅
MissingGreenlet¶
对异步 DBAPI 的调用是在通常由 SQLAlchemy AsyncIO 代理类设置的 greenlet 生成上下文之外发起的。通常,当在意外的位置尝试 IO,使用不直接提供使用 await
关键字的调用模式时,会发生此错误。当使用 ORM 时,这几乎总是由于使用 lazy loading 引起的,在 asyncio 下,如果不采取其他步骤和/或备用加载器模式,则无法直接支持 lazy loading 以便成功使用。
另请参阅
Preventing Implicit IO when Using AsyncSession - 涵盖了大多数可能发生此问题的 ORM 场景以及如何缓解,包括在延迟加载场景中使用的特定模式。
没有可用的检查¶
目前不支持直接在 AsyncConnection
或 AsyncEngine
对象上使用 inspect()
函数,因为尚没有可用的 Inspector
对象的 awaitable 形式。相反,通过以某种方式使用 inspect()
函数获取对象来使用该对象,使其引用 AsyncConnection
对象的底层 AsyncConnection.sync_connection
属性;然后,通过使用 AsyncConnection.run_sync()
方法以及执行所需操作的自定义函数,以 “同步” 调用样式使用 Inspector
。
async def async_main():
async with engine.connect() as conn:
tables = await conn.run_sync(
lambda sync_conn: inspect(sync_conn).get_table_names()
)
另请参阅
Using the Inspector to inspect schema objects - 使用 inspect()
和 asyncio 扩展的更多示例。
Core 异常类¶
有关 Core 异常类,请参阅 Core Exceptions。
ORM 异常类¶
有关 ORM 异常类,请参阅 ORM Exceptions。
遗留异常¶
本节中的异常不是由当前 SQLAlchemy 版本生成的,但在此处提供是为了适应异常消息超链接。
SQLAlchemy 2.0 中的 <some function> 将不再 <something>¶
SQLAlchemy 2.0 代表了 Core 和 ORM 组件中各种关键 SQLAlchemy 用法模式的重大转变。2.0 版本的目的是对 SQLAlchemy 自早期以来最基本的一些假设进行轻微调整,并交付一种新的精简用法模型,希望该模型在 Core 和 ORM 组件之间更加简约和一致,并且功能更强大。
在 SQLAlchemy 2.0 - Major Migration Guide 中介绍的 SQLAlchemy 2.0 项目包括一个全面的未来兼容性系统,该系统已集成到 SQLAlchemy 的 1.4 系列中,以便应用程序具有清晰、明确且增量的升级路径,从而将应用程序迁移为完全 2.0 兼容。RemovedIn20Warning
弃用警告是此系统的基础,旨在提供有关现有代码库中哪些行为需要修改的指导。有关如何启用此警告的概述,请参阅 SQLAlchemy 2.0 Deprecations Mode。
另请参阅
SQLAlchemy 2.0 - Major Migration Guide - 从 1.x 系列升级过程的概述,以及 SQLAlchemy 2.0 的当前目标和进展。
SQLAlchemy 2.0 Deprecations Mode - 关于如何在 SQLAlchemy 1.4 中使用 “2.0 弃用模式” 的具体指南。
对象正在沿着反向引用级联合并到 Session 中¶
这条消息指的是 SQLAlchemy 的 “backref cascade”(反向引用级联)行为,该行为已在 2.0 版本中移除。它指的是由于另一个已存在于该会话中的对象与之关联,导致一个对象被添加到 Session
中。由于此行为已被证明弊大于利,因此添加了 relationship.cascade_backrefs
和 backref.cascade_backrefs
参数,可以将它们设置为 False
以禁用它。在 SQLAlchemy 2.0 中,“cascade backrefs” 行为已完全移除。
对于旧版本的 SQLAlchemy,要在当前使用 relationship.backref
字符串参数配置的反向引用上将 relationship.cascade_backrefs
设置为 False
,则必须首先使用 backref()
函数声明反向引用,以便可以传递 backref.cascade_backrefs
参数。
或者,可以通过在 “future”(未来)模式下使用 Session
,即为 Session.future
参数传递 True
,从而完全关闭整个 “cascade backrefs” 行为。
另请参阅
cascade_backrefs 行为已弃用,将在 2.0 版本中移除 - 关于 SQLAlchemy 2.0 变更的背景信息。
在 “legacy”(旧式)模式下创建的 select() 构造;关键字参数等。¶
select()
构造已在 SQLAlchemy 1.4 中更新,以支持 SQLAlchemy 2.0 中更标准的新调用风格。为了在 1.4 系列中实现向后兼容性,该构造接受 “legacy” 风格和 “new”(新)风格的参数。
“new” 风格的特点是,列和表表达式仅以位置参数的形式传递给 select()
构造;对对象的任何其他修饰符都必须使用后续的方法链式调用传递
# this is the way to do it going forward
stmt = select(table1.c.myid).where(table1.c.myid == table2.c.otherid)
为了进行比较,在 SQLAlchemy 的旧版本中,在甚至添加 Select.where()
等方法之前,select()
的 “legacy” 形式看起来像
# this is how it was documented in original SQLAlchemy versions
# many years ago
stmt = select([table1.c.myid], whereclause=table1.c.myid == table2.c.otherid)
甚至 “whereclause” 也会以位置参数的形式传递
# this is also how it was documented in original SQLAlchemy versions
# many years ago
stmt = select([table1.c.myid], table1.c.myid == table2.c.otherid)
多年以来,接受的其他 “whereclause” 和其他参数已从大多数叙述性文档中移除,从而导致一种调用风格,这种风格最常见的是作为列表传递的列参数列表,但没有其他参数
# this is how it's been documented since around version 1.0 or so
stmt = select([table1.c.myid]).where(table1.c.myid == table2.c.otherid)
文档 select() 不再接受各种构造函数参数,列按位置传递 从 2.0 迁移 的角度描述了此更改。
通过旧式绑定元数据找到了绑定,但由于在此 Session 上设置了 future=True,因此忽略了此绑定。¶
“绑定元数据” 的概念在 SQLAlchemy 1.4 版本之前一直存在;在 SQLAlchemy 2.0 中,它已被移除。
此错误指的是 MetaData.bind
对象上的 MetaData
参数,该参数允许诸如 ORM Session
之类的对象将特定的映射类与 Engine
关联。在 SQLAlchemy 2.0 中,Session
必须直接链接到每个 Engine
。也就是说,不再是不带任何参数实例化 Session
或 sessionmaker
,并将 Engine
与 MetaData
关联
engine = create_engine("sqlite://")
Session = sessionmaker()
metadata_obj = MetaData(bind=engine)
Base = declarative_base(metadata=metadata_obj)
class MyClass(Base): ...
session = Session()
session.add(MyClass())
session.commit()
Engine
必须改为直接与 sessionmaker
或 Session
关联。MetaData
对象应不再与任何引擎关联
engine = create_engine("sqlite://")
Session = sessionmaker(engine)
Base = declarative_base()
class MyClass(Base): ...
session = Session()
session.add(MyClass())
session.commit()
在 SQLAlchemy 1.4 中,当在 sessionmaker
或 Session
上设置 Session.future
标志时,将启用此 2.0 风格 行为。
此 Compiled 对象未绑定到任何 Engine 或 Connection¶
此错误指的是 “绑定元数据” 的概念,这是仅在 1.x 版本中存在的旧式 SQLAlchemy 模式。当直接从与任何 Engine
无关的 Core 表达式对象调用 Executable.execute()
方法时,会发生此问题
metadata_obj = MetaData()
table = Table("t", metadata_obj, Column("q", Integer))
stmt = select(table)
result = stmt.execute() # <--- raises
engine = create_engine("mysql+pymysql://user:pass@host/db")
metadata_obj = MetaData(bind=engine)
在上面的代码中,任何派生自 Table
的语句,而 Table
又派生自该 MetaData
,都将隐式使用给定的 Engine
以调用该语句。
请注意,“绑定元数据” 的概念在 SQLAlchemy 2.0 中不存在。调用语句的正确方法是通过 Connection.execute()
的 Connection
方法
with engine.connect() as conn:
result = conn.execute(stmt)
当使用 ORM 时,可以通过 Session
获得类似的功能
result = session.execute(stmt)
另请参阅
此连接处于非活动事务中。请在继续之前完全 rollback()。¶
此错误情况是在 SQLAlchemy 1.4 版本中添加的,不适用于 SQLAlchemy 2.0。此错误指的是 Connection
使用诸如 Connection.begin()
之类的方法置于事务中,然后在该范围内创建进一步的 “marker”(标记)事务的状态;然后使用 Transaction.rollback()
回滚或使用 Transaction.close()
关闭 “marker” 事务,但是外部事务仍处于 “inactive”(非活动)状态,必须回滚。
该模式看起来像
engine = create_engine(...)
connection = engine.connect()
transaction1 = connection.begin()
# this is a "sub" or "marker" transaction, a logical nesting
# structure based on "real" transaction transaction1
transaction2 = connection.begin()
transaction2.rollback()
# transaction1 is still present and needs explicit rollback,
# so this will raise
connection.execute(text("select 1"))
在上面,transaction2
是一个 “marker” 事务,它指示外部事务中的逻辑事务嵌套;虽然内部事务可以通过其 rollback() 方法回滚整个事务,但其 commit() 方法除了关闭 “marker” 事务本身的范围外,没有任何效果。transaction2.rollback()
的调用具有停用 transaction1 的效果,这意味着它基本上在数据库级别回滚,但仍然存在,以便适应事务的一致嵌套模式。
正确的解决方法是确保外部事务也被回滚
transaction1.rollback()
此模式在 Core 中不常用。在 ORM 中,可能会发生类似的问题,这是 ORM 的 “logical”(逻辑)事务结构的产物;这在 FAQ 条目 “由于之前在 flush 期间发生异常,此 Session 的事务已被回滚。”(或类似情况) 中进行了描述。
“subtransaction”(子事务)模式在 SQLAlchemy 2.0 中被移除,因此这种特定的编程模式将不再可用,从而防止出现此错误消息。
flambé! 龙和 The Alchemist 图像设计由 Rotem Yaari 创作并慷慨捐赠。
使用 Sphinx 7.2.6 创建。文档最后生成时间:Tue 11 Mar 2025 02:40:17 PM EDT