错误消息


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


SQLAlchemy 通常在特定于 SQLAlchemy 的上下文中引发错误 exception 类。 有关这些类的详细信息,请参阅 Core ExceptionsORM Exceptions


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


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


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


连接和事务


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


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


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


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


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


  • 在大多数情况下,使用的默认池称为 QueuePool。当您要求此池为您提供一个连接但没有可用的连接时,如果 Play 中的连接总数 小于配置的值。 此值等于 pool size 加上 Max overflow。这意味着,如果您已将引擎配置为:

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


    上述 Engine 将允许任何时候最多 30 个连接处于活动状态,不包括与 Engine 分离或失效的连接。如果到达新连接的请求,并且应用程序的其他部分已经使用了 30 个连接,则连接池将在超时并引发此错误消息之前阻塞一段固定的时间。


    为了允许同时使用更多的连接, 可以使用 create_engine.pool_sizecreate_engine.max_overflow 传递给 create_engine() 函数的参数。 超时 等待连接可用是使用 create_engine.pool_timeout 参数。


  • 可以通过设置 create_engine.max_overflow 设置为值 “-1”。使用此设置,池仍将维护固定的连接池,但永远不会在请求新连接时阻止;如果没有可用的连接,它将无条件地建立一个新的连接。


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


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


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


  • 应用程序处理了太多并发请求,无法执行基于工作的请求 在池的配置值上 - 这是最直接的原因。如果您的应用程序在允许 30 个并发线程的线程池中运行,并且每个线程使用一个连接,如果您的池未配置为允许一次签出至少 30 个连接,则一旦您的应用程序收到足够的并发请求,您将收到此错误。解决方案是提高池的限制或降低并发线程的数量。


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


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


  • 应用程序正在尝试运行长时间运行的事务 - 数据库事务是一种非常昂贵的资源,绝不应如此 处于空闲状态,等待某些事件发生。如果应用程序正在等待用户按下按钮,或者等待长时间运行的作业队列中的结果,或者正在保持对浏览器的持久连接打开,请不要 使数据库事务始终保持打开状态。由于应用程序需要使用数据库并与事件交互,因此请在该点打开一个短期事务,然后将其关闭。


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


    • 使用隐式异步系统(如 gevent 或 eventlet)而没有正确地对所有套接字库和驱动程序进行 monkeypatch,或者存在未完全覆盖所有 monkeypatched 驱动程序方法的错误,或者不太常见的是,当异步系统用于 CPU 绑定的工作负载和利用数据库资源的 greenlet 只是等待太长时间来处理它们。隐式和显式异步编程框架通常都不是必要的,也不是绝大多数关系数据库作的合适对象;如果应用程序必须将异步系统用于某些功能区域,则最好将面向数据库的业务方法运行在将消息传递给应用程序的异步部分的传统线程中。


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


    • 线程错误,例如互斥锁中的互斥锁,或在同一线程中调用已锁定的互斥锁


请记住,使用池的替代方法是完全关闭池。有关此内容的背景信息,请参阅 Switching Pool Implementations 部分。但是,请注意,当出现此错误消息时,它始终是由于应用程序本身存在更大的问题;池只是有助于更快地发现问题。


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


QueuePool 池类使用线程。Lock 对象,并且与 asyncio 不兼容。如果使用 create_async_engine() 函数创建 AsyncEngine,则相应的队列池类是 AsyncAdaptedQueuePool,它是自动使用的,不需要指定。


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


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


另请参阅


连接池


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


此错误情况是指 Connection 无效的情况,要么是由于数据库断开连接检测,要么是由于显式调用 Connection.invalidate(),但仍存在由 Connection.begin() 显式启动的事务 方法,或者由于 Connection 自动开始事务 在 SQLAlchemy 的 2.x 系列中,当发出任何 SQL 语句时。 当连接失效时,任何 Transaction 正在进行中的演示现在处于无效状态,必须显式滚动 back 以将其从 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 本身。


内部错误


当数据库遇到内部错误时引发异常,例如游标不再有效,事务不同步等。


此错误是 DBAPI 错误,源自数据库驱动程序 (DBAPI),而不是 SQLAlchemy 本身。


InternalError 有时由上下文中的驱动程序引发 数据库连接被丢弃或无法连接 添加到数据库。 有关如何处理此问题的提示,请参阅该部分 处理断开连接


编程错误


因编程错误而引发异常,例如未找到表或已存在表、SQL 语句中的语法错误、指定的参数数量错误等。


