【发布时间】:2015-04-03 00:06:47
【问题描述】:
(抱歉,这个问题太长了。我试着把它分成几个部分,以便更清楚地说明我在问什么。如果我应该添加任何其他内容或重新组织它,请告诉我。)
背景:
我正在编写一个网络爬虫,它使用生产者/消费者模型和存储在名为 crawler_table 的 postgresql 数据库表中的作业(要爬取或重新爬取的页面)。我正在使用 SQLAlchemy 访问和更改数据库表。确切的架构对于这个问题并不重要。重要的是我(将)有多个消费者,每个消费者重复从表中选择一条记录,用 phantomjs 加载页面,然后将有关页面的信息写回记录。
有时可能会发生两个消费者选择同一个工作。这本身不是问题;但是,重要的是,如果他们同时使用结果更新记录,那么他们必须做出一致的更改。对我来说,只要找出更新是否会导致记录变得不一致就足够了。如果是这样,我可以处理它。
调查:
我最初假设如果不同会话中的两个事务读取然后同时更新同一记录,则第二个提交将失败。为了测试这个假设,我运行了以下代码(稍微简化了):
SQLAlchemySession = sessionmaker(bind=create_engine(my_postgresql_uri))
class Session (object):
# A simple wrapper for use with `with` statement
def __enter__ (self):
self.session = SQLAlchemySession()
return self.session
def __exit__ (self, exc_type, exc_val, exc_tb):
if exc_type:
self.session.rollback()
else:
self.session.commit()
self.session.close()
with Session() as session: # Create a record to play with
if session.query(CrawlerPage) \
.filter(CrawlerPage.url == 'url').count() == 0:
session.add(CrawlerPage(website='website', url='url',
first_seen=datetime.utcnow()))
page = session.query(CrawlerPage) \
.filter(CrawlerPage.url == 'url') \
.one()
page.failed_count = 0
# commit
# Actual experiment:
with Session() as session:
page = session.query(CrawlerPage) \
.filter(CrawlerPage.url == 'url') \
.one()
print 'initial (session)', page.failed_count
# 0 (expected)
page.failed_count += 5
with Session() as other_session:
same_page = other_session.query(CrawlerPage) \
.filter(CrawlerPage.url == 'url') \
.one()
print 'initial (other_session)', same_page.failed_count
# 0 (expected)
same_page.failed_count += 10
print 'final (other_session)', same_page.failed_count
# 10 (expected)
# commit other_session, no errors (expected)
print 'final (session)', page.failed_count
# 5 (expected)
# commit session, no errors (why?)
with Session() as session:
page = session.query(CrawlerPage) \
.filter(CrawlerPage.url == 'url') \
.one()
print 'final value', page.failed_count
# 5 (expected, given that there were no errors)
(显然不正确)预期:
我原以为从记录中读取一个值然后在同一个事务中更新该值会:
- 是一个原子操作。也就是说,要么完全成功,要么完全失败。这似乎是真的,因为最终值是 5,这是在要提交的最后一个事务中设置的值。
- 如果正在更新的记录在尝试提交事务时由并发会话 (other_session) 更新,则会失败。我的理由是,所有事务都应该表现得好像它们尽可能按照提交顺序独立执行,或者应该无法提交。在这些情况下,读取的两个事务会更新同一记录的相同值。在版本控制系统中,这相当于合并冲突。显然,数据库与版本控制系统不同,但它们有足够的相似性来说明我对它们的一些假设,无论好坏。
问题:
- 为什么第二次提交没有引发异常?
- 我对 SQLAlchemy 如何处理事务有误解吗?
- 我对 postgresql 如何处理事务有误解吗? (我觉得这个最有可能。)
- 还有别的吗?
- 有没有办法让第二次提交引发异常?
【问题讨论】:
标签: python postgresql orm transactions sqlalchemy