Django 管理并发操作之悲观锁即select_for_update

对于后端的服务,有可能要承受很大的并发,比如火车票购买系统,后端服务需要准确的反应出剩余的票数。
那么后端怎么处理并发呢? select … for update 是数据库层面上专门用来解决并发取数据后再修改的场景的,主流的关系数据库 比如mysql、postgresql都支持这个功能, 新版的Django ORM甚至直接提供了这个功能的shortcut。
那么我们就来讨论怎么使用 select_for_update 和 验证锁的持有情况。

后端视图函数如下:

from django.db import transaction
	
def concurrence(request):
    print 'In the concurrence'	
    with transaction.atomic():
        db = UserInfo.objects.select_for_update().get(id=4)
        print 'db = {0}'.format(db)
        # In production code: maybe you should update some properties of the db object
        db.user_type = db.user_type + 1
        db.save()
        time.sleep(5)
        print 'after sleep'

视图函数中需要注意:
1. 把 select_for_update 放到了一个事务里
2. 在同一个事务里,我们还执行了 sleep(5) 

怎么验证 select_for_update 持有了锁呢?

  1. 在浏览器里访问相应的 URL,触发视图函数执行,视图函数仅仅让 user_type 增加 1

  2. 快速打开一个mysql client, 连接同一个数据库,看到 id = 4 的记录的 user_type 原始值是 1

     mysql> select * from app01_userinfo where id = 4;
     +----+-----------+---------------+----------+
     | id | user_type | username      | password |
     +----+-----------+---------------+----------+
     |  4 |         1 | gwwww         | 3252     |
     +----+-----------+---------------+----------+
     1 row in set (0.00 sec)
     mysql>
    
  3. 然后再快速执行 update app01_userinfo set user_type = user_type + 1 where id = 4 即视图函数处理的那条记录
    可以看到这条普通的 update SQL 居然执行了 4.54 second,且最后 user_type 的值变成了 3 :

     mysql> update app01_userinfo set user_type = user_type + 1 where id = 4;
     Query OK, 1 row affected (4.54 sec)
     Rows matched: 1  Changed: 1  Warnings: 0
     mysql>
     mysql> select * from app01_userinfo where id = 4;
     +----+-----------+---------------+----------+
     | id | user_type | username      | password |
     +----+-----------+---------------+----------+
     |  4 |         3 | gwwww         | 3252     |
     +----+-----------+---------------+----------+
     1 row in set (0.00 sec)
     mysql>
    

完美验证了, select_for_update 对锁的持有,其他请求需要等待锁释放。

你也许会有疑问,为什么必须要把 select_for_update 放在事务里呢?
我们可以反过来想,如果不放在事务里,什么时候释放 select_for_update 持有的锁呢。
另外通过代码尝试,如果没有把 select_for_update 放在事务里,
会报异常 select_for_update cannot be used outside of a transaction

扩展:混搭的写法会有什么效果。即在 with transaction.atomic() 中编写且执行 SQL, 也是可以的!

1. http://192.168.56.101:8082/helloDBTransaction/
2. 在视图函数 sleep 的过程中, 
3. 打开一个 MySQL Client, 执行下面的 SQL 居然用了 16.33 sec
   mysql> update student set name = concat(name, '4') where id = 4;
   Query OK, 1 row affected (16.33 sec)
   Rows matched: 1  Changed: 1  Warnings: 0
   mysql>

@login_required
def helloDBTransactionSQL(request):
    print 'In the helloDBTransactionSQL'
    host = '192.168.56.1'
    port = 3306
    db = 'robertdb'
    with transaction.atomic():
        get_sql = 'select * from student where id = 3 for update'
        stus = mysql_fetchall(host, port, db, get_sql)
        print 'stus = {0}'.format(stus)
        new_name = stus[0]['name'] + str(stus[0]['id'])
        print 'new_name = {0}'.format(new_name)
        update_sql = "update student set name = '{0}' where id = {1}".format(new_name, stus[0]['id'])
        stus = mysql_execute(host, port, db, update_sql)
        print 'sleep 20'
        time.sleep(20)
        print 'wakeup 20'
    return HttpResponse(new_name)

关于乐观锁请参考: Django 管理并发操作之乐观锁

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值