此错误是 DBAPI 错误,源自数据库驱动程序 (DBAPI),而不是 SQLAlchemy 本身。


ProgrammingError 有时由驱动程序在上下文中引发 数据库连接被丢弃或无法连接 添加到数据库。 有关如何处理此问题的提示,请参阅该部分 处理断开连接


NotSupportedError(不支持错误)¶


如果使用了数据库不支持的方法或数据库 API,则会引发异常,例如,在不支持事务或已关闭事务的连接上请求 .rollback() 。


此错误是 DBAPI 错误,源自数据库驱动程序 (DBAPI),而不是 SQLAlchemy 本身。


SQL 表达式语言


Object 不会产生缓存键,性能影响


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


如果相关警告引用广泛使用的对象,例如 Column 对象,并显示影响大多数 SQL 结构(使用 使用 Logging 估计缓存性能)因此,通常不会为应用程序启用缓存,这将对性能产生负面影响,并且与以前的 SQLAlchemy 版本相比,在某些情况下可能会有效地导致性能下降升级到 1.4 和/或 2.x 后,为什么我的应用程序运行缓慢?中的常见问题解答更详细地介绍了这一点。


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


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


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


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


由于以上两个原因,SQLAlchemy 的缓存系统非常 保守地决定缓存与对象对应的 SQL。


用于缓存的断言属性


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


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


此错误通常在尝试字符串化包含不属于默认编译的元素的 SQL 表达式构造时发生;在这种情况下,错误将针对 StrSQLCompiler 类。在不太常见的情况下,当特定类型的数据库后端使用错误类型的 SQL 表达式时,也会发生这种情况;在这些情况下,将命名其他类型的 SQL 编译器类,例如 SQLCompilersqlalchemy.dialects.postgresql.PGCompiler 。下面的指南更具体地介绍了 “stringification” 用例,但也描述了一般背景。


通常,Core SQL 结构或 ORM 查询对象可以直接字符串化,例如当我们使用 print() 时:

>>> from sqlalchemy import column
>>> print(column("x") == 5)
x = :x_1


当上述 SQL 表达式被字符串化时,StrSQLCompiler compiler 类,该类是调用 当构造被字符串化而没有任何特定于方言的信息时。


但是,有许多结构特定于某种特定类型的数据库方言,而 StrSQLCompiler 不知道如何作 转换为字符串,例如 PostgreSQL 插入。。。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: <operator> 在 'ColumnProperty' 和 <something> 的实例之间不受支持


这通常发生在尝试使用 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 对象或将映射到 Bar 类。


虽然 ColumnProperty 确实具有 __clause_element__() 方法, 这允许它在某些面向列的上下文中工作,但它不能在 如上所示的开放式比较上下文,因为它没有 Python __eq__() 方法,该方法将允许它将与数字 “5” 的比较解释为 SQL 表达式,而不是常规的 Python 比较。


解决方案是使用 ColumnProperty.expression 属性:

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


当消息采用 “a value is required for bind parameter 在参数组 “ 中,消息引用 ”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 子句中,除非它们被包装在子查询中第一。这是 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() 方法来生成 this。在其他数据库上,子查询具有名称以解决将来对子查询中列名的引用的任何歧义仍然要清晰得多。


除了上述实际原因之外,还有许多其他面向 SQLAlchemy 的原因正在进行更改。因此,上述两个语句的正确形式要求使用 SelectBase.subquery()

subq = stmt.subquery()

new_stmt_1 = select(subq)

new_stmt_2 = select(some_table).select_from(some_table.join(subq))


正在为 raw 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 == "ed@foo.com")
    .all()
)


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

a1 = Address.__table__.alias()

q = (
    s.query(User)
    .join(a1, User.addresses)
    .filter(a1.c.email_address == "ed@foo.com")
    .all()
)


指定连接目标的正确方法始终是使用映射类本身或别名对象,在后一种情况下使用 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")
)


加入别名:

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() 方法,其中包含涉及联接表继承的映射。问题在于,当在共享公共基表的两个联接的继承模型之间进行联接时,如果不将别名应用于一侧或另一侧,则无法在两个实体之间形成正确的 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” 数据库 table 中,从 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 选择 FROM employee 表,表示 Employee 实体。然后,它联接到 employee AS employee_1 JOIN manager AS manager_1 ,其中员工 table 的 S Alpha Bean 的 Alpha S employee_1S S Alpha S S Alpha S Alpha这是警告消息所指的“自动生成别名”。


