错误消息

本节列出了 SQLAlchemy 抛出或发出的常见错误消息和警告的描述和背景信息。

SQLAlchemy 通常在 SQLAlchemy 特定异常类的上下文中抛出错误。有关这些类的详细信息,请参见 核心异常ORM 异常

SQLAlchemy 错误大致可以分为两类,即编程时错误运行时错误。编程时错误是在函数或方法使用不正确的参数调用时,或在其他配置相关的操作(例如无法解析的映射器配置)时抛出。编程时错误通常是立即发生的和确定性的。另一方面,运行时错误表示程序运行时发生的故障,以响应任意发生的某些条件,例如数据库连接耗尽或发生某些与数据相关的错误。运行时错误更有可能在运行应用程序的日志中看到,因为程序在响应遇到的负载和数据时会遇到这些状态。

由于运行时错误不像编程时错误那样容易重现,并且通常在程序运行时以响应某些任意条件而发生,因此它们更难调试,也会影响已经投入生产的程序。

在本节中,目标是尝试提供一些最常见的运行时错误以及编程时错误的背景信息。

连接和事务

QueuePool 大小限制 <x> 溢出 <y> 已达到,连接超时,超时 <z>

这可能是最常见的运行时错误,因为它直接涉及应用程序的工作负载超过配置的限制,而这种限制通常适用于几乎所有 SQLAlchemy 应用程序。

以下几点总结了此错误的含义,从大多数 SQLAlchemy 用户应该已经熟悉的最基本要点开始。

  • SQLAlchemy Engine 对象默认使用连接池 - 这意味着当您使用 Engine 对象的 SQL 数据库连接资源,然后 释放 该资源时,数据库连接本身将保持连接到数据库,并返回到一个内部队列,在那里可以再次使用它。即使代码似乎正在结束与数据库的对话,但在许多情况下,应用程序仍然会维护一定数量的数据库连接,这些连接会持续到应用程序结束或显式处置连接池。

  • 由于连接池的存在,当应用程序使用 SQL 数据库连接时,通常是通过使用 Engine.connect() 或使用 ORM Session 进行查询时,此活动不一定会在获取连接对象时建立与数据库的新连接;它会改为查询连接池以获取连接,这通常会从池中检索现有的连接以供重用。如果池中没有可用连接,则连接池将创建一个新的数据库连接,但前提是连接池尚未超过配置的容量。

  • 大多数情况下使用的默认连接池称为 QueuePool。当您要求此连接池提供连接,而池中没有可用连接时,它将创建一个新的连接,前提是正在使用的连接总数少于配置的值。此值等于池大小加上最大溢出量。这意味着如果您将引擎配置为

    engine = create_engine("mysql+mysqldb://u:p@host/db", pool_size=10, max_overflow=20)

    上面的 Engine 将允许最多 30 个连接同时使用,不包括已与引擎分离或已失效的连接。如果收到新的连接请求,而 30 个连接已被应用程序的其他部分使用,则连接池将阻塞一段时间,然后超时并抛出此错误消息。

    为了允许一次使用更多连接,可以使用传递给 create_engine() 函数的 create_engine.pool_sizecreate_engine.max_overflow 参数调整连接池。等待连接可用的超时时间使用 create_engine.pool_timeout 参数配置。

  • 可以通过将 create_engine.max_overflow 设置为值“-1”来将连接池配置为无限溢出。使用此设置,连接池将仍然维护固定数量的连接池,但它永远不会在请求新连接时阻塞;它会改为在没有可用连接时无条件地建立新的连接。

    但是,以这种方式运行时,如果应用程序存在使用所有可用连接性资源的问题,它最终会遇到数据库本身配置的可用连接限制,这也会返回错误。更严重的是,当应用程序耗尽数据库的连接时,它通常会在发生故障之前耗尽大量资源,并且还会干扰依赖于能够连接到数据库的其他应用程序和数据库状态机制。

    鉴于上述情况,可以将连接池视为连接使用的安全阀,为防止恶意应用程序导致整个数据库对所有其他应用程序不可用提供了一层关键的保护。当收到此错误消息时,最好通过修复使用过多的连接或适当配置限制来修复问题,而不是允许无限溢出,因为这实际上并不能解决根本问题。

