行锁升级为表锁
总所周知,MySQL 的 InnoDB 存储引擎支持事务,支持行级锁(InnoDB 的行锁是通过给索引项加锁实现的)。得益于这些特性,数据库支持高并发。如果 InnoDB 更新数据使用的不是行锁,而是表锁呢?是的,InnoDB 其实很容易就升级为表锁,届时并发性将大打折扣了。
常用的索引有三类:主键、唯一索引、普通索引。主键不由分说,自带最高效率的索引属性;唯一索引指的是该属性值重复率为0,一般可作为业务主键,例如:学号;普通索引与前者不同的是,属性值的重复率大于0,不能作为唯一指定条件,例如:学生姓名。
下面我们来看下使用索引与不使用索引的情况下加锁会不会升级为表锁。
在不使用索引的情况下加锁
事务顺序 | 事务1 | 事务2 |
① | begin; | |
② | select * from user where age = 17 for update; | |
③ | begin; | |
④ | select * from user where age = 15 for update; | |
⑤ | commit; | commit; |
运行结果:
在不给 age 字段加索引的情况下进行排它锁的加锁操作,可以看到尽管加锁的数据是不同的,但是事务2在加锁时出现了锁等待现象。说明此时事务1从行级锁升级为表锁,导致事务2在给 age = 15 的数据加锁时出现了锁等待现象。
在使用普通索引的情况进行加锁
alter table user add index idx_age(age); --给age字段加个索引
事务顺序 | 事务1 | 事务2 |
① | begin; | |
② | select * from user where age = 17 for update; | |
③ | begin; | |
④ | select * from user where age = 15 for update; | |
⑤ | commit; | commit; |
运行结果:
在加了索引之后,再一次进行以上操作。可以看到,user 表不在进行表锁,那是因为行锁是建立在索引字段的基础上,如果行锁锁定的列表锁索引列则会升级为表锁。
范围性查询测试
事务顺序 | 事务1 | 事务2 |
① | begin; | |
② | select * from user where age between 13 and 16 for update; | |
③ | begin; | |
④ | select * from user where age = 17 for update; | |
⑤ | commit; | commit; |
运行结果:
当要进行加锁的数据不确定时,也一样会升级为表锁。
总结:
行锁是建立在索引的基础上。
普通索引的数据重复率过高导致索引失效,行锁升级为表锁。