当 SQLAlchemy 加载 ORM 行时,每个行都包含一个 Employee 和一个 Manager 对象,则 ORM 必须从上面的 employee_1manager_1 表别名转换为未别名的 Manager 类。此过程内部很复杂,无法适应 对于所有 API 功能,尤其是在尝试使用预先加载功能(如 contains_eager() 的嵌套查询比此处显示的要深。由于该模式对于更复杂的场景不可靠,并且涉及难以预测和遵循的隐式决策,因此会发出警告,并且此模式可能被视为遗留功能。编写此查询的更好方法是使用适用于任何其他自引用关系的相同模式,即使用 aliased() 构造。 对于 joined-inheritance 和其他面向 join 的映射, 通常希望添加 aliased.flat 参数,该参数将允许两个或多个表的 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 的单个实例上调用的并发方法 对象,并扩展为 AsyncSession 代理对象。这些并发访问调用通常(但并非唯一)发生在多个并发线程之间共享 Session 的单个实例而没有同步此类访问时,或者类似地,当 AsyncSession 的单个实例在多个并发任务之间共享时(例如,当使用像 asyncio.gather() 这样的函数时)).这些使用模式不是这些对象的适当使用,如果没有主动警告系统,SQLAlchemy 实现仍会在对象中产生无效状态,从而产生难以调试的错误,包括数据库连接本身的驱动程序级错误。


SessionAsyncSession 的实例是 可变的有状态对象,没有内置方法调用同步,并且表示特定 EngineAsyncEngine 一次在单个数据库连接上的单个、正在进行的数据库事务 对象绑定到的 (请注意,这些对象都支持被绑定 一次连接到多个引擎,但在这种情况下,仍然只有一个 在事务范围内运行中的每个引擎的连接)。 单个 数据库事务不是并发 SQL 命令的合适目标; 相反,运行并发数据库作的应用程序应该使用 并发事务。对于这些对象,则适当的 pattern 是每个线程的 Session,或 AsyncSession 每个任务。


有关并发的更多背景信息,请参阅以下部分 Session 线程安全吗? 在并发任务中共享 AsyncSession 是否安全?.


父实例 <x> 未绑定到会话;(延迟加载/延迟加载/刷新/等)作无法继续


这可能是处理 ORM 时最常见的错误消息,它是由于 ORM 广泛使用的一种称为延迟加载的技术的性质而发生的。延迟加载是一种常见的对象关系模式,其中 ORM 持久化的对象维护数据库本身的代理,这样当访问对象上的各种属性时,可以从数据库中延迟检索它们的值。此方法的优点是,可以从数据库中检索对象,而不必一次加载其所有属性或相关数据,而是只能在此时传送请求的数据。主要缺点基本上是优点的镜像,即如果正在加载大量已知在所有情况下都需要特定数据集的对象,那么零碎地加载这些额外的数据是浪费的。


除了通常的效率问题之外,延迟加载的另一个警告是,为了继续延迟加载,对象必须保持关联 替换为 Session 才能检索其状态。此错误消息表示对象已与其 Session 取消关联,并被要求从数据库延迟加载数据。


对象与其 Session 分离的最常见原因 是会话本身已关闭,通常是通过 Session.close() 方法。 然后,这些对象将继续存在,以便进一步、非常频繁地访问 在 Web 应用程序中,它们被传送到服务器端模板 engine 的 intent 和被要求提供它们无法加载的其他属性。


通过以下技术缓解此错误:


  • 尽量不要有分离的对象;不要过早关闭会话 - 通常,应用程序会在将相关对象传递给其他系统之前关闭事务,然后由于此错误而失败。有时交易不需要这么快关闭;一个例子是 Web 应用程序在呈现视图之前关闭事务。这通常是以 “正确” 的名义完成的,但可能被视为 “encapsulation” 的错误应用,因为这个术语指的是代码组织,而不是实际作。使用 ORM 对象的模板正在使用代理模式 它使数据库逻辑封装在调用方之外。 如果 Session 可以保持打开状态,直到对象的生命周期结束,这是最好的方法。


  • 否则,预先加载所需的一切 - 通常不可能保持事务打开,尤其是在更复杂的应用程序中,这些应用程序需要将对象传递给其他系统,这些系统无法在同一上下文中运行,即使它们位于同一进程中。在这种情况下,应用程序应该准备处理分离的对象,并应尝试适当地使用预先加载,以确保对象预先具有它们需要的内容。


  • 重要的是,将 expire_on_commit 设置为 False - 当使用分离对象时,对象需要重新加载数据的最常见原因是它们在上次调用 Session.commit() 时已过期。 此过期时间应 在处理分离的对象时不使用;所以 Session.expire_on_commit 参数设置为 False。通过防止对象在事务之外过期,加载的数据将保持存在,并且在访问该数据时不会产生额外的延迟加载。


    还要注意,Session.rollback() 方法无条件地使 Session 中的所有内容过期,在非错误情况下也应避免使用。


    另请参阅


    关系加载技术 - 有关预先加载和其他面向关系的加载技术的详细文档


    提交 - 会话提交的背景


    刷新/过期 - 属性过期的背景