是什么原因导致应用程序使用完所有可用的连接?

  • 应用程序正在处理太多并发请求,无法根据池的配置值执行工作 - 这是最直接的原因。如果你的应用程序在一个允许 30 个并发线程的线程池中运行,每个线程使用一个连接,如果你的池配置不允许一次检出至少 30 个连接,那么当你的应用程序收到足够的并发请求时,就会出现此错误。解决方案是提高池的限制或减少并发线程的数量。

  • 应用程序没有将连接返回池 - 这是另一个最常见的原因,即应用程序正在使用连接池,但程序未能释放这些连接,而是将它们保持打开状态。连接池以及 ORM Session 确实有一些逻辑,当会话和/或连接对象被垃圾回收时,会导致底层连接资源被释放,但是不能依赖这种行为及时释放资源。

    发生这种情况的一个常见原因是应用程序使用 ORM 会话,并且没有在完成涉及该会话的工作后调用 Session.close()。解决方案是确保使用 ORM 时,ORM 会话,或者使用 Core 时,引擎绑定的 Connection 对象,在完成工作时显式关闭,无论是通过适当的 .close() 方法,还是通过使用可用的上下文管理器之一(例如 “with:” 语句)来正确释放资源。

  • 应用程序试图运行长时间运行的事务 - 数据库事务是一种非常昂贵的资源,并且永远不应该闲置等待某个事件发生。如果应用程序正在等待用户按下按钮,或者等待结果从长时间运行的作业队列中出来,或者正在保持与浏览器的持久连接,不要在整个时间内保持数据库事务处于打开状态。当应用程序需要与数据库交互并与事件交互时,在该点打开一个短暂的事务,然后关闭它。

  • 应用程序正在死锁 - 也是导致此错误的一个常见原因,并且更难理解,如果应用程序由于应用程序端或数据库端死锁而无法完成其对连接的使用,应用程序可能会使用完所有可用连接,然后导致其他请求收到此错误。死锁的原因包括

    • 使用隐式异步系统(如 gevent 或 eventlet),而没有正确地为所有套接字库和驱动程序进行猴子补丁,或者在没有完全覆盖所有猴子补丁的驱动程序方法时存在错误,或者当异步系统用于 CPU 密集型工作负载,并且使用数据库资源的格林线程只是等待太长时间来处理它们时,这种情况不太常见。对于绝大多数关系型数据库操作,隐式或显式异步编程框架通常不是必需的,也不适合;如果应用程序必须为某些功能区域使用异步系统,最好是在将消息传递到应用程序的异步部分的传统线程中运行面向数据库的业务方法。

    • 数据库端死锁,例如行相互死锁

    • 线程错误,例如互斥锁处于相互死锁状态,或者在同一线程中调用已经锁定的互斥锁

请记住,使用池的另一种方法是完全关闭池。有关这方面的背景信息,请参阅部分 切换池实现。但是,请注意,当此错误消息出现时,它总是由于应用程序本身的更大问题导致的;池只是帮助更早地发现问题。

池类不能与 asyncio 引擎一起使用(反之亦然)

QueuePool 池类在内部使用 thread.Lock 对象,与 asyncio 不兼容。如果使用 create_async_engine() 函数创建 AsyncEngine,则合适的队列池类是 AsyncAdaptedQueuePool,它会自动使用,不需要显式指定。

除了 AsyncAdaptedQueuePool 之外,NullPoolStaticPool 池类不使用锁,也适合与异步引擎一起使用。

在不太可能的情况下,如果 AsyncAdaptedQueuePool 池类使用 create_engine() 函数显式指定,也会引发此错误。

另请参阅

连接池

无法重新连接,直到无效的事务回滚。请在继续之前完全回滚()

此错误条件是指 Connection 被失效的情况,无论是由于数据库断开连接检测还是由于显式调用 Connection.invalidate(),但仍然存在一个事务,该事务是通过 Connection.begin() 方法显式启动的,或者由于连接在发出任何 SQL 语句时(如 SQLAlchemy 的 2.x 系列中)自动开始事务而导致的。当连接失效时,任何正在进行的 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 表达式语言

对象不会生成缓存键,性能影响

从 1.4 版本开始,SQLAlchemy 包含一个 SQL 编译缓存机制,它允许 Core 和 ORM SQL 结构缓存其字符串化形式,以及用于从语句中获取结果的其他结构信息,从而在下次使用结构上等效的结构时跳过相对昂贵的字符串编译过程。此系统依赖于为所有 SQL 结构实现的功能,包括对象,例如 Columnselect()TypeEngine 对象,以生成一个 **缓存键**,它完全代表其状态,其程度影响 SQL 编译过程。

