Innodb锁介绍

Innodb 锁

通过前面两节事务、MVCC介绍都出现了锁的影子,下面我们来看下Innodb中锁的内容

操作系统以及各个编程语言中都存在关于锁相关内容的介绍,锁的算法以及基础的原理是一致的,不同点主要体现在以下几点

数据库锁 操作系统/编程语言
目标对象 事务 线程
受保护对象 数据库行记录 内存数据结构/临界资源/共享变量
持续时间 事务生命周期,时间较长 代码块,时间较短
类型 表锁/行锁/意向锁 读写锁、互斥量

通过上表可以直观的看出来数据库锁与操作系统、编程语言中的锁区别,Mysql由C语言开发,运行与各个宿主机操作系统之上,因此在运行中的mysql实例中同时存在上述两种类型的锁,今天的主角是数据库锁

Mysql中锁的分类

按照大的维度进行分类

  • 表锁,锁住整表,通常用于数据的初始化,大批量的数据迁移,一般是离线操作或者业务低峰时期
  • 行锁,基于数据行的锁
  • 意向锁,一种介于表锁于行锁之间锁类型,下文会介绍

按照读写类型进行分类

  • 读锁/共享锁,亦称之为S锁
  • 写锁/排它锁,亦称之为X锁

上述三种类型的锁,每种又有相对应的读锁于写锁,读写锁的兼容可以参见下表

操作类型 S X
S 兼容 不兼容
X 不兼容 不兼容

注意,此处的读写锁兼容不要与上文MVCC章节中介绍的读写操作搞混,表锁以及行锁的读写锁操作类型兼容性均与上表一致,意向锁的兼容类型比较特殊,下文会介绍

来看一个表锁的示例

step Tx Session1 Tx Session2
1 tx1 begin; tx2 begin;
2 tx1 lock tables account read; tx2 select id,balance,username from account where id = 1;
3 tx1 select * from account where id =1; tx2 insert into account values(NULL,10,'footer',now(),now());
4 tx1 insert into account values(NULL,10,'foobar',now(),now()); tx2 -- waiting until tx1 unlock tables;
5 tx1 ERROR 1099 (HY000): Table 'account' was locked with a READ lock and can't be updated tx2
6 tx1 unlock tables; tx2 commit;

通过上述示例可以知道

  • 表锁在S锁的模式下,其他事务无法修改,只能进行读取,无法进行任何写入操作,参见上述读写锁的兼容性描述
  • 表锁在S锁的模式下,当前事务亦不能进行数据的修改操作,参见上述step5 session1的错误输出

表锁的X锁模式与上述类似,不同的是在加X锁的情况下,其他的事务读操作会被阻塞(X锁于其他任何类型的锁都不兼容),此处不在放具体的示例,感兴趣的同学可以执行下述的语句

lock tables account write;
... do something for write operation
unlock tables;

意向锁

上文提到意向锁是一种介于行锁于表锁之间的锁定,我们知道innodb底层是基于数据页(16kb)的,一张表物理上实际由N多个16KB的数据页构成,带来的一个问题就是锁定上粗细粒度的问题,表锁过于粗,行锁某写场景下过于细,意向锁的出现就是为了支持在不同粒度上进行加锁,意向锁也分为读/写锁,通常用IS/IX指代,

举两个例子

  • 若需要对记录上X锁,则需要对记录所属的库/表/页/记录先上意向锁IX,若中间有任何一个环节造成IX锁等待,那么该环节需要等待更粗粒度的锁完成后,释放相关的意向锁资源方可继续进行
  • 若需要对记录上S锁,则需要对记录所属的库/表/页/记录先上意向锁IS,若中间有任何一个环节造成IS锁等待,那么该环节需要等待更粗粒度的锁完成后,释放相关的意向锁资源方可继续进行

需要注意的是,innodb支持的意向锁是表级别的(参见官方文档:mysql锁),意向锁与其他类型锁之间的兼容下可参见下表

