MySQL Python并发访问同一表列
我尝试了几种解决方案,但都没成功。我有两个 Python 进程(是用 subprocess 的 Popen 创建的),还有一个 MySQL 表(叫做 persone
),里面有一行数据,包含两个列:Age:0, id:1
。一个进程会选择这一行,取出年龄并把它加一,这个过程会重复 1000 次。另一个进程做的事情是把年龄减一,它也会重复 1000 次。我是同时运行这两个进程的。
理论上,我希望最后年龄能保持在零。但是,实际上我总是得到一些随机值,范围在 100 到 -100 之间,这让我觉得可能是因为同时有多个进程在访问数据库,导致数据出错。我在想,我是不是漏掉了什么?
这是我用来测试的代码:
ajoutAge.py
import MySQLdb as lite
num=1000
connexion = lite.connect(host="localhost", user="root", passwd="123", db="test")
with connexion:
for i in range(num):
cur = connexion.cursor()
cur.execute('start transaction')
cur.execute('select Age from persone where id=1')
age = cur.fetchone()[0]
cur.execute('update persone set Age='+str(age+1)+' where id=1')
# cur.execute('rollback')
cur.execute('commit')
print "ajout Done"
retraitAge.py
import MySQLdb as lite
num=1000
connexion = lite.connect(host="localhost", user="root", passwd="123", db="test")
with connexion:
for i in range(num):
cur = connexion.cursor()
cur.execute('start transaction')
cur.execute('select Age from persone where id=1')
age = cur.fetchone()[0]
cur.execute('update persone set Age='+str(age-1)+' where id=1')
cur.execute('commit')
print "retrait Done"
mymain.py
from subprocess import Popen
a=Popen("python ajoutAge.py", shell=True)
aa=Popen("python ajoutAge.py", shell=True)
b=Popen("python retraitAge.py", shell=True)
bb=Popen("python retraitAge.py", shell=True)
a.communicate()
aa.communicate()
b.communicate()
bb.communicate()
print "Main done"
我的表使用的是 InnoDB 存储引擎。
1 个回答
4
你正在制造一个竞争条件。
每次你查询当前年龄的时候,和你更新数据之间会有一个很短的时间间隔。在这个间隔里,其他的进程可能会(而且有时确实会)更新这个值。
所以在第一个进程中,当你把年龄更新为年龄+1的时候,其实是基于一个稍微过时的值在增加。
要解决这个问题,你有几个选择:
SELECT ... FOR UPDATE
,这个命令会锁定这一行数据,防止其他进程在你完成操作之前修改它。你需要确保不要提交你的查询事务,因为那样会释放锁,这样就可能再次出现竞争条件。UPDATE persone SET age = age+1 WHERE id=1
(当然也可以是age=age-1
)。换句话说,就是在同一个表达式中读取值并修改它,这样就形成了一个原子操作,其他进程就无法在这个过程中“插队”去修改它。