如果所讨论的警告涉及诸如 Column 对象之类的广泛使用对象,并且证明正在影响大多数正在发出的 SQL 结构(使用在 使用日志估计缓存性能 中描述的估计技术),因此缓存通常不会为应用程序启用,这将对性能产生负面影响,并且在某些情况下实际上会导致 **性能下降** 与之前的 SQLAlchemy 版本相比。为什么我的应用程序在升级到 1.4 和/或 2.x 后变慢了? 在 FAQ 中更详细地介绍了这一点。

如果有任何疑问,缓存会自行禁用

缓存依赖于能够生成一个缓存键,该键能够以 **一致** 的方式准确地表示语句的 **完整结构**。如果特定 SQL 结构(或类型)没有适当的指令来允许它生成适当的缓存键,则无法安全地启用缓存。

  • 缓存键必须表示 **完整结构**:如果使用该结构的两个独立实例可能导致渲染不同的 SQL,则使用不捕获第一个和第二个元素之间明显差异的缓存键对第一个元素实例的 SQL 进行缓存,将导致为第二个实例缓存和渲染不正确的 SQL。

  • 缓存键必须 **一致**:如果某个结构表示每次都会发生变化的状态,例如文字值,为它的每个实例生成唯一的 SQL,那么这个结构也不安全用于缓存,因为该结构的重复使用会很快用唯一的 SQL 字符串填满语句缓存,这些字符串可能不会再次使用,从而削弱了缓存的目的。

出于以上两个原因,SQLAlchemy 的缓存系统在决定缓存与对象相对应的 SQL 时 **非常保守**。

缓存的断言属性

警告是根据以下标准发出的。有关每个标准的更多详细信息,请参阅部分 为什么我的应用程序在升级到 1.4 和/或 2.x 后变慢了?

另请参阅

使用日志估计缓存性能 - 有关观察缓存行为和效率的背景信息

为什么我的应用程序在升级到 1.4 和/或 2.x 后变慢了? - 在 常见问题解答 部分

编译器 StrSQLCompiler 无法渲染类型为 <element type> 的元素

此错误通常发生在尝试将包含不属于默认编译的元素的 SQL 表达式结构转换为字符串时;在这种情况下,错误将针对 StrSQLCompiler 类。在不太常见的情况下,它也可能发生在将错误类型的 SQL 表达式与特定类型的数据库后端一起使用时;在这些情况下,将命名其他类型的 SQL 编译器类,例如 SQLCompilersqlalchemy.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() 方法,传递一个 EngineDialect 对象,该对象将调用正确的编译器。下面我们使用一个 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()))

有关 SQL 元素直接字符串化/编译的更多详细信息,请参阅下面的 FAQ 链接。

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() 来声明一个连接,但目标是映射类所在的 Table 或其他 Core 可选,而不是 ORM 实体,例如映射类或 aliased() 结构。

a1 = Address.__table__

q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(Address.email_address == "[email protected]")
    .all()
)

上面的模式还允许使用任意可选,例如 Core JoinAlias 对象,但是此元素没有自动适配,这意味着需要直接引用 Core 元素。

a1 = Address.__table__.alias()

q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(a1.c.email_address == "[email protected]")
    .all()
)

指定连接目标的正确方法始终是使用映射类本身或 aliased 对象,在后一种情况下使用 PropComparator.of_type() 修饰符来设置别名。

# normal join to relationship entity
q = s.query(User).join(User.addresses).filter(Address.email_address == "[email protected]")

# name Address target explicitly, not necessary but legal
q = (
    s.query(User)
    .join(Address, User.addresses)
    .filter(Address.email_address == "[email protected]")
)

连接到别名

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 == "[email protected]")
)

# target, onclause form
q = s.query(User).join(a1, User.addresses).filter(a1.email_address == "[email protected]")

由于表重叠,正在自动生成别名

版本 1.4.26 中的新增内容。

此警告通常在使用 Select.join() 方法或传统的 Query.join() 方法查询涉及连接表继承的映射时生成。问题是,当连接两个共享公共基表的连接继承模型时,如果没有对其中一侧应用别名,就无法形成两个实体之间的正确 SQL JOIN;SQLAlchemy 对连接的右侧应用了别名。例如,给定以下连接继承映射:

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,
    }

