最近遇到一个业务问题,购物券号是预先产生的,然后进行客户ID的绑定,从SQL上来讲基本上有两种方式实现
方式一:
begin;
select * from purchase_code where code_type = 0 limit 1 for update;
update purchase_code set code_type=1,user_id=123 where purchase_code_id = 1
commit
方式二:
update purchase_code set code_type=1,user_id=123 where code_type=0 limit 1;
之前一直担心方式一会出现问题;主要是担心会因为并发问题,导致选出来的行被其他线程所修改掉,因为一直会觉得第一种方式加的是行锁,需要查出来继续判断一下code_type才能进行更新。进行测试了一下发现上面两种方式加的都是表锁。
因为什么原因上面加的是表锁呢?
经过测试,mysql加锁有这样的特点:
主键查询加行锁
begin;
select * from t_user_role where role_id = "1111" limit 1 for update ;
此时可以对其他行加读锁和写锁
select * from t_user_role where role_id = 'operator';
select * from t_user_role where role_id = 'operator' for update
锁升级的情况
role_id为varchar类型,但是用int类型也能搜索出来,但是此时加的是表锁
select * from t_user_role where role_id = 1111 for update;
当按非主键查询的时候,此时mysql加的是表锁
此时下面的查询都会卡住
select * from t_user_role where role_type = 2 limit 1 for update ;
select * from t_user_role where role_id = 'operator' for update;
如果对role_type加上一个key索引,情况就变得有意思
除了最后一次查询被卡住外,其他查询全部能返回
select * from t_user_role where role_type = 2 limit 1 for update ;
select * from t_user_role where role_id = 'operator' for update;
select * from t_user_role where role_id = '2222' for update;
select * from t_user_role where role_type = 1 limit 1 for update ;
研究innodb的锁机制发现
innodb是通过索引实现行锁,只有通过索引项查询的时候,才会加行锁,否则统一加的是表锁。因此role_type上加索引之后,锁加在了role_type的索引上,从而上面其他三能顺利获得锁,但是此时已然无法获取表锁