带你读《2022技术人的百宝黑皮书》——mysql锁机制的再研究(1)https://developer.aliyun.com/article/1340034?groupCode=taobaotech
行锁
InnoDB支持行级别锁, 锁粒度小并发度高,但是加锁开销大也很可能会出现死锁innodb行锁住的是索引项,注意当回表时,主键的聚簇索引也会加上锁。
举个栗子:
INSERT INTO `test_lock` (`id`,`gmt_create`,`gmt_modified`,`lock_key`,`lock_context`,`lock_biz`) VALUES (1,now(),now(),'123456',null,'AccountUser');
当执行下面语句, 因为查询字段多余组合索引覆盖字段, 会出现回表操作补齐其他字段, 此时唯一索引lock_key=123423-lock_biz=AccountUsery以及主键索引 id=1,均被锁住。
1 select * from test_lock where lock_key='123456' and lock_biz='AccountUser' for update;
加锁方式:
- 普通 select… 查询 (不加锁)
- 普通 insert、update、delete… (隐式加写锁)
- select…lock in share mode (加读锁)
- select…for update (加写锁)
解锁:
提交/回滚事物(commit/rollback) kill 阻塞进程
由于行锁用的最多且更容易出现死锁问题,下面会详细讲述行锁。
锁的模式分类
我们常规理解的锁分为2大类:读锁(也叫共享锁,S)和写锁(也叫排他锁,X)。
这两把锁之间的兼容性说明如下表
横轴表示已持有的锁,纵轴表示尝试获取的锁。1表示成功(即兼容,表现为正常进行下一步操作),0表示失败
(即冲突,表现为阻塞住当前操作)
兼容性 |
X |
S |
X |
0 |
0 |
S |
0 |
1 |
总结一句话就是:排他锁和任何锁均不兼容。
如果仅有读写锁,会存在一个性能问题,思考下面这个场景,其中T代表事务,T1代表事务1,以此类推。T1: 锁住表中的一行,只能读不能写(行级读锁)。
T2:申请整个表的写锁(表级写锁)。
如果T2申请成功,则能任意修改表中的一行,但这与T1持有的行锁是冲突的。故数据库应识别这种冲突,让T2的申请锁被阻塞,直到T1释放行锁。
若自己实现,最容易想到的识别方案就是遍历: step1:判断表是否已被其他事物用表锁锁住。step2: 判断表中的每一行是否已被行锁锁住。
其中step2需要遍历整个表,效率在数据库是没法接收的。因此innodb使用意向锁来解决这个问题
Innodb实现方案: T1需要先申请表的意向共享锁IS(注意意向共享锁为表级锁,且是由存储引擎自己维护,无需用户命手工命令干预),成功后再申请一行的行锁S。
在意向锁存在的情况下,上面的判断可以改为:step1: 不变 step2:发现表上有意向共享锁,说明表中行被共享行锁锁住了,因此,事务B申请表的写锁被阻塞。
此时就引入的意向锁,加入意向锁后,锁的兼容性分析如下表:
横轴表示已持有的锁,纵轴表示尝试获取的锁。1表示成功(即兼容,表现为正常进行下一步操作),0表示失败
(即冲突,表现为阻塞住当前操作)
兼容性 |
IX |
IS |
X |
S |
IX |
1 |
1 |
0 |
0 |
IS |
1 |
1 |
0 |
1 |
X |
0 |
0 |
0 |
0 |
S |
0 |
1 |
0 |
1 |
带你读《2022技术人的百宝黑皮书》——mysql锁机制的再研究(3)https://developer.aliyun.com/article/1340032?groupCode=taobaotech