上面的映射包括 EmployeeManager 类之间的关系。由于这两个类都使用“employee”数据库表,从 SQL 的角度来看,这是一个自引用关系。如果我们想使用连接从 EmployeeManager 模型中进行查询,那么在 SQL 级别,需要在查询中两次包含“employee”表,这意味着它必须是别名。当我们使用 SQLAlchemy ORM 创建这样的连接时,我们会得到类似以下 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 实体。然后它连接到 employee AS employee_1 JOIN manager AS manager_1 的右嵌套连接,其中 employee 表再次被声明,但作为一个匿名别名 employee_1。这就是警告消息所指的“自动生成别名”。

当 SQLAlchemy 加载包含 EmployeeManager 对象的 ORM 行时,ORM 必须将来自上述 employee_1manager_1 表别名的行适配到未加别名的 Manager 类。这个过程在内部很复杂,而且无法适应所有 API 功能,尤其是在尝试使用诸如 contains_eager() 的急切加载功能时,而该功能比这里显示的嵌套查询更深。由于这种模式对于更复杂的场景不可靠,并且涉及难以预测和遵循的隐式决策,因此会发出警告,并且这种模式可以被认为是遗留功能。编写此查询的更好方法是使用适用于任何其他自引用关系的相同模式,即显式使用 aliased() 结构。对于连接继承和其他连接导向的映射,通常希望添加 aliased.flat 参数的使用,这将允许通过对连接内的各个表应用别名来对两个或多个表的连接进行别名,而不是将连接嵌入到一个新的子查询中。

>>> 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 实现将仍然会产生对象内的无效状态,从而产生难以调试的错误,包括数据库连接本身的驱动程序级别错误。

SessionAsyncSession 的实例是 **可变的、有状态的对象,没有内置的同步** 方法调用,并且代表 **对单个数据库连接的单个、持续的数据库事务**,一次用于特定 EngineAsyncEngine,该对象绑定到该对象(请注意,这些对象都支持绑定到多个引擎,但在这种情况下,在一个事务的范围内,每个引擎仍然只会有一个连接)。单个数据库事务不是并发 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 的刷新过程中,如 刷新 所述,如果遇到错误,将回滚数据库事务,以保持内部一致性。但是,一旦发生这种情况,会话的事务现在处于“非活动”状态,必须由调用应用程序显式回滚,就像在没有发生故障的情况下,它需要显式提交一样。

这是在使用 ORM 时常见的错误,通常适用于应用程序尚未在其 Session 操作周围建立正确的“框架”。有关详细信息,请参阅 FAQ 中的 “此会话的事务已因先前在刷新过程中发生的异常而回滚。”(或类似内容)

对于关系 <relationship>,通常仅在“一对多”关系的“一”端配置删除孤儿级联,而不是在“多对一”或“多对多”关系的“多”端配置

当在“多对一”或“多对多”关系上设置“删除孤儿”级联 时,会发生此错误,例如

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 上的“删除孤儿”设置表明,当删除所有引用特定 AB 对象时,该 A 也应该被删除。也就是说,它表示被删除的“孤儿”将是 A 对象,并且当所有引用它的 B 被删除时,它就变成了“孤儿”。

“删除孤儿”级联模型不支持此功能。“孤儿”考虑只针对删除单个对象进行,该单个对象随后会引用现在被此单个删除操作“孤立”的零个或多个对象,从而导致这些对象也被删除。换句话说,它旨在仅根据一个且仅一个“父”对象的删除来跟踪“孤儿”的创建,这是“一对多”关系中自然的情况,其中“一”端对象的删除会导致随后删除“多”端的相关项。

为了支持此功能,上述映射将把级联设置放在“一对多”的一端,这看起来像

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,)

但是,事情在以后将不会按预期进行,因为“删除孤儿”级联将继续针对单个领先对象工作,这意味着如果我们删除任何一个 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,则该端可以使用“删除孤儿”级联,但这很可能不是人们真正想要的,因为“多对多”关系的目的是让多个对象在任一方向上引用一个对象。

总的来说,“删除孤儿”级联通常应用于“一对多”关系的“一”端,以便它删除“多”端的对象,而不是相反。

