首先设定一个前提,为了防止超卖现象,所有减库存操作都需要进行一次减后检查,保证减完不能等于负数。(由于 MySQL 事务的特性,这种方法只能降低超卖的数量,但是不可能完全避免超卖)
update number set x=x-1 where (x -1 ) >= 0;
为什么这条语句会出现超卖
update number set x=x-1 where (x -1 ) >= 0;
为什么这条语句会出现超卖

1 lihongjie0209 Nov 20, 2018 你的事务级别是什么? |
2 iloveyou Nov 20, 2018 悲观锁乐观锁 |
3 WeaponXu Nov 20, 2018 取数据的时候要锁一下 |
4 mmdsun Nov 20, 2018 via Android update number set x=x-1 where x >0 这样写肯定不会超卖的。RR 隔离级别 |
5 opengps Nov 20, 2018 via Android 秒杀是个高并发的东西,不适合用数据库控制 |
6 gejun123456 Nov 20, 2018 via iPhone 你这样写肯定不会的 |
7 blue0125 Nov 20, 2018 via Android 据说用 Redis 可以 |
8 xavier007 Nov 20, 2018 队列!比如小米原来的秒杀,就是有个中间页,然后通过队列限制进入实际购买页。另外数据库更新也可以利用队列。这样限制了更新数据库的并发量 |
9 zjsxwc Nov 20, 2018 via Android 这种 sql 明显会超卖的啊, mysql 默认的事务级别是允许幻读的, 使用队列处理秒杀是最佳实践. |
10 ilyh Nov 20, 2018 你这样写是不会的, mysql 默认的隔离级别是可重复读, update set 操作是当前读, 会加锁的. 和幻读没有关系 |
11 luozic Nov 20, 2018 via iPhone 事务,你这只是 update, |
12 heww Nov 20, 2018 via iPhone 用 select for update 来做 |
13 o00o Nov 20, 2018 via Android 艺高人胆大,我这都是在 Java 端用线程锁判断 |
14 limuyan44 Nov 20, 2018 via Android mysql 默认的 rr 级别不会的,另,这个和上面说的幻读有毛关系。。 |
15 Raymon111111 Nov 20, 2018 这样写应该不会超卖啊 |
16 mayday526 Nov 20, 2018 默认级别 RR,理论上不会,那么问题来了,为什么这样还是会超卖呢 |
17 leriou Nov 20, 2018 秒杀用 redis 分布式锁服务 |
18 insert000 Nov 20, 2018 当并发进来的时候 x-1 可能是同时的,并且 x 设置成非负 |
19 Va1n3R Nov 20, 2018 了解一下安全界的条件竞争漏洞~ |
20 cheeseyu1994 Nov 20, 2018 via Android 这样会超卖吗?等答案|ω·) 是不是没有对 mysql 操作结果进行判断就直接写入订单了? |
22 qsbaq Nov 20, 2018 高并发的话建议 redis 或者 memcached,频繁更新数据库会增加负载。 https://www.jiloc.com/44364.html |
24 insert000 Nov 20, 2018 用 redis SETNX 当锁拦一道,抢到锁的再操作数据库就好了 |
25 tuntunxiong Nov 20, 2018 redis list, rpush rpop |
26 linbiaye Nov 20, 2018 这个写法,事务级别为 RR, RC, Serialize 都不应该会出现问题才是,是不是没检查返回值? |
27 oovveeaarr Nov 20, 2018 的确比较奇怪,我也差不多是这种写法不应该会负数的 |
28 alcarl Nov 20, 2018 via Android 这跟隔离级别没关系吧又不是 select,update 要先锁记录的,有别的事务锁不了就等着了,因此这条不会出现超卖,但现实中一般不会这么就一个条件就更新,一般 where 里还有别的条件需要先查查 sql 的别的地方 |
29 ppyybb Nov 20, 2018 via iPhone 不讨论秒杀 就这个语句,不可能超卖: 默认是 rr 的,即便是 rc 级别,update 也会加行锁,不存在超卖。 我怀疑你看的教程的正确性 |
30 alex321 Nov 20, 2018 redis list len |
31 37rangers Nov 20, 2018 你给它放到 redis list 里,先到先得每次只能出 list 一个 ,相对会好很多 |
32 jimchen9999 Nov 21, 2018 via Android 直接 redis pop 啊 要什么数据库 |
33 cubecube Nov 21, 2018 @ppyybb 我觉得你说得对,帖子里面扯队列,redis 也是够了。就事论事,这个 mysql 语句是基本的 mysql 锁机制。行级别都保证不了一致性的话,mysql 死去算了。楼主其实没说清楚,最后是应用层超卖,还是这个 mysql 控制库存 x 小于 0 了? |
34 loqixh Nov 21, 2018 没有读 update 影响行数判断是否成功吧? |
35 myhot21 Nov 21, 2018 via Android 并发下,这种 sql 是完全避免不了超卖,加事务也只是降低出现概率,最好的避免方式是改用队列。 |
36 polymerdg Nov 21, 2018 上 redis 很嘛? |
37 ljzxloaf Nov 21, 2018 你这是表锁啊,应该不会超卖,但是性能很差 |
38 wmhack Nov 21, 2018 via iPhone 在外层代码里做 x-1 试试 |
39 ccl945 Nov 21, 2018 via Android 超卖放 redis,就 mysql 这尿性,没崩就要庆幸了 |
40 irgil Nov 21, 2018 有人能分析一下为何会超卖吗? |
41 cqu1980 Nov 21, 2018 这个只是保证减库存后,库存数不会变成负数,和超卖是两个概念.... |
42 CSDreamer Nov 21, 2018 秒杀请求走队列,消费者去消费,就不出现这种情况,不能完全基于 mysql 做秒杀 |
43 juneszh Nov 21, 2018 难道楼主没有用 ROW_COUNT()来获取 UPDATE 语句的执行结果,而是直接用 EXEC()的返回值? |
44 cqu1980 Nov 21, 2018 估计没做 43 楼的操作~~~~~~ |
45 awanabe Nov 21, 2018 via iPhone 事务搞到串行当然可以避免的 那双十一的订单可能要几年才能处理完吧? |
46 springGun Nov 21, 2018 为什么不是 where x>0 |
47 ikaros Nov 21, 2018 放在内存里用互斥锁操作,或者先锁表 |
48 kismetX Nov 21, 2018 如果是 innodb,并且加了事务的话,是不会出现负数的情况的,每行数据是都有一个事务版本号的,修改前 innodb 引擎是会检查事务版本号再更改的,这个就是解决前面部分说幻读的机制,而且这也是行级锁,讲道理,如果判断了返回的影响行数,不仅不会成为负数,也不会比库存多订单 |
49 tabris17 Nov 21, 2018 我猜 lz 用的 MyISAM 引擎,大家散了吧 |
50 shenhhd Nov 21, 2018 坐等大神终结此问题~~~~ |
51 cyhulk OP @tabris17 不是,这只是我无意间看网上的文章看到的,不信你百度,秒杀超卖,很多都这样写,所以我就很奇怪,1000 个并发去操作也没有问题,所以就问问了,还有大家纠结的锁,其实即使是 RC 隔离级别下,也会有 X 锁,也不会出现问题,这个我一会验证下。 |
52 amon Nov 21, 2018 如果是 InnoDB,加了合适的事务的话,不会有问题吧。 另外秒杀这种还是走队列。 |
53 linxy Nov 21, 2018 按 LZ 的说法,搜了一下来源,原来是别人博客里的。。。。。 |
55 cyssxt Nov 21, 2018 via iPhone 通过数据库锁并不是好的办法 需要在最上层杜绝 比如 200 的的秒杀量 10000 的访问量 后面的人 99800 的请求应该直接返回失败 个人觉得最好的办法是用队列去处理 前端轮回处理秒杀结果 |
57 tingfang Nov 21, 2018 不可能超卖的吧? |
58 karllynn Nov 21, 2018 这个不会超卖啊,这跟事务也没啥关系,这是数据库的基本保证吧 另外为啥不直接写`where x > 0`… |
59 grandpa Nov 21, 2018 有检查 affected rows 吗 |
62 realpg PRO 事务呢…… |
63 mineqiqi Nov 21, 2018 上 redis 分布式锁是比较好的解决方案 |
64 saltxy Nov 21, 2018 网上的博客都是到处抄,连写错的地方都是一模一样~这条语句顶多就是高并发扛不住,库存是不会减到负数的 |
65 jzmws Nov 21, 2018 数据库把 库存的数量设置为 unsigned 的类型 数据库做个最后的拦截 |
66 Exceptions Nov 21, 2018 这个和事务没关系,update 不管在 innodb 下是行锁还在 myisam 下是表锁, 都不会造成负数的情况。网上博客一大抄,看看就行了,别较真, 真较真起来能气死自己.... |
68 mnhkahn Nov 21, 2018 你们秒杀没量吗?居然数据库减 |
69 micean Nov 21, 2018 贴一下超卖的博客看一下? |
70 lixikei Nov 21, 2018 秒杀这种场景,直接减数据库,总会有意想不到的惊喜。 |
71 lixikei Nov 21, 2018 秒杀开始前,先将库存信息存到 redis、memcache 里,减库存直接内存减,订单付款结束更新数据库。 |
72 xiaoxiaoan317 Nov 21, 2018 高并发,首先要做上游拦截,然后使用内存队列,最后异步处理,mysql 就可以轻轻松松应对了 |
73 qilishasha Nov 21, 2018 设计模式可以看一下……不要相信那些博客上愚蠢的办法(我朝当真是人才济济)…… 12306 坑那么久也是没得说了,可以多看看 12306 的解决方案,不要浪费时间在数据库这种东西的操作上,不要感谢我,我的名字叫雷锋! |
74 weizhen199 Nov 21, 2018 大哥啊,用队列啊,数据库吃不消啊 |
75 cyhulk OP @Exceptions 是行锁没问题,但是不同隔离级别的下的锁类型可能也不一样,X 锁还是 S 锁,这个我也不是特别清楚,其实一直想找个 DBA 问下 |
76 Exceptions Nov 21, 2018 @cyhulk 底层用到的锁肯定不止这两种,排它锁共享锁间隙锁范围锁意向锁等等,事务下每次更新数据,都会用到特定的锁去锁定,然后修改数据存到 redo 日志,旧数据存到回滚段的 undo 日志里,读取都是不加锁的快照读,直接读取 undo 日志的数据,所以 innodb 才适合高并发的互联网应用 |
77 cyhulk OP @Exceptions 不考虑 RC 和 RR 级别,这两个都是严格的 X 锁,RR 还要加间隙锁,我唯一不确定的就是 Read uncommit 级别下的是否是 S 锁,如果是 S 锁,确实可能会存在,但是我实践上没有这个问题 |
78 wentaoliang Nov 21, 2018 为啥上面一堆人在扯事务的隔离级别, 这个事务隔离级别有毛的关系,我觉得这和 update number set x=x-1 where x >= 1 没区别, 会把 x >= 1 的全部加上行锁,所以不会超发 |
79 he583899772 Nov 21, 2018 我以前也出现超卖了,后来我用 redis 链表实现的,活动前吧库存缓存到 redis 列表,放在下订单逻辑的前面,卖一个出一个,为空就报错了,不让创建订单,前端也搞搞几层缓解一下并发 |
80 cyhulk OP @he583899772 为啥要用列表呢?直接数字 incrby 不也可以吗? |
81 he583899772 Nov 21, 2018 @cyhulk 都行吧,当初萌新也是摸索过河,只要反正利用 redis 原子性操作就行了,不过订单没付款,自动取消的时候还要反补 redis 活动库存吧 |
82 zerozerone Nov 21, 2018 via Android 老哥找到答案记得帮忙艾特我一下,我也想知道 |
83 cyhulk OP @zerozerone 我自己做过 1000 个并发,不会初心超卖,那个博客说的是错的 |
84 agostop Nov 22, 2018 1、这语句肯定是全表锁,全表遍历 2、既然是全表加锁遍历,当然是不会出现超卖 |