我将 sqlalchemy 与 PostgreSQL 和 Pyramid Web 框架结合使用。这是我的 models.py
:
engine = create_engine(db_url, pool_recycle=3600,
isolation_level="READ UNCOMMITTED")
Session = scoped_session(sessionmaker(bind=engine))
session = Session()
Base = declarative_base()
Base.metadata.bind = engine
class _BaseMixin(object):
def save(self):
session.add(self)
session.commit()
def delete(self):
session.delete(self)
session.commit()
我为我的模型继承了 Base 和 _BaseMixin。例如:
class MyModel(Base, _BaseMixin):
__tablename__ = 'MY_MODELS'
id = Column(Integer, primary_key=True, autoincrement=True)
原因是我想做类似的事情
m = MyModel()
m.save()
我一直面临着奇怪的 session 问题。示例错误消息包括
- InvalidRequestError:此 session 处于“准备”状态;此事务中不能发出进一步的 SQL。
- InvalidRequestError:事务已开始。使用 subtransactions=True 允许子事务。
我想做的就是将内存中的内容提交到数据库中。但 SQLAlchemy 间歇性地抛出如上所述的错误并且无法提交。
我的方法有问题吗?
最佳答案
tl;dr 问题在于您在线程之间共享一个 Session
对象。它失败了,因为 Session
对象 is not thread-safe本身。
会发生什么?
您创建一个 Session 对象,该对象绑定(bind)到当前线程(我们将其称为Thread-1
)。然后在 _BaseMixin
中关闭它。传入请求在不同的线程中处理(我们称它们为Thread-2
和Thread-3
)。处理请求时,您调用 model.save()
,它使用在 Thread-1
中从 Thread-2 创建的
或Session
对象Thread-3
。多个请求可以同时运行,这与线程不安全的 Session
对象一起给您带来完全不确定的行为。
如何处理?
当使用scoped_session()
时,每次使用Session()
创建新对象时,它都会绑定(bind)到当前线程。此外,如果有一个 session 绑定(bind)到当前线程,它将返回现有 session 而不是创建新 session 。
因此,您可以将 session = Session()
从模块级别移动到 save()
和 delete()
方法。它将确保您始终使用当前线程中的 session 。
class _BaseMixin(object):
def save(self):
session = Session()
session.add(self)
session.commit()
def delete(self):
session = Session()
session.delete(self)
session.commit()
但它看起来像重复,并且创建 Session
对象也没有意义(它总是在当前线程内返回相同的对象)。因此 SA 为您提供 implicitly 的能力访问当前线程的 session 。它生成更清晰的代码。
class _BaseMixin(object):
def save(self):
Session.add(self)
Session.commit()
def delete(self):
Session.delete(self)
Session.commit()
另请注意,对于普通应用程序,您永远不想显式创建Session
对象。但想要通过使用 Session.some_method()
来隐式访问线程本地 session 。
进一步阅读
关于postgresql - 面临 sqlalchemy+postgresql session 管理问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32328354/