由于 flush 期间的先前异常,此 Session 的事务已回滚


Session 的 flush 过程,如 Flushing 将在遇到错误时回滚数据库事务,以保持内部一致性。但是,一旦发生这种情况,会话的事务现在是 “非活动” 的,必须由调用应用程序显式回滚,就像在没有发生故障时需要显式提交一样。


这是使用 ORM 时的常见错误,通常适用于 尚未在其 会话作。更多详细信息在 FAQ 中进行了描述,网址为 “由于刷新期间的上一个异常,此 Session 的事务已回滚。”(或类似)


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


当 “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 也应该被删除。也就是说,它表示被删除的 “orphan” 将是一个 A 对象,当引用它的每个 B 都被删除时,它就变成了 “orphan”。


“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.


请注意,此验证器的范围有限,不会阻止通过另一个方向创建多个 “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” 级联将继续以单个 lead 对象的方式工作,这意味着如果我们删除 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” 级联通常应用于一对多关系的 “one” 端,以便它删除 “many” 端的对象,而不是相反。


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


该 intent 表示一次只能有多个 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 防止写入冲突,或者有时配置是完全的 intentional 并应配置 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 parameter 可以指定警告应针对的关系的名称 不生效。这通常发生在与 包含自定义 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.c2Child.parent 是有意为之的。


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


在 1.4.26 版本加入.


添加此消息是为了适应 Result 对象在原始 Session 关闭后被迭代,或者具有 Session.expunge_all() 方法调用。当会话 一次删除所有对象,该对象使用的内部身份映射 会话将替换为新的会话,并丢弃原始会话。未使用且未缓冲的 Result 对象将在内部维护对现已丢弃的身份映射的引用。因此,当 Result 被消耗时,将产生的对象无法与该 Session 相关联。这种安排是通过 设计,因为通常不建议迭代 unbuffered 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 返回 sync-style 一样 Result,则在执行语句时,结果已预先缓冲。这是为了允许辅助 Eager Loader 调用,而无需额外的 await 调用。


要预先缓冲会导致上述情况,请使用常规的 Sessionasyncio 扩展相同,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 状态交付。


注意


上面对 “pre-buffered” 与 “un-buffered” 的引用 Result 对象是指 ORM 将来自 DBAPI 的传入原始数据库行转换为 ORM 对象的过程。它并不意味着底层游标是否 object 本身(表示 DBAPI 的待处理结果)本身就是 buffered 或 unbuffered,因为这本质上是较低的缓冲层。 有关缓冲游标结果本身的背景信息,请参阅使用服务器端游标(也称为流结果)部分。


类型注释不能被解释为 Annotated Declarative Table 表单


SQLAlchemy 2.0 引入了一个新的 带注释的声明式表声明式系统,从 PEP 484 派生 ORM 映射属性信息 Annotations 中的类定义。此表格的要求是 所有 ORM 注解都必须使用一个名为 Mapped 进行正确注释。旧版 SQLAlchemy 映射,其中包括显式 PEP 484 类型注释,例如使用 传统的 Mypy 扩展,可能包含不包含此泛型的指令,例如 relationship() 的指令。


要解决此问题,可以使用__allow_unmapped__ boolean 属性标记这些类,直到它们可以完全迁移到 2.0 语法。有关示例,请参阅迁移到 2.0 第 6 步 - 将__allow_unmapped__添加到显式类型的 ORM 模型中的迁移说明。


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


当将声明性数据类映射中描述的 SQLAlchemy ORM 映射数据类功能与任何本身未声明为数据类的混合类或抽象基结合使用时,会出现此警告,例如以下示例:

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.


解决方法是将 MappedAsData类添加到 Mixin 中也:

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


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


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


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


另请参阅


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


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


继承 - 在 python.org 网站上


每行 ORM Bulk Update by Primary Key 要求记录包含主键值


当使用 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 Bulk Update by Primary Key,它期望参数字典包含主键值,例如:

