Innodb锁机制

136 阅读4分钟

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语句的锁:

示例1UPDATE t SET d=1 WHERE id = 3;
id=3首先在(0,5]这个next-key锁中,又由于优化2,会退化为间隙锁,因此最终加的是ID索引上间隙锁(0,5)。

示例2UPDATE 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)的间隙锁。

示例3SELECT id FROM t WHERE c = 3 in share mode;
c=3的next-key锁是c上的(0,5],由于优化2,会退化为间隙锁(05),同时由于没有访问id索引,所以没在id上加锁。

示例4SELECT id FROM t WHERE c = 5 for update;
c=3的next-key锁是c上的(0,5]和(5,10],由于优化2,会退化为next-key(0.5]和间隙锁(5,10)。

示例5SELECT * 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)。