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 管理并发操作之乐观锁

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

猜你喜欢

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