SQLAlchemy连接错误

Mat*_*aas 5 python multithreading sqlalchemy

我遇到了一些奇怪的错误,这些错误似乎是由Sqlalchemy使用的连接造成的,我无法准确确定..我希望有人有一个线索在这里发生什么.

我们正在开发Pyramid(版本1.5b1)并使用Sqlalchemy(版本0.9.6)进行所有数据库连接.有时我们会得到与数据库连接或会话相关的错误,大部分时间都是错误cursor already closedThis Connection is closed错误,但我们也得到其他相关的异常:

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

没有银弹可以重现它们,只有刷新很多次它们必然会在某个时刻发生.所以我使用multi-mechanize制作了一个脚本,同时发送不同的url并查看它发生的位置和时间.

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

在谷歌搜索这些问题后,我发现了很多主题,其中大多数都告诉我们使用范围会话,但问题是我们已经使用它们了:

db_session = scoped_session(sessionmaker(extension=ZopeTransactionExtension(), autocommit=False, autoflush=False))
db_meta = MetaData()
Run Code Online (Sandbox Code Playgroud)
  • 我们所有的orm对象都有一个BaseModel:

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

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

  • 我们将db_session.remove()挂钩到金字塔NewResponse事件(在一切运行后触发).我也尝试将它放在pyramid_tm之后运行的单独补间中,或者甚至根本不执行它,这些似乎都没有效果,所以响应事件似乎是最干净的地方.

  • 我们在金字塔项目的主要入口点创建引擎,并使用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处理与postgres的连接,其中包含pgbouncer

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

我还尝试使用不同的web服务器进行垃圾邮件测试,使用女服务员和cogen我很容易得到错误,使用wsgiref我们不出所料没有错误(这是单线程).使用uwsgi和gunicorn(4名工人,gevent)我没有得到任何错误.

鉴于所使用的网络服务器的差异,我认为它要么与一些处理线程请求的Web服务器有关,还有一些使用新进程(可能是分叉问题)?更复杂的事情,当时间继续,我做了一些新的测试,问题已经在女服务员消失了,但现在发生了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
Run Code Online (Sandbox Code Playgroud)

令人惊讶的是,这有时会引发异常:'Connection'对象没有属性'pdtb_start_timer'

这让我感到非常奇怪..我发现了一个关于类似内容的讨论:https://groups.google.com/d/msg/sqlalchemy/GQZSjHAGkWM/rDflJvuyWnEJ 并尝试将strategy ='threadlocal'添加到引擎中,该内容来自于什么我明白应该强制1连接胎面.但它对我看到的错误没有任何影响..(除了一些单元测试失败,因为我需要两个不同的会话/连接进行一些测试,这迫使1个连接被关联)

有没有人知道这里可能会发生什么,或者有更多关于如何解决这个问题的指示?

提前致谢!

Matthijs Blaas

Mat*_*aas 4

更新:错误是由在一个准备好的 sql 语句中发送的多个命令引起的。Psycopg2 似乎允许这样做,但显然它会导致奇怪的问题。PG8000 连接器更加严格,可以解决多个命令,发送一个命令即可解决问题!