操作类型 IS IX S X
IS 兼容 兼容 兼容 不兼容
IX 兼容 兼容 不兼容 不兼容
S 兼容 不兼容 兼容 不兼容
X 不兼容 不兼容 不兼容 不兼容

可以看到X锁仍然是不与其他任何类型的锁兼容

行锁

Innodb 支持三种类型的行锁

  • Record锁,最简单的基于锁定行
  • Gap锁,锁定区间
  • Next-Key锁,锁定区间,包括行记录本身

下述建表语句数据用于演示行锁的几种case


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),(10,10,10),(20,20,20),(30,30,30),(40,40,40),(50,50,50),(60,60,60);

Record锁是使用的比较多也是最简单的一种行锁,诸如下述语句使用的就是行锁

select * from t where id = 10 lock in share mode;
select * from t where id = 10 for update;

上述SQL会基于id=10的行主键生成行锁数据

GAP锁定

select * from t where id >10 and id<15 for update;

看下下述示例:

step Tx Session1 Tx Session2
1 tx1 begin; tx2 begin;
2 tx1 select * from t where id >10 and id<15 for update; tx2 insert into t values(8,8,8); -- Query OK, 1 row affected (0.00 sec)
3 tx1 tx2 insert into t values(21,21,21); -- Query OK, 1 row affected (0.00 sec)
4 tx1 tx2 insert into t values(18,18,18); -- waiting until tx1 commit or rollback
5 tx1 commit; tx2 commit;

step 2 tx1 session1 执行select * from t where id >10 and id<15 for update;范围查询,mysql表中数据想为0,10,20,30,40,50,60,针对此范围查询,生成的GAP锁定区间为(10,20),前后均为开区间

Next-Key 锁定

select * from t where c >10 and c<=20 for update;

如下辅助索引示例

step Tx Session1 Tx Session2
1 tx1 begin; tx2 begin;
2 tx1 select * from t where id >10 and id<=20 for update; tx2 insert into t values(8,8,8); -- Query OK, 1 row affected (0.00 sec)
3 tx1 tx2 insert into t values(21,21,21); --waiting until tx1 commit or rollback
4 tx1 tx2 insert into t values(31,31,31); -- Query OK, 1 row affected (0.00 sec)
update t set d=d+1 where c=30;--waiting until tx1 commit or rollback
5 tx1 commit; tx2 commit;

step2 tx1 session1 执行select * from t where id >10 and id<=20 for update; 辅助索引范围查询,针对此查询的锁定区间为辅助索引(10,20],(20,30],session2 中的三个insert语句以及update语句可以验证猜想

一点总结

实际上,innodb在默认的RR隔离级别下,默认使用next-key锁定,满足一定条件会退化为行锁或者gap锁定,行锁住要针对主键/唯一索引,gap锁住要针对范围查询,上述两个示例是两个最简单的示例,实际上的锁定规则远比此复杂,索引唯一性、查找类型(等值or范围)、全表扫描、事务隔离级别等均有一定程度的关系,详细的示例可参见下述链接 http://www.heartthinkdo.com/?p=3741#22

三种行锁的规则为:

(1)Next-Key是默认锁。左开右的闭理解:右闭是行锁(一行记录),左开-右开是间隙锁,所以某种程度上Next-KEY=行锁+间隙锁

(2)行锁场景

  • 主键、唯一索引上等值查询,对主键B+树上元素。
  • 普通索引需要回表(等值&范围), 主键B+树上满足条件的元素都是行锁。

(2)间隙锁场景,Innodb在RR级别解决幻读就是靠间隙锁实现

  • 主键等值查询,只有在未找到元素时候,需要对向右第一个元素加间隙锁。
  • 主键范围查询时,无论是否有结果,需要向右第一个元素加Next-Key。
  • 二级索引等值查询时,无论是否命中结果,需要对向右第一个元素加间隙锁。
posted @ 2022-09-14 11:52  dev_song  阅读(68)  评论(0编辑  收藏  举报