我正在使用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()
我一直面临着会话的奇怪问题。示例错误消息包括
我想做的就是将内存中的内容提交到数据库中。但间歇性的SQLAlchemy会抛出如上所述的错误而无法提交。
我的方法有问题吗?
答案 0 :(得分:0)
tl; dr 问题在于您在线程之间共享一个Session
对象。它失败了,因为Session
对象is not thread-safe本身。
您创建一个Session对象,该对象绑定到当前线程(让我们称之为Thread-1
)。然后你在_BaseMixin
内关闭它。传入请求在不同的线程中处理(让我们称之为Thread-2
和Thread-3
)。处理请求时,请致电model.save()
,该Session
使用Thread-1
或Thread-2
Thread-3
中创建的Session
个对象。多个请求可以并发运行,这与线程不安全的scoped_session()
对象一起为您提供完全不确定的行为。
使用Session()
时,每次使用session = Session()
创建新对象时,它都将绑定到当前线程。此外,如果有一个会话绑定到当前线程,它将返回现有会话而不是创建新会话。
因此,您可以将save()
从模块级别移至delete()
和class _BaseMixin(object):
def save(self):
session = Session()
session.add(self)
session.commit()
def delete(self):
session = Session()
session.delete(self)
session.commit()
方法。它将确保您始终使用当前线程的会话。
Session
但它看起来像重复,并且创建class _BaseMixin(object):
def save(self):
Session.add(self)
Session.commit()
def delete(self):
Session.delete(self)
Session.commit()
对象也没有意义(它将始终在当前线程内返回相同的对象)。因此SA为您提供implicitly访问当前线程会话的能力。它产生更清晰的代码。
Session
请注意,对于普通应用,您从不想要明确创建Session.some_method()
个对象。但是希望使用def exp(numbers):
total = 0
for n in numbers:
if n < 10000:
total = total + 1
return total
x = int(input("Enter a number: "), 10)
exp(x)
print(exp)
来隐式访问线程本地会话。