最近在使用sqlalchemy,关于select * for update
有一些疑惑的地方,记录在此。
当Isolation Level是RR级别时,在一个会话里是允许幻读的,即select * from x
的结果是会话刚开始的一个快照,无法读取其他会话修改commit的内容。
如果需要当前读,可以使用select * for update
。
但是当前读在使用sqlalchemy里的with_lockmode时却表现不一致,即仍然是快照读。问题的原因我一直没有想出来,希望看到的朋友能够指点下。
首先创建一个测试用表:
CREATE TABLE `user_test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` char(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
插入几条数据:
insert into `user_test` (name) values ('a');
insert into `user_test` (name) values ('b');
insert into `user_test` (name) values ('c');
sqlalchemy对应的model:
class User(BaseModel):
__tablename__ = 'user_test'
__table_args__ = {
'mysql_engine': 'InnoDB'
}
id = Column(Integer, primary_key=True)
name = Column(CHAR(30))
def __str__(self):
return 'User[%d, %s]' % (self.id, self.name)
数据构造完毕后,写了一个简单的测试用例如下:
session = DB_Session()
u = session.query(User).filter(User.name == 'a').first()
locking_u = session.query(User).\
with_for_update().\
filter(User.id == u.id).\
first()
for u in engine.connect().execute("select * from user_test where id = %d" % u.id):
print u
if locking_u and locking_u.name == 'a':
print 'need to change'
locking_u.name = 'aa'
else:
print 'no need to change'
time.sleep(10)
session.commit()
测试过程如下:
- terminal-1运行该程序,在L14 sleep 10秒,然后释放x锁.
- terminal-2运行该程序,在L3-L6等待x锁释放,然后检测锁定的这行的值,如果没有改变则修改。如果已经修改,则只打印。
预期terminal-1在L15session.commit()
释放x锁后,terminal-2读到的值是terminal-1修改后的值,不应做改动。
但实际上读到的仍然是修改前的值,与mysql预期的行为不符。同时L52-L53的通过connection而不是session的输出可以看到terminal-1修改后的值的,说明修改已经生效了。
解决方法:
- 在L48-L51加锁时增加一句话:populate_existing,但该函数是否会导致其他问题,我还没有搞清楚。
- 在查找u的时候,即L2,不是查找整行,而是只查找u的id列,即修改为:
u_id = session.query(User.id).filter(User.name == 'a').first()
对应的L48-L51直接使用u_id,不再使用u.id。
根据以上两点猜测可能跟sqlalchemy的对象的生效期有关?希望了解的朋友留言告诉我,谢谢。
PREVIOUScodeforces Contest 479
NEXT高级Bash脚本编程指南读书笔记