我已经探索了一些没有太大成功的解决方案。我有两个python进程(使用子进程中的Popen创建)和一个包含一行有两列的mysql表:Age:0, id:1
。一个进程选择该行,取其年龄并将其递增1。它做了1000次。第二个做同样的事情,但改为减少它。我并行运行每个流程。
理论上,我希望最后,年龄保持为零。问题是我在mymain.py结尾处持续获得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。
答案 0 :(得分:2)
您正在创建race condition。
每当您选择当前年龄时,它与您的UPDATE之间会有一瞬间的差异。在那个瞬间,另一个过程可能(并且显然有时是)更新值。
因此,在第一个过程中,当您将值更新为age + 1时,它会根据稍微过时的值递增。
要解决此问题,您可以选择以下几种方法:
SELECT ... FOR UPDATE
,它会锁定行,阻止其他进程在您完成事务之前修改它。您需要确保不提交SELECT事务,这将释放锁定,允许另一种竞争条件。
UPDATE persone SET age = age+1 WHERE id=1
(当然还是age=age-1
)。换句话说,读取您更改值的同一表达式中的值,因此它是原子的,并且没有并发进程可以“潜入”并在其间进行更改。