(对不起提前提出这个问题。我试着把它分成几部分,以便让我更清楚我要问的。如果我应该添加任何其他内容或者重新组织它,请告诉我。)
背景
我正在编写一个Web爬虫,它使用生产者/消费者模型,其中包含存储在名为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)
(显然不正确)期望:
我原本希望从记录中读取值然后在同一事务中更新该值:
问题:
答案 0 :(得分:1)
PostgreSQL有select . . . for update
,SQLAlchemy似乎支持。
我的理由是所有交易都应该表现得像 尽可能按提交顺序独立执行,或 应该没有提交。
嗯,总的来说交易比这更多。 PostgreSQL的默认事务隔离级别是" read committed"。简而言之,这意味着多个事务可以同时从表中的相同行读取提交的值。如果要阻止set transaction isolation serializable
(可能不起作用)或select...for update
,或锁定表,或使用逐列WHERE子句或其他任何内容。
您可以通过打开两个psql连接来测试和演示事务行为。
begin transaction; begin transaction; select * from test where pid = 1 and date = '2014-10-01' for update; (1 row) select * from test where pid = 1 and date = '2014-10-01' for update; (waiting) update test set date = '2014-10-31' where pid = 1 and date = '2014-10-01'; commit; -- Locks released. SELECT for update fails. (0 rows)