SQLAlchemy连接错误

9
我遇到了一些奇怪的错误,似乎是由Sqlalchemy使用的连接引起的,但我无法确定确切原因。希望有人能知道这里发生了什么。
我们正在使用Pyramid(版本1.5b1),并且使用Sqlalchemy(版本0.9.6)进行所有数据库连接。有时我们会出现与数据库连接或会话相关的错误,大多数情况下会出现“游标已关闭”或“此连接已关闭”的错误,但我们还会遇到其他相关的异常:
(OperationalError) connection pointer is NULL
(InterfaceError) cursor already closed
Parent instance <...> is not bound to a Session, and no contextual session is established; lazy load operation of attribute '...' cannot proceed

A conflicting state is already present in the identity map for key (<class '...'>, (1001L,))
This Connection is closed (original cause: ResourceClosedError: This Connection is closed)
(InterfaceError) cursor already closed
Parent instance <...> is not bound to a Session; lazy load operation of attribute '...' cannot proceed
Parent instance <...> is not bound to a Session, and no contextual session is established; lazy load operation of attribute '...' cannot proceed
'NoneType' object has no attribute 'twophase'
(OperationalError) connection pointer is NULL
This session is in 'prepared' state; no further

没有什么万能的方法可以重现它们,只有通过多次刷新才有可能在某个时间点发生。因此,我使用multi-mechanize编写了一个脚本,同时垃圾邮件不同的url,以查看何时何地会发生。

似乎触发的url并不重要,错误发生在同时跨越较长时间的并发请求时(其他请求在其中得到服务)。这似乎表明存在某种线程问题;即会话或连接在不同的线程之间共享。

在搜索这些问题后,我找到了很多主题,大部分都建议使用作用域会话,但事实上我们已经在使用它们:

db_session = scoped_session(sessionmaker(extension=ZopeTransactionExtension(), autocommit=False, autoflush=False))
db_meta = MetaData()
  • 我们为所有 orm 对象都有一个 BaseModel:

    BaseModel = declarative_base(cls=BaseModelObj, metaclass=BaseMeta, metadata=db_meta)

  • 我们使用 pyramid_tm tween 来处理请求期间的事务

  • 我们将 db_session.remove() 钩子挂在了 pyramid NewResponse 事件上(该事件在所有事情运行之后触发)。我也尝试过将其放在运行在 pyramid_tm 之后的单独的 tween 中,甚至不做任何操作,但这些都似乎没有效果,因此响应事件似乎是最干净的放置位置。

  • 我们在 pyramid 项目的主入口处创建引擎,并使用 NullPool 并将连接池留给 pgbouncer。我们还在此处为我们的 BaseModel 配置会话和绑定:

    engine = engine_from_config(config.registry.settings,'sqlalchemy.', poolclass=NullPool) db_session.configure(bind=engine, query_cls=FilterQuery) BaseModel.metadata.bind = engine config.add_subscriber(cleanup_db_session, NewResponse) return config.make_wsgi_app()

  • 在我们的应用中,我们使用以下访问所有数据库操作:

    from project.db import db_session ... db_session.query(MyModel).filter(...) db_session.execute(...)

  • 我们使用 psycopg2==2.5.2 处理与中介 pgbouncer 的 postgres 连接

  • 我确保没有将 db_session 或连接的引用保存在任何地方(这可能导致其他线程重复使用它们)

我还尝试了使用不同的 Web 服务器进行垃圾邮件测试,使用 waitress 和 cogen 很容易出现错误,使用 wsgiref 我们毫不意外地没有错误(这是单线程的)。使用 uwsgi 和 gunicorn(4 个 worker,gevent)我没有遇到任何错误。

考虑到使用不同的 Web 服务器时存在差异,我认为这可能与一些 Web 服务器在线程中处理请求,而另一些则使用新进程有关(也许是一个分叉问题)?更让事情变得复杂的是,当时间过去并且我做了一些新的测试时,问题在 waitress 中已经消失了,但现在在使用 gunicorn(使用 gevent 时)时出现了!我不知道如何进行调试...

最后,为了测试连接发生了什么,我在光标执行的开头附加了一个属性,并尝试在执行结束时读取该属性:

@event.listens_for(Engine, "before_cursor_execute")
def _before_cursor_execute(conn, cursor, stmt, params, context, execmany):
  conn.pdtb_start_timer = time.time()

@event.listens_for(Engine, "after_cursor_execute")
def _after_cursor_execute(conn, cursor, stmt, params, context, execmany):
  print conn.pdtb_start_timer

令人惊讶的是,有时会出现异常:'Connection'对象没有属性'pdtb_start_timer'
这让我感到非常奇怪... 我找到了一个关于类似问题的讨论:https://groups.google.com/d/msg/sqlalchemy/GQZSjHAGkWM/rDflJvuyWnEJ并尝试将strategy='threadlocal'添加到引擎中,据我所知,这应该强制每个线程使用1个连接。但它对我看到的错误没有任何影响.. (除了一些单元测试失败,因为我需要为某些测试使用两个不同的会话/连接,并强制将1个连接关联)
有人有任何想法可能在这里发生什么或者有更多关于如何解决这个问题的指针吗?
提前感谢!
Matthijs Blaas

我有一个问题的更新,现在我只在使用gevent模式的gunicorn时始终收到“(InterfaceError) cursor already closed”异常:https://groups.google.com/d/msg/sqlalchemy/7szX4cbw2Ho/qBHcxYAfDgcJ - Matthijs Blaas
1个回答

4

更新:错误是由于在一个预准备的SQL语句中发送了多个命令导致的。Psycopg2似乎允许这样做,但显然会造成奇怪的问题。PG8000连接器更加严格,在多个命令时退出,发送一个命令即可解决问题!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接