圣泉山人 后端开发工程师

where条件解决并发问题

2016-01-15

问题引入

互联网项目中,存在一个非常常见的问题,就是并发问题,举一个电商项目中很常见的一个场景: 下单减库存。常规业务中商品是不能出现超卖的情况,因为这可能导致后续一些很麻烦的问题,但是如何在高并发的减库存操作下,保证库存不会被减成负数呢?

场景模拟

MySQL环境

版本:10.0.17-MariaDB

数据库使用引擎: InnoDB

事务隔离级别: REPEATABLE-READ

模拟

比如库存表中该商品的库存只有1个了

> select * from stock where goods_id = 1;
+----------+-------+
| goods_id | stock |
+----------+-------+
|        1 |     1 |
+----------+-------+

此时有A 、B 两个用户并发在下单(下单逻辑一般都会添加事务控制,以此保证业务数据的一致性),我们来简单模拟一下库存的修改:

开启两个终端窗口,都开启事务:

> begin;
Query OK, 0 rows affected (0.00 sec)

这个时候 A 用户开始消耗一个库存:

> update stock set stock = stock - 1;
Query OK, 1 row affected (0.02 sec)
Rows matched: 1  Changed: 1  Warnings: 0

A 用户毫无悬念地修改成功,库存变成了 0:

> select * from stock where goods_id = 1;
+----------+-------+
| goods_id | stock |
+----------+-------+
|        1 |     0 |
+----------+-------+

此时 B 用户还在下单逻辑当中,此时查看库存:

> select * from stock where goods_id = 1;
+----------+-------+
| goods_id | stock |
+----------+-------+
|        1 |     1 |
+----------+-------+

库存查询结果为:1 , 这是因为 B 用户还处于事务当中,加上此时的事务隔离级别用的是 MySQL 默认的 可重复读 级别(具体详情可参考:事务的隔离级别

我们继续模拟 B 用户也来消耗掉一个库存,不过此时要注意,在 A 用户还未提交事务之前,B用户在修改同一行数据时,会等待锁的释放,因此这之前需要提交下 A 用户的事务

> commit;
Query OK, 0 rows affected (0.05 sec)

> select * from stock where goods_id = 1;
+----------+-------+
| goods_id | stock |
+----------+-------+
|        1 |     0 |
+----------+-------+

可以看到, A 用户成功消耗了一个库存,此时库存为0,那么从常规业务上来讲,其他用户就不能下单了,因为已经无货可卖了。

我们继续观察 B 用户的修改:

> update stock set stock = stock - 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

可以看到修改成功了,提交事务,再查看结果:

> commit;
Query OK, 0 rows affected (0.03 sec)

> select * from stock where goods_id = 1;
+----------+-------+
| goods_id | stock |
+----------+-------+
|        1 |    -1 |
+----------+-------+

问题出现:库存被减成了负数!

解决问题

当然,处理该问题有很多种方法,比如借助内存缓存来支持更大的并发,或者其他更加优秀的方法。但是这里我想分享的是使用 where 条件 来解决该问题。

我们重头来过,库存重置为1,然后 A B 用户同时开启事务,此时,A 先消耗一个库存,更新时,加上where条件

> select * from stock where goods_id = 1;
+----------+-------+
| goods_id | stock |
+----------+-------+
|        1 |     1 |
+----------+-------+

> update stock set stock = stock - 1 where stock-1 >= 0;
Query OK, 1 row affected (0.04 sec)
Rows matched: 1  Changed: 1  Warnings: 0

> commit;
Query OK, 0 rows affected (0.00 sec)

> select * from stock where goods_id = 1;
+----------+-------+
| goods_id | stock |
+----------+-------+
|        1 |     0 |
+----------+-------+

提交后,库存被正常消耗掉,B 用户在事务里面查询库存依然为1

select * from stock where goods_id = 1;
+----------+-------+
| goods_id | stock |
+----------+-------+
|        1 |     1 |
+----------+-------+

然后 B 用户消耗库存,也加上where条件:

> update stock set stock = stock - 1 where stock-1 >= 0;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0  Changed: 0  Warnings: 0

其实此时就能看出来:更新失败了!

提交事务后,再次查看是否更新成功:

> commit;
Query OK, 0 rows affected (0.00 sec)

> select * from stock where goods_id = 1;
+----------+-------+
| goods_id | stock |
+----------+-------+
|        1 |     0 |
+----------+-------+

如此:库存得以保证!

思维发散

按照上面的结果,那么where也同样可以用于其他地方来保证数据的一致性,比如订单状态的修改、红包的领取等等


Similar Posts

上一篇 Git

下一篇 Redis

Comments