>>> session.execute(
...     update(User),
...     [
...         {"id": 1, "fullname": "Spongebob Squarepants"},
...         {"id": 3, "fullname": "Patrick Star"},
...         {"id": 5, "fullname": "Eugene H. Krabs"},
...     ],
... )


要在不提供每条记录的主键值的情况下调用 UPDATE 语句,请使用 Session.connection() 获取当前 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 一起使用时,通常会引发此错误。


缺失的 Greenlet


对异步 DBAPI 的调用是在 greenlet 生成上下文之外启动的,通常由 SQLAlchemy AsyncIO 代理类设置。通常,当在意外位置尝试 IO 时,使用不直接提供 await 关键字使用的调用模式,会发生此错误。当使用 ORM 时,这几乎总是由于使用了延迟加载,如果没有额外的步骤和/或替代加载器模式,asyncio 不直接支持延迟加载才能成功使用。


另请参阅


使用 AsyncSession 时防止隐式 IO - 涵盖可能发生此问题的大多数 ORM 场景以及如何缓解,包括用于延迟加载场景的特定模式。


无可用的检查


直接在 AsyncConnectionAsyncEngine 对象为 目前不支持,因为尚不存在 Inspector 对象可用。相反,对象 通过使用 inspect() 函数,使其引用底层的 AsyncConnection.sync_connection 属性的 AsyncConnection 对象;Inspector 是 然后通过使用 AsyncConnection.run_sync() 方法以及执行所需作的自定义函数:

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() 的其他示例 使用 asyncio 扩展。


核心异常类


请参阅 Core Exceptions for Core 异常类。


ORM 异常类


有关 ORM 异常类,请参阅 ORM 异常


遗留异常


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


SQLAlchemy 2.0 中的 <some 函数>将不再是 <something>


SQLAlchemy 2.0 代表了 Core 和 ORM 组件中各种关键 SQLAlchemy 使用模式的重大转变。2.0 版本的目标是对 SQLAlchemy 自早期以来的一些最基本的假设进行轻微的重新调整,并提供一种新的简化使用模型,希望在 Core 和 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 Deprecations Mode - 有关如何在 SQLAlchemy 1.4 中使用“2.0 Deprecations Mode”的特定指南。


Object 正在沿着 backref 级联合并到一个 Session 中¶


此消息引用了 SQLAlchemy 的 “backref cascade” 行为,已在 2.0 版中删除。这是指由于该 Session 中已存在的另一个对象与该对象相关联而被添加到 Session 中的作。由于此行为已被证明令人困惑多于帮助,因此 relationship.cascade_backrefs 和 添加了backref.cascade_backrefs参数,可以将其设置为 False 以禁用它,并且在 SQLAlchemy 2.0 中,“级联反向引用”行为已被完全删除。


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


或者,通过在 “future” 模式下使用 Session,通过 Session.future 参数传递 True,可以全面关闭整个 “cascade backrefs” 行为。


另请参阅


cascade_backrefs行为在 2.0 中已弃用并删除 - SQLAlchemy 2.0 更改的背景。


select() 结构;关键字参数等


select() 结构从 SQLAlchemy 1.4 开始已更新,以支持 SQLAlchemy 2.0 中标准的较新调用样式。为了在 1.4 系列中向后兼容,该结构接受 “legacy” 样式和 “new” 样式的参数。


“新”样式的特点是列和表表达式仅按位置传递给 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 对象,而 ORM Session 将特定映射类与 发动机。在 SQLAlchemy 2.0 中,Session 必须直接链接到每个 Engine。也就是说,不是实例化不带任何参数的 Sessionsessionmaker,而是将 Engine元数据

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 中,当 在 sessionmaker 上设置 Session.future 标志Session (会话)。


这个 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


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

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


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


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

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


使用 ORM 时,可以通过 Session 使用类似的工具:

result = session.execute(stmt)


另请参阅


语句执行基础


此连接位于非活动事务上。在继续之前,请完全 rollback()


此错误条件是从版本 1.4 添加到 SQLAlchemy 的,不适用于 SQLAlchemy 2.0。该错误是指使用 Connection.begin() 等方法将 Connection 放入事务中,然后在该范围内创建进一步的“标记”事务的状态;然后使用 Transaction.rollback() 回滚 “marker” 事务或使用 Transaction.close() 关闭,但是外部事务仍处于 “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 的“逻辑”交易结构的产物;这在 FAQ 条目 “This Session's transaction has been roll back due to a previous exception during flush” 中进行了描述。(或类似)


在 SQLAlchemy 2.0 中删除了 “subtransaction” 模式,因此此特定编程模式不再可用,从而防止此错误消息。