【问题标题】:"This session is in 'prepared' state; no further" error with SQLAlchemy using scoped_session in threaded mod_wsgi app“此会话处于'准备'状态;没有更多”在线程 mod_wsgi 应用程序中使用 scoped_session 的 SQLAlchemy 错误
【发布时间】:2016-12-31 19:05:39
【问题描述】:

我最近更新到我在 Django 1.10 下使用的 SQLAlchemy 1.1(最近也从 1.6 更新),并且我不断收到This session is in 'prepared' state; no further SQL can be emitted within this transaction. 的 sqlalchemy/mysql 错误

如何调试?

它在 mod_wsgi 下的单进程、多线程环境中运行 - 我不确定我是否正确配置了 SQLAlchemy 的 scoped_session。

我使用分配给每个传入请求的请求容器,它设置会话并清理它。 (我假设 Django 中的每个请求都在它自己的线程上。)

# scoped_session as a global variable
# I constant errors if pool_size = 20 for some reason
Engine = create_engine(host, pool_recycle=600, pool_size=10, connect_args=options)
Session = scoped_session(sessionmaker(autoflush=True, bind=Engine))
RUNNING_DEVSERVER = (len(sys.argv) > 1 and sys.argv[1] == 'runserver') # Session.remove() fails in dev

# Created in my API, once per request (per thread)
class RequestContainer(object):
    def __init__(self, request, *args, **kwargs):
        self.s = Session()

    def safe_commit(self):
        try:
            self.s.commit()
        except:
            self.s.rollback()
            raise

    def __del__(self):
        if self.s:
            try:
                self.s.commit()
            except:
                self.s.rollback()
                raise

            if not RUNNING_DEVSERVER:
                Session.remove()
            self.s = None

而且代码中会弹出prepared state错误,一般在同一个地方,但不是一直,有时在其他地方:

...
rs = request_container.s.query(MyTable)
...
if rs.count():
# Error log:
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 3011, in count
    return self.from_self(col).scalar()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2765, in scalar
    ret = self.one()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2736, in one
    ret = self.one_or_none()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2706, in one_or_none
    ret = list(self)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2777, in __iter__
    return self._execute_and_instances(context)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2798, in _execute_and_instances
    close_with_result=True)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2807, in _get_bind_args
    **kw
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2789, in _connection_from_session
    conn = self.session.connection(**kw)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 903, in connection
    execution_options=execution_options)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 908, in _connection_for_bind
    engine, execution_options)
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 319, in _connection_for_bind
    self._assert_active()
  File "/usr/local/lib/python2.7/dist-packages/SQLAlchemy-1.1.0b3-py2.7-linux-x86_64.egg/sqlalchemy/orm/session.py", line 201, in _assert_active
    "This session is in 'prepared' state; no further "
InvalidRequestError: This session is in 'prepared' state; no further SQL can be emitted within this transaction.

【问题讨论】:

  • 没关系 - RequestContainer 被意外分配给全局 API 接口处理程序,导致一个会话在多个线程之间被误用。

标签: python mysql django sqlalchemy mod-wsgi


【解决方案1】:

RequestContainer 被意外分配给全局 API 接口处理程序,导致一个会话在多个线程之间被误用,而该会话原本是按线程创建的。

更新以显示我如何将会话分配给每个线程,包括拆卸以防止数据库提交错误挂起会话状态:

class ThreadSessionRequest(object):
    def __init__(self, request, *args, **kwargs):
        self.s = Session()

        def __del__(self):
            if self.s:
                self.remove_session()

        def remove_session(self):
            if self.s:
                try:
                    self.safe_commit()
                finally:
                    Session.remove()
                    del self.s
                    self.s = None

        def safe_commit(self):
            if self.s:
                try:
                    self.s.commit()
                except:
                    self.s.rollback()
                    raise

【讨论】:

  • 这是否意味着我必须在线程之间创建不同的会话?
  • @divij-sehgal 创建一个全局Session = scoped_session(...),然后在每个线程中,从该s = Session() 创建您自己的连接——在apache / mod_wsgi 上的线程中,我通常会创建一个单个s = Session() 用于线程,所以不要用完连接......并处理可能挂起状态的提交错误。我已经更新了答案以显示我的工作。
  • 知道了。这就是我基本上需要的。谢谢@亚当
猜你喜欢
  • 2018-03-25
  • 1970-01-01
  • 2012-03-26
  • 2016-10-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-29
  • 1970-01-01
相关资源
最近更新 更多