在版本 1.3.18 中更改: 在“多对一”或“多对多”关系上使用“删除孤儿”错误消息的文本已更新,以更具描述性。

实例 <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 标志是在响应 对于关系 <relationship>,通常仅在“一对多”关系的“一”端配置删除孤儿级联,而不是在“多对一”或“多对多”关系的“多”端配置。 中描述的错误消息后应用的,而问题实际上是对“删除孤儿”级联设置的误解。有关详细信息,请参阅该消息。

关系 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.parentParent.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.c1Parent.c2Child.parent 之间的重叠是故意的。

对象无法转换为“持久”状态,因为此身份映射不再有效

版本 1.4.26 中的新增内容。

此消息是为了解决以下情况而添加的:当在创建 Result 对象(该对象将生成 ORM 对象)的原始 Session 关闭或以其他方式调用其 Session.expunge_all() 方法后,对该对象进行迭代时。当 Session 一次性将所有对象剔除时,该 Session 使用的内部 标识映射 将被替换为新的标识映射,而原始标识映射将被丢弃。未消耗且未缓冲的 Result 对象将在内部维护对该现在已丢弃的标识映射的引用。因此,当消耗 Result 时,将要生成的那些对象无法与该 Session 关联。这种安排是经过设计的,因为通常不建议在创建 Result 对象的事务上下文中之外迭代未缓冲的 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 调用情况下调用。

为了以与 asyncio 扩展相同的方式,在上述情况下使用常规的 Session 预先缓冲结果,可以使用 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 已将其剔除,它们将处于 分离 状态。

注意

上面提到的“预先缓冲”与“未缓冲”的 Result 对象是指 ORM 将来自 DBAPI 的原始数据库行转换为 ORM 对象的过程。它并不意味着底层的 cursor 对象(表示来自 DBAPI 的待处理结果)本身是缓冲的还是未缓冲的,因为这本质上是较低层的缓冲。有关 cursor 结果本身缓冲的背景信息,请参见 使用服务器端游标(也称为流结果) 一节。

无法为带注释的声明性表格形式解释类型注释

SQLAlchemy 2.0 引入了一个新的 带注释的声明性表格 声明性系统,该系统从 PEP 484 注释中派生 ORM 映射属性信息,这些注释在运行时位于类定义中。这种形式的一个要求是所有 ORM 注释必须使用称为 Mapped 的泛型容器才能正确地进行注释。包含显式 PEP 484 类型注释的旧式 SQLAlchemy 映射(例如使用 旧版 Mypy 扩展 进行类型支持的映射)可能包含指令(例如 relationship() 的指令),这些指令不包含此泛型。

为了解决这个问题,可以在这些类上标记 __allow_unmapped__ 布尔属性,直到它们能够完全迁移到 2.0 语法。有关示例,请参见 迁移到 2.0 第六步 - 将 __allow_unmapped__ 添加到显式类型化的 ORM 模型 中的迁移说明。

将 <cls> 转换为数据类时,属性(s) 来自超类 <cls>,该超类不是数据类。

此警告在使用 SQLAlchemy ORM 映射数据类功能(如 声明性数据类映射 中所述)时出现,前提是它与任何不是自身声明为数据类的 mixin 类或抽象基类一起使用,例如以下示例

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.

解决方法是在 Mixin 的签名中也添加 MappedAsDataclass

class Mixin(MappedAsDataclass):
    create_user: Mapped[int] = mapped_column()
    update_user: Mapped[Optional[int]] = mapped_column(default=None, init=False)

Python 的 PEP 681 规范不适应为数据类的超类声明的属性(这些超类本身不是数据类);根据 Python 数据类的行为,这些字段将被忽略,如以下示例所示

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 解释为数据类属性。这是因为 Mixin 不是数据类。

SQLAlchemy 在 2.0 系列中的数据类功能没有正确地遵守这种行为;相反,非数据类 mixin 和超类上的属性被视为最终数据类配置的一部分。但是,像 Pyright 和 Mypy 这样的类型检查器不会将这些字段视为数据类构造函数的一部分,因为根据 PEP 681,它们应该被忽略。由于它们的出现其他情况下是模棱两可的,因此 SQLAlchemy 2.1 将要求在数据类层次结构中具有 SQLAlchemy 映射属性的 mixin 类本身也必须是数据类。

在为 <classname> 创建数据类时遇到 Python 数据类错误

