在更改 SQLAlchemy Session 从每次请求都建立到共享同一个 Session 以后遇到了以下问题:html
StatementError: (sqlalchemy.exc.InvalidRequestError) Can’t reconnect until invalid transaction is rolled back [SQL: ]python
或者是mysql
raised unexpected: OperationalError(“(_mysql_exceptions.OperationalError) (2006, ‘MySQL server has gone away’)”,)web
错误是 SQLAlchemy 抛出。缘由是你从 pool 拿的 connection 没有以 session.commit 或 session.rollback 或者 session.close 放回 pool 里。这时 connection 的 transaction 没有完结(rollback or commit)。 而不知什么缘由(recyle 了,timeout 了)你的 connection 又死掉了,你的 sqlalchemy 尝试从新链接。因为 transaction 还没完结,没法重连。sql
正确用法是确保 session 在使用完成后用 session.close, session.commit 或者 session.rollback 把链接还回 pool。数据库
sessions 和 connections 不是相同的东西, session 使用链接来操做数据库,一旦任务完成 session 会将数据库 connection 交还给 pool。安全
在使用 create_engine
建立引擎时,若是默认不指定链接池设置的话,通常状况下,SQLAlchemy 会使用一个 QueuePool 绑定在新建立的引擎上。并附上合适的链接池参数。服务器
在以默认的方法 create_engine 时(以下),就会建立一个带链接池的引擎。session
engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname')
在这种状况下,当你使用了 session 后就算显式地调用 session.close(),也不能把链接关闭。链接会由 QueuePool 链接池进行管理并复用。并发
这种特性在通常状况下并不会有问题,不过当数据库服务器由于一些缘由进行了重启的话。最初保持的数据库链接就失效了。随后进行的 session.query() 等方法就会抛出异常致使程序出错。
若是想禁用 SQLAlchemy 提供的数据库链接池,只须要在调用 create_engine 是指定链接池为 NullPool,SQLAlchemy 就会在执行 session.close() 后马上断开数据库链接。固然,若是 session 对象被析构可是没有被调用 session.close(),则数据库链接不会被断开,直到程序终止。
下面的代码就能够避免 SQLAlchemy 使用链接池:
#!/usr/bin/env python #-*- coding: utf-8 -*- from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import NullPool engine = create_engine('mysql+mysqldb://root:password@127.0.0.1:3306/dbname', poolclass=NullPool) Session = sessionmaker(bind=engine) session = Session() usr_obj_list = session.query(UsrObj).all() print usr_obj_list[0].id session.close()
create_engine()
函数和链接池相关的参数有:
-pool_recycle
, 默认为 -1, 推荐设置为 7200, 即若是 connection 空闲了 7200 秒,自动从新获取,以防止 connection 被 db server 关闭。-pool_size=5
, 链接数大小,默认为 5,正式环境该数值过小,需根据实际状况调大-pool_timeout=30
, 获取链接的超时阈值,默认为 30 秒直接只用 create_engine
时,就会建立一个带链接池的引擎
engine = create_engine('postgresql://postgres@127.0.0.1/dbname')
当使用 session 后就显示地调用 session.close(),也不能把链接关闭,链接由 QueuePool 链接池管理并复用。
引起问题
当数据库重启,最初保持的链接就会失败,随后进行 session.query()
就会失败抛出异常 mysql 数据 ,interactive_timeout 等参数处理链接的空闲时间超过(配置时间),断开
基本
确保 transaction 有很是清晰的开始和结束,保持 transaction 简短,也就意味着让 transaction 能在一系列操做以后终止,而不是一直开放着。
from contextlib import contextmanager
@contextmanager def session_scope(): “"”Provide a transactional scope around a series of operations.””” session = Session() try: yield session session.commit() except: session.rollback() raise finally: session.close()
Session 不是为了线程安全而设计的,所以确保只在同一个线程中使用。
若是实际上有多个线程参与同一任务,那么您考虑在这些线程之间共享 Session 及其对象;可是在这种极不寻常的状况下,应用程序须要确保实现正确的 locking scheme,以便不会同时访问 Session 或其状态。处理这种状况的一种更常见的方法是为每一个并发线程维护一个 Session,而是将对象从一个 Session 复制到另外一个 Session,一般使用 Session.merge() 方法将对象的状态复制到本地的新对象中。
想要线程安全时使用 scoped_session()
,文档解释
the scoped_session() function is provided which produces a thread-managed registry of Session objects. It is commonly used in web applications so that a single global variable can be used to safely represent transactional sessions with sets of objects, localized to a single thread.
using transactional=False is one solution, but a better one is to simply rollback(), commit(), or close() the Session when operations are complete - transactional mode (which is called “autocommit=False” in 0.5) has the advantage that a series of select operations will all share the same isolated transactional context..this can be more or less important depending on the isolation mode in effect and the kind of application.
DBAPI has no implicit “autocommit” mode so there is always a transaction implicitly in progress when queries are made.
This would be a fairly late answer. This is what happens: While using the session, a sqlalchemy Error is raised (anything which would also throw an error when be used as pure SQL: syntax errors, unique constraints, key collisions etc.).
You would have to find this error, wrap it into a try/except-block and perform a session.rollback().
After this you can reinstate your session.