使用 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 的总和 ,然后判断是否允许添加数据 [总和大于某一值时,不允许添加]
但是生产环境的时候,经常会出现 !!!判断失效的情况!!!,用户连续点击添加数据(请求时间非常短), 判断失效,用户可以添加总和大于某一值的情况,请问这个是为什么,怎么解决 [个人对数据库的知识比较薄弱,是否是数据库锁的问题?代码层面怎么解决] ?
能帮小弟解决的大神送小蓝杯咖啡十杯,以表敬意
|  |      1pabupa      2019-01-29 19:45:13 +08:00 via Android | 
|      2kaneg      2019-01-29 19:53:53 +08:00 via iPhone 你的 flask server 应该是运行在多线程或者多进程模式,所以出现了并发问题。 如果是多线程模式,不考虑太复杂的情况下,可以加个锁 | 
|      3leochan32767 OP @kaneg 的确,开了 16 个 gunicorn 请问下加锁有什么教程吗? | 
|  |      4wwqgtxx      2019-01-29 20:34:23 +08:00 借助 redis 或者是 multiprocessing.Manager 很容易实现跨进程锁,我自己还造过这方面的轮子 https://github.com/wwqgtxx/RedisTools | 
|      5kaneg      2019-01-29 20:36:34 +08:00 可以看看这个链接最后的 Lock 部分: http://blog.jobbole.com/52060/ | 
|  |      6Trim21      2019-01-29 20:38:25 +08:00 via Android 你需要一个分布式锁 | 
|      7okwork      2019-01-29 21:09:19 +08:00 多个 worker 不加锁,出现超卖的情况不可避免,严谨的做法就是加锁。也有粗暴的做法,很多秒杀排队也是简单粗暴的抛弃请求,比如你可以把相邻时间小于 1 秒钟的请求丢弃,具体返回个什么值或提升,根据具体场景设计。 | 
|  |      8cz5424      2019-01-29 21:15:08 +08:00 via iPhone 看看能否把高频的判断值扔在 redis,或者函数上 redis 锁,保证高并发只有单个在处理 | 
|  |      9guog      2019-01-29 21:22:26 +08:00 via Android 最简单的就是使用 redlock,一个基于 Redis 的简单锁。加几行代码的事。 | 
|      10ziding      2019-01-29 21:23:58 +08:00 pg 的话可以意向锁或者 for updte,mysql 看看有没有 for update 的等价物。 | 
|  |      11dagger      2019-01-29 21:47:25 +08:00  1 查询语句把用户表一起 join 出来,用 for update 锁住,记得建好索引,不然 mysql 的 for update 会锁全表 | 
|      12zeraba      2019-01-29 21:53:24 +08:00 via Android  1 锁 MySQL 已经自己做了,判断大于总和这个逻辑可以在执行插入的时候判断 比如 update price where amount > 10000 而不是前端拿这个作为条件去判断能否执行插入 | 
|  |      13ericls      2019-01-29 22:32:35 +08:00 via iPhone 在数据库那边应该加个锁 MySQL 有 select for update. Isolation 等级可能要改一下 | 
|      14zsen      2019-01-29 22:40:02 +08:00 如果基于以下的条件: 1、用于公司内部的管理系统 2、用户连续点击添加数据(请求时间非常短) 前端加一个点击后禁掉点击事件,根据后台返回结果再处理是否可行呢? | 
|  |      15tomczhen      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。 | 
|      16alvin666      2019-01-29 23:06:00 +08:00 via Android 并发大的话用乐观锁,改一下表结构和 update 语句就行了,并发小的话用悲观锁 |