当使用 MappedAsDataclass mixin 类或 registry.mapped_as_dataclass() 装饰器时,SQLAlchemy 会使用 Python 标准库中的实际 Python 数据类 模块,以便将数据类行为应用于目标类。此 API 有自己的错误情况,其中大多数涉及在用户定义的类上构造 __init__() 方法;类上声明的属性的顺序,以及 超类上的顺序,决定了 __init__() 方法的构造方式,并且属性的组织方式以及它们使用参数(如 init=Falsekw_only=True 等)的方式存在特定规则。SQLAlchemy 不会控制或实现这些规则。因此,对于这种性质的错误,请参考 Python 数据类 文档,特别注意应用于 继承 的规则。

另请参阅

声明式数据类映射 - SQLAlchemy 数据类文档

Python 数据类 - 在 python.org 网站上

继承 - 在 python.org 网站上

按主键进行的每行 ORM 批量更新要求记录包含主键值

当使用 ORM 按主键进行批量更新 功能,但没有在给定记录中提供主键值时,会出现此错误,例如

>>> 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 一起使用时引发。

另请参阅

异步 I/O (asyncio)

MissingGreenlet

对异步 DBAPI 的调用是在通常由 SQLAlchemy AsyncIO 代理类设置的 greenlet 生成上下文之外发起的。通常,此错误发生在意外位置尝试进行 I/O 时,使用不直接提供使用 await 关键字的调用模式。在使用 ORM 时,这几乎总是由于使用 延迟加载 造成的,在 asyncio 下,没有额外的步骤和/或替代加载器模式,延迟加载无法直接支持。

另请参阅

使用 AsyncSession 时防止隐式 I/O - 涵盖了此问题可能发生的大多数 ORM 场景以及如何减轻,包括在延迟加载场景中使用的特定模式。

No Inspection Available

直接对 inspect() 函数使用 AsyncConnectionAsyncEngine 对象目前不受支持,因为还没有 Inspector 对象的异步形式可用。相反,该对象通过使用 inspect() 函数以一种引用 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()
        )

另请参阅

使用 Inspector 检查架构对象 - 使用 inspect() 的异步扩展的更多示例。

核心异常类

请参见 核心异常,了解核心异常类。

ORM 异常类

请参见 ORM 异常,了解 ORM 异常类。

遗留异常

本节中的异常不是由当前 SQLAlchemy 版本生成的,但此处提供是为了适合异常消息超链接。

SQLAlchemy 2.0 中的 <some function> 将不再 <something>

SQLAlchemy 2.0 代表了 SQLAlchemy 核心和 ORM 组件中各种关键 SQLAlchemy 使用模式的重大转变。2.0 版本的目标是对 SQLAlchemy 自其早期开始以来的一些最基本假设进行轻微调整,并提供一种新的简化使用模型,希望这种模型在核心和 ORM 组件之间更加简化和一致,并且功能更强大。

SQLAlchemy 2.0 - 主要迁移指南 中引入,SQLAlchemy 2.0 项目包含一个全面的未来兼容性系统,该系统集成到 SQLAlchemy 的 1.4 系列中,这样应用程序将有一个清晰、明确和增量的升级路径,以便将应用程序迁移到完全兼容 2.0。 RemovedIn20Warning 弃用警告是该系统的基础,用于指导现有代码库中需要修改的行为。关于如何启用此警告的概述见 SQLAlchemy 2.0 弃用模式.

另请参阅

SQLAlchemy 2.0 - 主要迁移指南 - 从 1.x 系列升级过程的概述,以及 SQLAlchemy 2.0 的当前目标和进度。

SQLAlchemy 2.0 弃用模式 - 关于如何在 SQLAlchemy 1.4 中使用“2.0 弃用模式”的具体指南。

对象正在通过反向引用级联合并到会话中

此消息指的是 SQLAlchemy 的“反向引用级联”行为,在 2.0 版本中已删除。这指的是将对象添加到 Session 中的行为,这是由于该会话中已经存在另一个对象与该对象相关联造成的。由于这种行为已被证明比有益更有迷惑性,因此添加了 relationship.cascade_backrefsbackref.cascade_backrefs 参数,它们可以设置为 False 以禁用它,在 SQLAlchemy 2.0 中,“级联反向引用”行为已完全删除。

