对于后端的服务,有可能要承受很大的并发,比如火车票购买系统,后端服务需要准确的反应出剩余的票数。
那么后端怎么处理并发呢? 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 持有了锁呢?
-
在浏览器里访问相应的 URL,触发视图函数执行,视图函数仅仅让 user_type 增加 1
-
快速打开一个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>
-
然后再快速执行 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 管理并发操作之乐观锁