mysql python并发访问同一个表列

时间:2013-03-12 00:19:10

标签: python mysql concurrency process

我已经探索了一些没有太大成功的解决方案。我有两个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。

1 个答案:

答案 0 :(得分:2)

您正在创建race condition

每当您选择当前年龄时,它与您的UPDATE之间会有一瞬间的差异。在那个瞬间,另一个过程可能(并且显然有时是)更新值。

因此,在第一个过程中,当您将值更新为age + 1时,它会根据稍微过时的值递增。

要解决此问题,您可以选择以下几种方法:

  • SELECT ... FOR UPDATE,它会锁定行,阻止其他进程在您完成事务之前修改它。您需要确保不提交SELECT事务,这将释放锁定,允许另一种竞争条件。

  • UPDATE persone SET age = age+1 WHERE id=1(当然还是age=age-1)。换句话说,读取您更改值的同一表达式中的值,因此它是原子的,并且没有并发进程可以“潜入”并在其间进行更改。