“此会话处于‘准备’状态;在线程 mod_wsgi 应用程序中使用 scoped_session 的 SQLAlchemy 没有进一步的”错误

Ada*_*ris 5 python mysql django mod-wsgi sqlalchemy

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

我该如何调试?

它在 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
Run Code Online (Sandbox Code Playgroud)

并且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.
Run Code Online (Sandbox Code Playgroud)

Ada*_*ris 4

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
Run Code Online (Sandbox Code Playgroud)

  • @divij-sehgal 创建一个全局 `Session =scoped_session(...)`,然后在每个线程中,从该 `s = Session()` 创建您自己的连接 - 在 apache / mod_wsgi 上的线程中,我将通常为线程创建一个“s = Session()”,这样就不会耗尽连接......并处理可能挂起状态的提交错误。我已经更新了答案以展示我所做的事情。 (2认同)