1.简介
Inodb是MySql的存储引擎之一,也是大部分开发中最常用的,主要特性就是支持严格事务。底层的主要技术就是B+索引,MVCC、行锁机制以及redo/undo/binlog日志等,这里主要介绍锁机制。
2. 锁类型
从锁的粒度来分的话,主要有全局锁,表级锁以及行级别锁。
2.1 全局锁(MySql)
全局锁就是对整个数据库实例加锁,主要的使用场景是全库的备份。MySql实现了全局读锁,加锁后数据变更操作\数据定义语句都会失败。如果在主库上备份,则主库停止写服务了;如果在从库上,则会导致主从数据的不一致。
2.2 表锁
表锁主要有两种,表定义锁(MDL)和表数据锁。
表定义锁
当访问表时,会自动加上表定义锁,放置访问期间会表结构变更,修改表结构也会自动加上。对表数据的CRUD会,自动申请MDL读锁.修改表结构,则会加MDL写锁。读锁之间不互斥,写锁与其他锁之间互斥。
由于MDL写锁会导致数据读写操作阻塞,因此数据定义变更往往要业务低峰期执行。
表数据锁
表数据锁,需要用户显示申请和释放。
// 对user表加写锁,account表加读锁
// 加锁线程在释放锁前只能执行读account表,读/写user表的操作,不能访问其他表,不能写account表
// 其他线程则不能写account表,不能读/写user表
lock tables user write, account read;
xxxxx
// 释放锁
unlock tables;
行级别锁
行级别的锁主要有行锁,间隙锁和next-key锁,每一种锁又可以分为共享锁和排他锁。
行锁
行锁是最小粒度的锁。行锁是通过索引来实现的,Innodb不是锁定某一行数据,而是锁定索引。读已提交隔离级别下使用。
间隙锁
间隙锁是一个范围锁,锁定的是左右都非闭合的一个范围。
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),
(10,10,10),(15,15,15),(20,20,20),(25,25,25);
上述表中,id和c都是索引字段,都存在间隙锁,以id为例,存在(-infinite,0),(0,5),(5,10)(10,15),(15,20),(20,25),(25, +infinite)这个几个间隙索引。间隙锁和next-key锁主要是可重复读隔离级别下使用。
next-key锁
next-key可以看做范围锁+行锁,简单表示为左开右闭的范围锁。还是以上表中c索引为例,存在以下next-key锁:
- (-infinite,0]
- (0,5]
- (5,10]
- (10,15]
- (15,20]
- (10,25]
在可重复读隔离级别下,next-key是加锁的基本单元。
3.锁策略
主要分析下可重复读隔离级别下加锁的原则
- 原则1:next-key是基本单位
- 原则2:加锁只会加在已访问的索引对象上,访问终止的条件是访问到第一个不满足条件的对象为止
- 优化1:针对等值查询(==、 >= 等会用到),如果使用的是唯一索引,则索引条件中的索引值存在,则退化为行锁
- 优化2:针对等值查询(==、 >= 等会用到),向右遍历最后一个加锁单位的最后一个值不满足条件,则next-key锁退化为间隙锁
还是以上表为例,分析以下sql语句的锁:
示例1:
UPDATE t SET d=1 WHERE id = 3;
id=3首先在(0,5]这个next-key锁中,又由于优化2,会退化为间隙锁,因此最终加的是ID索引上间隙锁(0,5)。
示例2:
UPDATE t SET d=1 WHERE id = 5;
id=5首先在(0,5]这个next-key锁中,同时第一个不满足条件的加锁单元是next-key(5,10],
由于优化1,(0,5]退化为id上的行锁id=5;由于优化2,(5,10]退化为id上的间隙锁(5,10)。因此最终加的是id=5的行锁和id上(5,10)的间隙锁。
示例3:
SELECT id FROM t WHERE c = 3 in share mode;
c=3的next-key锁是c上的(0,5],由于优化2,会退化为间隙锁(0,5),同时由于没有访问id索引,所以没在id上加锁。
示例4:
SELECT id FROM t WHERE c = 5 for update;
c=3的next-key锁是c上的(0,5]和(5,10],由于优化2,会退化为next-key(0.5]和间隙锁(5,10)。
示例5:
SELECT * FROM t WHERE id >=5 AND id < 11 FOR UPDATE;
上述语句首先会找id==5的数据,直至遇到id>=11的结束,因此next-key锁是(0,5]、(5,10]和(10,15],由于id是主键,也是唯一索引,(0,5]退化为(0,5);11 != 15,(10,15]退化为(10,15).
最终加的锁为id=5的行锁,next-key锁(5,10]以及间隙锁(10,15)。