在大多数情况下,SQLAlchemy会自动执行应用程序级连接池。除SQLite以外,Engine
对象将QueuePool
作为连接的来源。
有关更多详细信息,请参阅Engine Configuration和Connection Pooling。
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")
这个错误有两个主要原因:
1. MySQL客户端关闭已经空闲一段时间的连接,默认为八个小时。这可以通过在Connection Timeouts中描述的create_engine()
使用pool_recycle
设置来避免。
2. MySQLdb DBAPI或类似的DBAPI以非线程安全的方式使用,或以其他不适当的方式使用。MySQLdb连接对象不是线程安全的 - 这扩展到任何链接到包含ORM Session
的单个连接的SQLAlchemy系统。有关如何在多线程环境中使用Session
的背景信息,请参阅Is the session thread-safe?。
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))
除了True
,False
以外,reset_on_return
还接受commit
,rollback
None
设置为commit
会导致COMMIT,因为任何连接都会返回到池:
engine = create_engine('mssql://scott:tiger@mydsn', pool=QueuePool(reset_on_return='commit'))
如果使用SQLite :memory:
数据库或0.7版之前的SQLAlchemy版本,则默认连接池是SingletonThreadPool
,每个线程只维护一个SQLite连接。因此,在同一个线程中使用的两个连接实际上是相同的SQLite连接。确保你没有使用:memory:数据库,并使用NullPool
,这是当前SQLAlchemy版本中非内存数据库的默认值。
也可以看看
Threading/Pooling Behavior - info on PySQLite’s behavior.
使用常规的SA引擎级连接,您可以通过Connection
上的Connection.connection
属性获取DBAPI连接的池代理版本,真正的DBAPI连接可以调用ConnectionFairy.connection
属性 - 但不应该有任何需要访问非池代理的DBAPI连接,因为所有方法都通过以下方式代理:
engine = create_engine(...)
conn = engine.connect()
conn.connection.<do DBAPI things>
cursor = conn.connection.cursor(<DBAPI specific arguments..>)
您必须确保将连接上的任何隔离级别设置或其他特定于操作的设置恢复为正常状态,然后才能将其返回到池。
作为还原设置的替代方法,您可以在Connection
或代理连接上调用Connection.detach()
方法,该方法将从池中断开连接当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进程的关键目标是防止跨进程共享任何数据库连接。根据驱动程序和操作系统的具体情况,这里出现的问题包括从非工作连接到多个进程并发使用的套接字连接,导致断开的消息传递(后者通常最常见)。
SQLAlchemy Engine
对象引用现有数据库连接的连接池。所以当这个对象被复制到一个子进程时,目标是确保没有数据库连接继续。有三种通用的方法:
在任何给定的Engine
上调用Engine.dispose()
,只要在新进程中。在Python多处理中,像multiprocessing.Pool
这样的构造包含了可以执行这个操作的地方的“初始化器”钩子;否则在os.fork()
的顶部或者Process
对象开始子fork的位置,对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
在进程之间共享的情况。However, for the case of a transaction-active Session
or Connection
being shared, there’s no automatic fix for this; an application needs to ensure a new child process only initiate new Connection
objects and transactions, as well as ORM Session
objects. 对于Session
对象,从技术上讲,只有在会话当前是事务绑定的情况下才需要这样做,但是单个Session
的范围无论如何都是要保留在单个调用堆栈(例如,不是全局对象,不在进程或线程之间共享)。