对于旧版本的 SQLAlchemy,要在使用 relationship.backref 字符串参数配置的反向引用上将 relationship.cascade_backrefs 设置为 False,必须首先使用 backref() 函数声明反向引用,以便可以传递 backref.cascade_backrefs 参数。

或者,可以通过在“未来”模式下使用 Session(为 Session.future 参数传递 True)来关闭整个“级联反向引用”行为。

另请参阅

cascade_backrefs 行为在 2.0 中被弃用 - 关于 SQLAlchemy 2.0 中更改的背景信息。

在“遗留”模式下创建的 select() 结构;关键字参数等

自 SQLAlchemy 1.4 以来,select() 结构已更新以支持 SQLAlchemy 2.0 中的标准调用风格。为了在 1.4 系列中保持向后兼容性,该结构接受“遗留”风格和“新”风格的参数。

“新”风格的特点是,列和表表达式仅按位置传递给 select() 结构;对该对象的任何其他修改都必须使用后续的方法链接传递

# this is the way to do it going forward
stmt = select(table1.c.myid).where(table1.c.myid == table2.c.otherid)

为了比较,在像 Select.where() 这样的方法被添加之前,旧版 SQLAlchemy 中的 select() 看起来像

# 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 链接。也就是说,而不是实例化 Sessionsessionmaker 没有任何参数,并将 EngineMetaData

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 必须直接与 sessionmakerSession 关联。 MetaData 对象不应该再与任何引擎关联

engine = create_engine("sqlite://")
Session = sessionmaker(engine)
Base = declarative_base()


class MyClass(Base): ...


session = Session()
session.add(MyClass())
session.commit()

在 SQLAlchemy 1.4 中,当 Session.future 标志在 sessionmakerSession 上设置时,启用此 2.0 风格 行为。

此 Compiled 对象未绑定到任何 Engine 或 Connection

此错误指的是“绑定元数据”的概念,这是一个旧版 SQLAlchemy 模式,仅存在于 1.x 版本中。当在不与任何 Engine 关联的核心表达式对象上直接调用 Executable.execute() 方法时,就会出现此问题。

metadata_obj = MetaData()
table = Table("t", metadata_obj, Column("q", Integer))

stmt = select(table)
result = stmt.execute()  # <--- raises

逻辑期望的是 MetaData 对象已与 Engine **绑定**

engine = create_engine("mysql+pymysql://user:pass@host/db")
metadata_obj = MetaData(bind=engine)

在上面,任何从 Table 派生的语句,反过来从该 MetaData 派生,将隐式使用给定的 Engine 来调用语句。

请注意,**SQLAlchemy 2.0 中不存在绑定元数据** 的概念。调用语句的正确方法是通过 Connection.execute() 方法,该方法属于 Connection

with engine.connect() as conn:
    result = conn.execute(stmt)

在使用 ORM 时,可以使用类似的功能,通过 Session

result = session.execute(stmt)

另请参阅

语句执行基础

此连接处于非活动事务中。请在继续之前完全回滚()。

此错误条件从 SQLAlchemy 1.4 版本开始添加,不适用于 SQLAlchemy 2.0。此错误指的是将 Connection 放入事务的状态,使用像 Connection.begin() 这样的方法,然后在该范围内创建另一个“标记”事务;然后使用 Transaction.rollback() 回滚“标记”事务或使用 Transaction.close() 关闭“标记”事务,但外部事务仍处于“非活动”状态,必须回滚。

模式看起来像

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 是一个“标记”事务,它表示在外部事务中逻辑上嵌套了事务;虽然内部事务可以通过其 rollback() 方法回滚整个事务,但其 commit() 方法除了关闭“标记”事务本身的范围之外没有其他效果。调用 transaction2.rollback() 会使 transaction1 **失效**,这意味着它实质上已在数据库级别回滚,但仍存在,以便适应事务的一致嵌套模式。

正确的解决方案是确保外部事务也回滚

transaction1.rollback()

这种模式在核心模块中并不常用。在 ORM 中,可能会出现类似的问题,这是 ORM “逻辑”事务结构的产物;这在常见问题解答条目中进行了描述,位于 “此 Session 的事务已由于先前在 flush 期间出现的异常而回滚。”(或类似)

在 SQLAlchemy 2.0 中删除了“子事务”模式,因此不再提供这种特定编程模式,从而防止出现此错误消息。