1
nonduality 2020-11-12 16:06:20 +08:00
你在 try 里头加一行输出 log,看执行情况,也许是有些请求 miss 了(用内置开发服务器的话可能性很大)。
此外,你可以用 F 表达式,按说是可以避免 race condition 问题: ```python try: book = BookInfo.objects.get(id=1) BookInfo.objects.filter(id=book.id, read=origin_read).update(read=F('read')+1) book.save() except Exception as e: .... ``` |
2
nonduality 2020-11-12 16:10:24 +08:00
try 里头改成这两行差不多就行
book = BookInfo.objects.get(id=1) BookInfo.objects.filter(id=book.pk).update(read=F('read')+1) 使用 F 表达式后,不确定还需不需要使用 transaction,你可以测试下 |
3
IurNusRay OP @nonduality F 表达式我也试过,也不行
|
4
IurNusRay OP 之前是用 runserver 运行的,可能并发支持不行,刚刚换成 uwsgi 运行,发现结果如下:
1. book.read += 1 这种方式无法解决资源竞争,实测 1000 次请求,只能加到 500 左右 2. book.read = F("read") + 1 这种方式可以解决,实测 1000 次并发请求,分 5 批,最后 read 值加到了 5000 3. book = BookInfo.objects.get(id=1) origin_read = book.read BookInfo.objects.filter(id=book.id, read=origin_read).update(read=origin_read + 1) 这种所谓“乐观锁“的方式,实测完全无效,1000 次请求,read 值只能加到 500 左右 综上,使用 F 表达式是最有效的方式,不是很明白这种乐观锁的作用是什么,既没有解决资源竞争,实际运行也没有任何报错 |
5
nonduality 2020-11-12 19:03:49 +08:00
@IurNusRay 我刚测试了一下,用 F 表达式进行数据自加,gunicorn 起 1 个进程跑,用 ab -n 1000 -c 100 测试,完全没问题。所以,用 F 表达式对多数情形下是够用的。
|
6
nonduality 2020-11-12 19:20:58 +08:00
我不太清楚谁提出来“乐观锁”,但看其实现,大概是要保证 filter 到的实例状态具备 origin_read 的值,在此基础上 update 数值,但就算有 trasaction,也无法保证它一定获取到你要的数据状态,自然就 miss 掉了。但 F 表达式不一样,它用 SQL 语句在数据库层面直接操作数据的。
|
7
IurNusRay OP @nonduality 刚刚有看了一下,原来是我代码漏掉了一部分,这个"乐观锁"的原理是要开启一个循环,在成功+1 的时候退出循环,否则继续, 比如 row = BookInfo.objects.filter(id=book.id, read=origin_read).update(read=origin_read + 1),当 row 为 0 时继续循环。
但是经过测试,仍然达不到 F 表达式的效果,1000 次请求只能加到 990 左右,所以,还是用 F 表达式吧 |
8
todd7zhang 2020-11-13 09:32:28 +08:00
@IurNusRay 我试过,django3.1, 默认使用 sqlite3 数据库,不开启事务的时候。1000 次,20 并发 sleep 重试是能实现的
|
9
todd7zhang 2020-11-13 09:33:32 +08:00
|
10
todd7zhang 2020-11-13 09:49:54 +08:00
如果在外面包一个 atomic, 在执行 filter().update 时,会触发 sqlite3.OperationalError: database is locked 。然后尝试对 save_point rollback 时又有新的 An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block 。不知道是不是 sqlite 的 isolation level 导致的,我后面试一下 postgreSQL 。 按理来说,只要是 read committed 就可以的
|
11
todd7zhang 2020-11-13 09:58:33 +08:00
|
12
nonduality 2020-11-13 10:07:23 +08:00
@todd7zhang 我用 F 表达式,不用 transaction,1000 请求,100 并发(尝试过更高,但受系统限制开不起来),多次测试都完全正常。如果不涉及关键的数值,用 F 表达式足够了,用 transaction 降低数据库性能。
|
13
todd7zhang 2020-11-13 10:42:35 +08:00
@nonduality 这种 filter().update() 主要还是为了防止超售吧,如果你单纯的为了+1, F 表达式确实可以
|