mysql行级锁处理小规模并发实现抢购秒杀

MySQL的锁机制比较简单,其最显著的特点是不同的存储引擎支持不同的锁机制。比如,MyISAM和MEMORY存储引擎采用的是表级锁(table-level locking);BDB存储引擎采用的是页面锁(page-level locking),但也支持表级锁;InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。

1表级锁:直接锁定整张表,在你锁定期间,其它进程无法对该表进行写操作。如果你是写锁,则其它进程则读也不允许
2行级锁:仅对指定的记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
3页面锁:表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。

场景:现在最后只有1件库存,同时有100个或1000个请求,如何能保证只有一个用户能够抢到最后这件商品呢

不做并发处理的简单示范

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public function actionTest()
{
$data = TestService::get();
$number = $data['number'];
//还有库存
if ($number > 0) {
//库存减1
$result = TestService::update($data['id']);
if ($result) {
//插入订单
TestService::insert();
}
}
}

这里update()执行的sql语句是

1
update goods set number = number-1 where id = '1';

用ab做压力测试

1
/ab -n1000 -c100 "http://127.0.0.1/xxxxxxxx/test"

100个用户1000个请求
测试完看数据库,发现number变成了-3,订单表里面出现了4个订单
出现这个结果是在我们意料之中的,并发越大,订单数也越多,超卖的也越多

同样的方法,我们只改一下sql再做测试

利用mysql行级锁

update()的sql语句改为

1
update goods set number = number-1 where id = '1' and number > '0';

再用ab测试

会发现number为0,订单表也只有1个订单

甚至1000个用户10000个请求测试,依然如此

分析下发现,当条件加上number > '0'后,假如有3个并发,同时执行这条sql
第一个执行的时候,mysql的行级锁会将此条记录加锁,当这个执行完以后,number已被更新为0

此时锁打开,第二个第三个执行的时候,where条件number > '0'无法满足,所以后面的请求将无法再去更新number和下单

而之前不加这个number > '0'这个条件的话,有锁和没锁是无所谓的,都会执行,并且完成update