这个 create_engine()
调用接受其他参数,或者直接通过 connect_args
关键字参数:
e = create_engine("mysql://scott:tiger@localhost/test",
connect_args={"encoding": "utf8"})
或者对于基本的字符串和整数参数,通常可以在URL的查询字符串中指定它们:
e = create_engine("mysql://scott:tiger@localhost/test?encoding=utf8")
此错误的主要原因是MySQL连接已超时,并且已被服务器关闭。mysql服务器关闭空闲一段时间的连接,默认为8小时。为了适应这种情况,立即设置为启用 create_engine.pool_recycle
设置,这将确保早于设置的秒数的连接将被丢弃,并在下次签出时替换为新连接。
对于适应数据库重新启动和其他由于网络问题导致的临时连接丢失的更一般的情况,池中的连接可以根据更普遍的断开检测技术进行回收。断面 处理断开连接 提供“悲观”(如预ping)和“乐观”(如优雅恢复)技术的背景。现代的SQLAlchemy倾向于“悲观”的方法。
参见
MySQL驱动程序有相当广泛的故障模式,其中到服务器的连接状态处于无效状态。通常,当再次使用连接时,会出现这两条错误消息之一。原因是服务器的状态已更改为客户机库不期望的状态,这样当客户机库在连接上发出新语句时,服务器不会按预期响应。
在sqlAlchemy中,由于数据库连接被合并,连接上的消息传递不同步的问题变得更加重要,因为当一个操作失败时,如果连接本身处于不可用状态,如果它返回到连接池,当再次签出时,它将出现故障。这个问题的缓解措施是 失效的 当出现这样的故障模式时,将放弃与MySQL的底层数据库连接。对于许多已知的故障模式,这种失效会自动发生,也可以通过 Connection.invalidate()
方法。
在这个类别中还有第二类失败模式,其中上下文管理器如 with session.begin_nested():
希望在发生错误时“回滚”事务;但是在连接的某些失败模式中,回滚本身(也可以是释放保存点操作)也会失败,从而导致堆栈跟踪误导。
最初,这个错误的原因相当简单,它意味着多线程程序正在从多个线程对单个连接调用命令。这适用于原始的“mysqldb”本机C驱动程序,它几乎是唯一使用的驱动程序。但是,随着pymysql和mysql connector python等纯python驱动程序的引入,以及gevent/eventlet、多处理(通常使用celery)等工具的使用量的增加,导致这个问题的因素有一系列,其中一些已经在sqlachemy版本中得到了改进,但是其他的一些已经在sqlachemy版本中得到了改进。不可避免的风险:
在线程之间共享连接 -这就是发生这些错误的原始原因。一个程序同时在两个或多个线程中使用相同的连接,这意味着连接上的多组消息混合在一起,使服务器端会话处于客户机不再知道如何解释的状态。然而,其他原因在今天通常更为可能。
共享进程间连接的文件句柄 -这通常发生在程序使用 os.fork()
为了产生一个新进程,父进程中存在的TCP连接将被共享到一个或多个子进程中。由于多个进程现在向基本相同的filehandle发送消息,服务器接收交错消息并中断连接状态。
如果程序使用python的“多处理”模块并使用 Engine
在父进程中创建的。在使用芹菜之类的工具时,“多处理”很常见。正确的方法应该是 Engine
在子进程首次启动时生成,丢弃任何 Engine
来自父进程的;或 Engine
从父进程继承的可以通过调用释放其内部连接池 Engine.dispose()
.
Greenlet Monkeypatching w/ Exits -当使用像gevent或eventlet这样的库(monkeypatches the python networking api)时,pymysql这样的库现在正以异步操作模式工作,即使它们不是针对该模型显式开发的。一个常见的问题是一个greenthread被中断,通常是由于应用程序中的超时逻辑。这导致 GreenletExit
引发异常,纯python mysql驱动程序的工作被中断,这可能是因为它正在从服务器接收响应,或者正在准备以其他方式重置连接状态。当异常将所有工作时间缩短时,客户机和服务器之间的对话现在不同步,后续的连接使用可能会失败。从1.1.0版开始的SQLAlchemy知道如何防范这种情况,就像数据库操作被一个所谓的“退出异常”中断一样,其中包括 GreenletExit
以及python的任何其他子类 BaseException
这也不是 Exception
,连接无效。
Rollbacks / SAVEPOINT releases failing -某些类型的错误会导致连接在事务上下文中以及在“保存点”块中操作时不可用。在这些情况下,连接失败导致任何保存点不再存在,但是当SQLAlchemy或应用程序尝试“回滚”此保存点时,“释放保存点”操作失败,通常会显示一条消息,如“保存点不存在”。在这种情况下,在python 3下将有一系列异常输出,其中也将显示错误的最终“原因”。在python 2下,没有“链接”异常,但是最近版本的sqlAlchemy将尝试发出一个警告,说明原始失败原因,同时仍然抛出即时错误,即回滚失败。
SQLAlchemy当前假定DBAPI连接处于“非自动提交”模式-这是Python数据库API的默认行为,这意味着必须假定事务始终在进行中。连接池问题 connection.rollback()
当返回连接时。这是为了释放连接上剩余的任何事务性资源。在像postgresql或mssql这样的数据库中,表资源被严重锁定,这是非常重要的,这样行和表就不会在不再使用的连接中保持锁定。否则,应用程序可能挂起。但是,它不仅适用于锁,而且对于任何具有任何事务隔离的数据库(包括使用innodb的mysql)同样重要。如果在隔离的连接中已经查询了旧事务中的数据,那么仍然在旧事务中的任何连接都将返回过时的数据。有关即使在MySQL上也可能看到过时数据的背景,请参阅http://dev.mysql.com/doc/refman/5.1/en/innodb-transaction-model.html
连接池的连接返回行为可以使用配置 reset_on_return
::
from sqlalchemy import create_engine
from sqlalchemy.pool import QueuePool
engine = create_engine('mysql://scott:tiger@localhost/myisam_database', pool=QueuePool(reset_on_return=False))
reset_on_return
接受值 commit
, rollback
除了 True
, False
和 None
. 设置为 commit
将导致提交,因为任何连接都将返回池::
engine = create_engine('mssql://scott:tiger@mydsn', pool=QueuePool(reset_on_return='commit'))
如果使用sqlite :memory:
数据库或0.7版之前的SQLAlchemy版本,默认连接池是 SingletonThreadPool
,每个线程只维护一个SQLite连接。所以在同一线程中使用的两个连接实际上是同一个sqlite连接。确保您没有使用 :memory: database and use NullPool
这是当前SQLAlchemy版本中非内存数据库的默认值。
参见
线程/池行为 -关于pysqlite行为的信息。
通过常规的SA引擎级连接,您可以通过 Connection.connection
属性对 Connection
对于真正的DBAPI连接,可以调用 ConnectionFairy.connection
属性-但不需要访问非池代理DBAPI连接,因为所有方法都是通过以下方式代理的:
engine = create_engine(...)
conn = engine.connect()
conn.connection.<do DBAPI things>
cursor = conn.connection.cursor(<DBAPI specific arguments..>)
在将连接恢复到池之前,必须确保将连接上的任何隔离级别设置或其他操作特定设置恢复为正常设置。
作为恢复设置的替代方法,您可以调用 Connection.detach()
方法 Connection
或代理连接,它将取消与池中的连接的关联,以便在 Connection.close()
被称为:
conn = engine.connect()
conn.detach() # detaches the DBAPI connection from the connection pool
conn.connection.<go nuts>
conn.close() # connection is closed for real, the pool replaces it with a new connection
多个Python进程的关键目标是防止任何数据库连接在进程间共享。根据驱动程序和操作系统的具体情况,这里出现的问题从非工作连接到多个进程同时使用的套接字连接,从而导致消息传递中断(后者通常是最常见的情况)。
圣卢西亚 Engine
对象引用现有数据库连接的连接池。因此,当将此对象复制到子进程时,目标是确保不传递任何数据库连接。对此,有三种一般方法:
调用 Engine.dispose()
在任何给定的 Engine
一旦进入新的过程。在Python多处理中,构造 multiprocessing.Pool
包括“初始值设定项”挂钩,该挂钩是可以执行此操作的位置;否则在其中的顶部 os.fork()
或者在哪里 Process
对象开始子分叉,对 Engine.dispose()
将确保刷新所有剩余的连接。
事件处理程序可以应用于连接池,该池测试跨进程边界共享的连接,并使它们失效。如下所示:
import os
import warnings
from sqlalchemy import event
from sqlalchemy import exc
def add_engine_pidguard(engine):
"""Add multiprocessing guards.
Forces a connection to be reconnected if it is detected
as having been shared to a sub-process.
"""
@event.listens_for(engine, "connect")
def connect(dbapi_connection, connection_record):
connection_record.info['pid'] = os.getpid()
@event.listens_for(engine, "checkout")
def checkout(dbapi_connection, connection_record, connection_proxy):
pid = os.getpid()
if connection_record.info['pid'] != pid:
# substitute log.debug() or similar here as desired
warnings.warn(
"Parent process %(orig)s forked (%(newproc)s) with an open "
"database connection, "
"which is being discarded and recreated." %
{"newproc": pid, "orig": connection_record.info['pid']})
connection_record.connection = connection_proxy.connection = None
raise exc.DisconnectionError(
"Connection record belongs to pid %s, "
"attempting to check out in pid %s" %
(connection_record.info['pid'], pid)
)
这些事件应用于 Engine
一经创建:
engine = create_engine("...")
add_engine_pidguard(engine)
上述策略将适应 Engine
在进程之间共享。但是,对于活动事务的情况 Session
或 Connection
由于是共享的,因此没有自动修复;应用程序需要确保新的子进程只启动新的 Connection
对象和事务以及ORM Session
对象。对于一个 Session
对象,从技术上讲,仅当会话当前是事务绑定的,而单个 Session
在任何情况下都要保持在单个调用堆栈中(例如,不是全局对象,不在进程或线程之间共享)。