Django 管理并发操作之乐观锁

乐观锁其实并不是锁。通过SQL的where子句中的条件是否满足来决定是否满足更新条件来更新数据库,通过受影响行数判断是否更新成功,如果更新失败可以再次进行尝试,如果多次尝试失败就返回更新失败的结果。

为了验证乐观锁的使用情况:使用乐观锁时必须设置数据库的隔离级别是Read Committed(可以读到其他线程已提交的数据)。如果隔离级别是Repeatable Read(可重复读,读到的数据都是开启事务时刻的数据,即使其他线程提交更新数据, 该线程读取的数据也是之前读到的数据),乐观锁如果第一次尝试失败,那么不管尝试多少次都会失败。 (Mysql数据库的默认隔离级别是Repeatable Read,需要修改成Read Committed, 可以用 show variables like ‘transaction_isolation’; 来确认数据库事务隔离级别)。

def concurrence(request):
    s1 = transaction.savepoint()
    print 'begin: {0}'.format(time.time())
    retry_time =5
    for i in range(retry_time):
        db = UserInfo.objects.filter(id=4).first()
        old_balance = int(db.password)
        new_balance = old_balance - 1
        # 用 password 列来模拟余额, 在 sleep 期间把通过 mysql client 修改数据库的值
        time.sleep(10)
        res = UserInfo.objects.filter(id=4, password=str(old_balance)).update(password=str(new_balance))
        if res == 0:
            if i == retry_time - 1:
                print 'update failed: {0}'.format(time.time())
                transaction.savepoint_rollback(s1)
                return HttpResponse('update failed')
            continue
        else:
            print 'update successfully: {0}'.format(time.time())
            return HttpResponse('update successfully')
    return HttpResponse('concurrence test')

视图函数需要注意:
我用 model  UserInfo 中的 password 列来模拟用户的余额。
关键的地方是: UserInfo.objects.filter(id=4, password=str(old_balance)).update(password=str(new_balance))
比如本次是取款 1 元,在视图函数中 sleep(10) 的期间,
如果用户的余额发生了变更(比如通过其他的 MySQL Client 连接数据库执行更新操作)即不等于 old_balance 了,那么上述语句的执行就会失败。
就需要去重试来更新.

关于悲观锁的文章,请参考:Django 管理并发操作之悲观锁即select_for_update

发布了44 篇原创文章 · 获赞 0 · 访问量 3953

猜你喜欢

转载自blog.csdn.net/cpxsxn/article/details/100274003