本节列出了SQLAlchemy引发或发出的常见错误消息和警告的描述和背景。
sqlAlchemy通常会在特定于sqlAlchemy的异常类的上下文中引发错误。有关这些类的详细信息,请参见 核心异常 和 ORM例外 .
SQLAlchemy错误大致可以分为两类,即 programming-time error 以及 运行时错误 . 由于使用不正确的参数调用函数或方法,或从其他面向配置的方法(如无法解决的映射器配置)调用,会引发编程时间错误。编程时间错误通常是直接的和确定性的。另一方面,运行时错误表示当程序响应某些任意发生的条件(如数据库连接耗尽或发生某些与数据相关的问题)运行时发生的故障。运行时错误更可能出现在正在运行的应用程序的日志中,因为程序在响应加载和遇到的数据时遇到这些状态。
由于运行时错误不像程序运行时那样容易重现,并且经常在响应某些任意条件时发生,因此它们更难调试,也会影响已投入生产的程序。
在本节中,目标是尝试提供一些最常见的运行时错误以及编程时错误的背景。
这可能是遇到的最常见的运行时错误,因为它直接涉及超过配置限制的应用程序的工作负载,该限制通常适用于几乎所有的SQLAlchemy应用程序。
以下几点总结了这个错误的含义,从大多数SQLAlchemy用户应该已经熟悉的最基本的点开始。
默认情况下,SQLAlchemy引擎对象使用连接池 -这意味着当使用SQL数据库连接资源时, Engine
对象,然后 releases 这个资源,数据库连接本身仍然连接到数据库,并返回到一个内部队列,在那里可以再次使用它。即使代码似乎正在结束与数据库的对话,在许多情况下,应用程序仍将保持固定数量的数据库连接,这些连接将一直保持到应用程序结束或池被显式释放为止。
由于池的原因,当应用程序使用SQL数据库连接时,通常从使用 Engine.connect()
或使用ORM进行查询时 Session
,此活动不一定在获取连接对象时建立到数据库的新连接;而是为连接查询连接池,该连接通常从要重新使用的池中检索现有连接。如果没有可用的连接,池将创建一个新的数据库连接,但前提是池没有超过配置的容量。
在大多数情况下使用的默认池被调用 QueuePool
. 当您要求此池为您提供一个连接但没有可用的连接时,它将创建一个新连接。 如果播放中的连接总数小于配置的值 . 该值等于 池大小加上最大溢出 . 这意味着如果您将引擎配置为:
engine = create_engine("mysql://u:p@host/db", pool_size=10, max_overflow=20)
以上 Engine
将允许 最多30个连接 可随时使用,不包括与引擎分离或失效的连接。如果一个新连接的请求到达,并且应用程序的其他部分已经使用了30个连接,那么在超时并引发此错误消息之前,连接池将阻塞一段固定的时间。
为了允许同时使用更多的连接,可以使用 create_engine.pool_size
和 create_engine.max_overflow
传递到的参数 create_engine()
功能。等待连接可用的超时使用 create_engine.pool_timeout
参数。
可以通过设置将池配置为具有无限制的溢出 create_engine.max_overflow
到值“-1”。使用此设置,池仍将保持固定的连接池,但是它不会在请求新连接时阻塞;相反,如果没有可用的连接,它将无条件创建新连接。
但是,以这种方式运行时,如果应用程序出现问题,即它正在耗尽所有可用的连接资源,则它最终将达到数据库本身配置的可用连接限制,这将再次返回错误。更严重的是,当应用程序耗尽连接数据库时,它通常会导致大量资源在失败之前耗尽,并且还会干扰其他依赖于能够连接到数据库的应用程序和数据库状态机制。
考虑到上述情况,可以将连接池视为 连接用安全阀 对恶意应用程序提供关键的保护层,导致整个数据库对所有其他应用程序不可用。当收到此错误消息时,最好使用过多的连接来修复问题和/或适当地配置限制,而不是允许不受限制的溢出,这实际上并不能解决基础问题。
是什么导致应用程序用尽所有可用的连接?
应用程序根据池的配置值部署了太多的并发请求,无法工作。 -这是最直接的原因。如果您有一个应用程序在一个线程池中运行,该线程池允许30个并发线程,每个线程使用一个连接,如果您的池没有配置为允许至少30个连接一次签出,那么一旦应用程序收到足够的并发请求,就会出现此错误。解决方案是提高池的限制或减少并发线程的数量。
应用程序没有返回到池的连接 -这是第二个最常见的原因,即应用程序正在使用连接池,但程序未能 release 这些连接,而不是让它们打开。连接池和ORM Session
一定要有这样的逻辑:当会话和/或连接对象被垃圾收集时,它会导致底层连接资源被释放,但是这种行为不能及时地释放资源。
出现这种情况的一个常见原因是应用程序使用ORM会话而不调用 Session.close()
在他们身上,涉及到那个会议的工作已经完成。解决方案是确保使用ORM或引擎绑定的ORM会话 Connection
如果使用core,则对象将在正在执行的工作结束时通过适当的 .close()
方法,或者使用一个可用的上下文管理器(例如“with:”语句)来正确释放资源。
The application is attempting to run long-running transactions -数据库事务是非常昂贵的资源,应该 永远不要在等待某个事件发生时处于空闲状态 . 如果应用程序正在等待用户按下按钮,或者结果从长时间运行的作业队列中出来,或者保持对浏览器的持久连接, 不要一直打开数据库事务 . 由于应用程序需要与数据库一起工作并与事件交互,因此在该点打开一个短期事务,然后关闭它。
应用程序死锁 -此外,此错误的一个常见原因是更难以理解,如果应用程序由于应用程序端死锁或数据库端死锁而无法完成连接的使用,则应用程序可以使用所有可用的连接,从而导致接收此错误的其他请求。僵局的原因包括:
使用隐式异步系统,如gevent或eventlet,而没有正确地对所有套接字库和驱动程序进行MonkeyPatching,或者在没有完全覆盖所有MonkeyPatching驱动程序方法方面存在缺陷,或者在异步系统用于CPU绑定的工作负载时不太常见,而使用数据库资源的greenlet只是在等待。太长时间不能照顾他们。隐式或显式异步编程框架对于绝大多数关系数据库操作都不是必需的或合适的;如果应用程序必须在某些功能领域使用异步系统,那么最好是在传统线程中运行面向数据库的业务方法,这些线程将消息传递给异步部分o。应用程序。
数据库端死锁,例如行相互死锁
线程错误,例如互斥锁中的互斥锁,或调用同一线程中已锁定的互斥锁
请记住,使用池的另一种选择是完全关闭池。见剖面图 切换池实现 作为背景。但是,请注意,当出现此错误消息时, 总是 由于应用程序本身存在更大的问题,池只是有助于更快地发现问题。
python数据库API(或dbapi)是一种数据库驱动程序规范,位于 Pep-249 . 此API指定了一组异常类,这些类可以适应数据库的所有故障模式。
SQLAlchemy不会直接生成这些异常。相反,它们是从数据库驱动程序截取的,并由SQLAlchemy提供的异常进行包装。 DBAPIError
但是,异常中的消息是 由驱动程序生成,而不是由sqlAlchemy生成 .
对与数据库接口而不是数据库本身相关的错误引发异常。
这个错误是 DBAPI Error 并且源于数据库驱动程序(DBAPI),而不是SQLAlchemy本身。
这个 InterfaceError
有时由驱动程序在数据库连接被删除或无法连接到数据库的上下文中引发。有关如何处理此问题的提示,请参阅部分 处理断开连接 .
对与数据库本身相关的错误(而不是正在传递的接口或数据)引发异常。
这个错误是 DBAPI Error 并且源于数据库驱动程序(DBAPI),而不是SQLAlchemy本身。
由于处理的数据出现问题(如被零除、数值超出范围等)导致的错误引发异常。
这个错误是 DBAPI Error 并且源于数据库驱动程序(DBAPI),而不是SQLAlchemy本身。
对与数据库操作相关且不一定在程序员控制下的错误引发的异常,例如发生意外断开、找不到数据源名称、无法处理事务、处理过程中发生内存分配错误等。
这个错误是 DBAPI Error 并且源于数据库驱动程序(DBAPI),而不是SQLAlchemy本身。
这个 OperationalError
是驱动程序在要删除或无法连接到数据库的数据库连接上下文中使用的最常见(但不是唯一)错误类。有关如何处理此问题的提示,请参阅部分 处理断开连接 .
当数据库遇到内部错误时引发异常,例如光标不再有效、事务不同步等。
这个错误是 DBAPI Error 并且源于数据库驱动程序(DBAPI),而不是SQLAlchemy本身。
这个 InternalError
有时由驱动程序在数据库连接被删除或无法连接到数据库的上下文中引发。有关如何处理此问题的提示,请参阅部分 处理断开连接 .
因编程错误引发的异常,例如,表未找到或已存在、SQL语句中的语法错误、指定的参数数目错误等。
这个错误是 DBAPI Error 并且源于数据库驱动程序(DBAPI),而不是SQLAlchemy本身。
这个 ProgrammingError
有时由驱动程序在数据库连接被删除或无法连接到数据库的上下文中引发。有关如何处理此问题的提示,请参阅部分 处理断开连接 .
在使用数据库不支持的方法或数据库API时引发异常,例如,在不支持事务或已关闭事务的连接上请求.rollback()。
这个错误是 DBAPI Error 并且源于数据库驱动程序(DBAPI),而不是SQLAlchemy本身。
当试图将包含不属于默认编译的元素的SQL表达式构造字符串化时,通常会发生此错误;在这种情况下,该错误将针对 StrSQLCompiler
班级。在不太常见的情况下,如果将错误类型的SQL表达式用于特定类型的数据库后端,也会发生这种情况;在这种情况下,将命名其他类型的SQL编译器类,例如 SQLCompiler
或 sqlalchemy.dialects.postgresql.PGCompiler
. 下面的指南更具体地描述了“细化”用例,但也描述了一般的背景。
通常,核心SQL构造或ORM Query
对象可以直接字符串化,例如当我们使用 print()
::
>>> from sqlalchemy import column
>>> print(column('x') == 5)
x = :x_1
当上面的SQL表达式被字符串化时, StrSQLCompiler
使用编译器类,这是一种特殊的语句编译器,当构造在没有任何方言特定信息的情况下进行字符串化时调用。
但是,有许多特定于某种特定类型的数据库方言的构造,其中 StrSQLCompiler
不知道如何变成字符串,比如PostgreSQL "insert on conflict" 结构:
>>> 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
或A 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
对象,可以使用 statement
访问器:
statement = query.statement
print(statement.compile(dialect=postgresql.dialect()))
有关SQL元素的直接字符串化/编译的更多详细信息,请参阅下面的FAQ链接。
当尝试使用 column_property()
或 deferred()
对象在SQL表达式的上下文中,通常位于声明性的,例如:
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
对象或 InstrmentedAttribute
将映射到 Bar
一旦声明过程完成,则初始化。
而 ColumnProperty
确实有 __clause_element__()
方法,它允许它在一些面向列的上下文中工作,它不能在上面所示的开放式比较上下文中工作,因为它没有python __eq__()
方法,它可以将与数字“5”的比较解释为SQL表达式,而不是常规的Python比较。
解决方案是访问 Column
直接使用 ColumnProperty.expression
属性:
class Bar(Base):
__tablename__ = 'bar'
id = Column(Integer, primary_key=True)
cprop = deferred(Column(Integer))
__table_args__ = (
CheckConstraint(cprop.expression > 5),
)
此错误是指“绑定元数据”的概念,如 无连接执行,隐式执行 . 当调用 Executable.execute()
直接从与任何 Engine
::
metadata = MetaData()
table = Table('t', metadata, Column('q', Integer))
stmt = select([table])
result = stmt.execute() # <--- raises
逻辑预期的是 MetaData
对象已经 跳跃 到A Engine
::
engine = create_engine("mysql+pymysql://user:pass@host/db")
metadata = MetaData(bind=engine)
在上文中,任何源自 Table
这反过来又是从 MetaData
将隐式地使用给定的 Engine
以便调用语句。
注意,绑定元数据的概念是 遗产模式 在大多数情况下 非常沮丧 . 调用语句的最佳方法是将其传递给 Connection.execute()
A方法 Connection
::
with engine.connect() as conn:
result = conn.execute(stmt)
使用ORM时,可通过 Session
::
result = session.exxecute(stmt)
参见
当语句使用 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>需要一个值”的形式时,消息引用的是“ExecuteMany”执行样式。在这种情况下,该语句通常是一个插入、更新或删除语句,并且正在传递参数列表。在这种格式中,语句可以动态生成,以包括参数列表中给定的每个参数的参数位置,在这里它将使用 第一组参数 确定这些应该是什么。
例如,下面的语句是根据需要参数“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
以便插入:
e.execute(
t.insert(), [
{"a": 1, "b": 2, "c": 3},
{"a": 2, "b": None, "c": 4},
{"a": 3, "b": 4, "c": 5},
]
)
这很可能是处理ORM时最常见的错误消息,并且它是由于ORM广泛使用的一种技术的性质而发生的,即 lazy loading . 延迟加载是一种常见的对象关系模式,通过这种模式,ORM持久化的对象保持对数据库本身的代理,这样当访问对象上的各种属性时,可以从数据库中检索它们的值。 懒洋洋地 . 这种方法的优点是,可以从数据库中检索对象,而不必一次加载所有属性或相关数据,而只需在那时传递请求的数据。主要缺点基本上是优势的镜像,也就是说,如果加载了许多已知在所有情况下都需要特定数据集的对象,则逐段加载这些额外数据是浪费的。
除了通常的效率问题之外,延迟加载的另一个警告是,为了继续进行延迟加载,对象必须 与会话保持关联 以便能够检索其状态。此错误消息表示某个对象已与它的 Session
并被要求从数据库中延迟加载数据。
最常见的原因是对象从 Session
会话本身已关闭,通常通过 Session.close()
方法。然后,这些对象将继续存在以供进一步访问,通常在Web应用程序中,这些对象被传递到服务器端模板引擎,并被要求提供它们无法加载的其他属性。
通过两种通用技术来缓解此错误:
不要过早关闭会话 -通常,应用程序会在将相关对象传递给其他系统之前关闭一个事务,然后由于这个错误而失败。有时事务不需要这么快关闭;例如,Web应用程序在呈现视图之前关闭事务。这通常是以“正确性”的名义进行的,但可能被视为“封装”的错误应用,因为这个术语指的是代码组织,而不是实际操作。使用ORM对象的模板正在使用 proxy pattern 这样就可以从调用者那里封装数据库逻辑。如果 Session
可以一直打开直到对象的寿命结束,这是最好的方法。
把所有需要的东西提前装上 -通常不可能保持事务的开放性,尤其是在更复杂的应用程序中,这些应用程序需要将对象传递给其他系统,而这些系统即使在同一进程中也无法在同一上下文中运行。在这种情况下,应用程序应该尝试适当地使用 eager loading 以确保对象在前面具有所需的内容。作为附加措施,特殊指令如 raiseload()
选项可以确保系统不会在不期望的情况下调用延迟加载。
参见
关系加载技术 -有关预加载和其他面向关系加载技术的详细文档