使用 python 的 flask 框架搭建了一个服务,用于公司内部的管理系统,公司比较大,员工比较多,有时候访问比较频发,最近发生了如下的一个问题,苦思冥想各种解决方法都试过了,还是无法解决,江湖告急
1 用的数据库是 mysql
2 表的引擎是 innodb
3 orm 用的是 SQLAlchemy
问题:
添加数据的时候,使用 load.reqMoney = 100000 db.session.add(load) db.session.commit() 并且在添加数据前使用了 db.session.query(func.sum(Load.reqMoney)).filter(Load.uid == xxx).scalar() 的语句,进行查询 load 的 reqMoney 的总和 ,然后判断是否允许添加数据 [总和大于某一值时,不允许添加]
但是生产环境的时候,经常会出现 !!!判断失效的情况!!!,用户连续点击添加数据(请求时间非常短), 判断失效,用户可以添加总和大于某一值的情况,请问这个是为什么,怎么解决 [个人对数据库的知识比较薄弱,是否是数据库锁的问题?代码层面怎么解决] ?
能帮小弟解决的大神送小蓝杯咖啡十杯,以表敬意
1
pabupa 2019-01-29 19:45:13 +08:00 via Android
|
2
kaneg 2019-01-29 19:53:53 +08:00 via iPhone
你的 flask server 应该是运行在多线程或者多进程模式,所以出现了并发问题。
如果是多线程模式,不考虑太复杂的情况下,可以加个锁 |
3
leochan32767 OP @kaneg 的确,开了 16 个 gunicorn 请问下加锁有什么教程吗?
|
4
wwqgtxx 2019-01-29 20:34:23 +08:00
借助 redis 或者是 multiprocessing.Manager 很容易实现跨进程锁,我自己还造过这方面的轮子
https://github.com/wwqgtxx/RedisTools |
5
kaneg 2019-01-29 20:36:34 +08:00
可以看看这个链接最后的 Lock 部分: http://blog.jobbole.com/52060/
|
6
Trim21 2019-01-29 20:38:25 +08:00 via Android
你需要一个分布式锁
|
7
okwork 2019-01-29 21:09:19 +08:00
多个 worker 不加锁,出现超卖的情况不可避免,严谨的做法就是加锁。也有粗暴的做法,很多秒杀排队也是简单粗暴的抛弃请求,比如你可以把相邻时间小于 1 秒钟的请求丢弃,具体返回个什么值或提升,根据具体场景设计。
|
8
cz5424 2019-01-29 21:15:08 +08:00 via iPhone
看看能否把高频的判断值扔在 redis,或者函数上 redis 锁,保证高并发只有单个在处理
|
9
guog 2019-01-29 21:22:26 +08:00 via Android
最简单的就是使用 redlock,一个基于 Redis 的简单锁。加几行代码的事。
|
10
ziding 2019-01-29 21:23:58 +08:00
pg 的话可以意向锁或者 for updte,mysql 看看有没有 for update 的等价物。
|
11
dagger 2019-01-29 21:47:25 +08:00 1
查询语句把用户表一起 join 出来,用 for update 锁住,记得建好索引,不然 mysql 的 for update 会锁全表
|
12
zeraba 2019-01-29 21:53:24 +08:00 via Android 1
锁 MySQL 已经自己做了,判断大于总和这个逻辑可以在执行插入的时候判断 比如 update price where amount > 10000 而不是前端拿这个作为条件去判断能否执行插入
|
13
ericls 2019-01-29 22:32:35 +08:00 via iPhone
在数据库那边应该加个锁
MySQL 有 select for update. Isolation 等级可能要改一下 |
14
zsen 2019-01-29 22:40:02 +08:00
如果基于以下的条件:
1、用于公司内部的管理系统 2、用户连续点击添加数据(请求时间非常短) 前端加一个点击后禁掉点击事件,根据后台返回结果再处理是否可行呢? |
15
tomczhen 2019-01-29 23:03:20 +08:00
数据库上根据 select 结果来 update,多线程下要么使用幂等逻辑,类似:update status = 1 where id =1 and status =0; update sum = 19 where id= 1 and sum = 10,要么 select 就要上锁。
加锁是为了保证一致性,会影响并发数。而且还得注意索引,确保锁的粒度,当然,内部系统没那么讲究的话其实也没所谓。 redis 也行,毕竟单线程。另外如果取 key,重新写 value 不是原子( redis lua )操作的话,还是会出现并发问题。 至于前端限制,为了并发最好也是要做的,减少无谓的请求,web 页面可以考虑 Redirect After Post。 |
16
alvin666 2019-01-29 23:06:00 +08:00 via Android
并发大的话用乐观锁,改一下表结构和 update 语句就行了,并发小的话用悲观锁
|