type
status
date
slug
summary
tags
category
icon
password
overview
本篇文章主要涉及innoDB中锁的种类,innoDB引擎中加锁的规则以及常见的sql语句背后的加锁过程。
注意:默认引擎为innoDB,隔离级别为可重复读。
有哪些锁
以下思维导图基本涵盖常见的锁
基本的加锁规则
全局锁
- 应用场景
一般只在对于数据库进行全库逻辑备份时使用;
- 使用方法
- 缺点
加上了全局锁,全局处于只读状态,所有与写有关的操作被阻塞,造成业务缓慢问题;
表级锁
表锁
首先注意,innoDB一般不需要加上表级锁,往往使用颗粒度更小且并发性能更好的行锁来处理;
- 使用方法
加上了共享锁,该表只可读;
加上了独占锁,该表只可被本线程写;
MDL
也被称作元数据锁,分为MDL写锁和MDL读锁;
MDL不需要用户显示调用,当用户对某表进行操作时,引擎会自动给这张表加上MDL锁;
当我们对表进行crud操作时,加上读锁,此时任何线程都可以获得另外的读锁,然后对此表进行crud操作;
当我们对表结构进行改变时,加上写锁,此时任何线程对表的操作都将被阻塞,直至该锁被释放;
注意点:
- 任何需要对表操作的线程,都处在该表的MDL申请队列中,而由于写锁的优先级是高于读锁的,那么一旦该队列中存在申请写锁的线程需要进行等待,那么该表后续所有crud操作也会被阻塞;
- MDL在事务提交之后自动释放;
意向锁
- 应用场景
当我们需要对表进行增,删,改这些操作时,我们会需要加上行级独占锁,我们需要先添加意向锁(此处为意向独占锁),这个锁本身没有锁住任何东西,它只是起到了快速告知作用,当其余线程需要对该表进行操作时,看到了这个意向锁就知道该表有记录被锁住了;
- 与select语句的关系
一般来讲,select语句并不会触发意向锁,因为在innoDB引擎以及可重复读的场景下,select的并发问题可以依靠MVCC机制以及间隙锁来进行解决,不需要用到行级共享锁,也就不会处罚意向锁;
不过也可以手动添加行锁:
用于主键自增的锁
任何变量的操作都需要考虑并发问题,而主键自增的锁就是数据库对自增主键的并发保护;
innoDB有两种用来处理主键自增的锁,AUTO-INC锁和轻量级锁;
两者最主要的区别在于释放时机,前者在执行完插入语句之后立即释放,而后者在该字段赋完自增的值后变自动释放,相比前者更为轻量;
可以通过控制innodb_autoinc_lock_mode的值来选择具体用什么锁
行级锁
记录锁
也被称为record lock
- 添加方式
- 释放
事务结束自动释放;
间隙锁
试想这样一个场景:表中有id为7,id为9两个记录,你开启一个事务,期间不想让这两个记录之间被插入另外的记录(id=8),此时你就用得上间隙锁,在id索引上锁住(7,9);
显然,这是一个范围锁,没有锁住具体的记录,只是为了防止插入操作而诞生的;
此外,间隙锁之间是不会发生冲突的;
- 应用场景
间隙锁常被用于处理幻读问题,而解决幻读问题是达到可重复度隔离级别的必经之路!
Next-key lock
本质是记录锁+间隙锁
之前我们讲间隙锁是范围锁,id索引上的(7,9)使得id7与id9之间不可插入记录,而Next-key lock为(7,9]相当于在(7,9)的间隙锁基础上加上了id为9的独占锁;我们无法在这个范围插入新记录,也无法修改id为9的这一条记录;
插入意向锁
之前讲到间隙锁可以阻塞插入记录的操作,那么,假如在这期间,有线程需要进行插入记录的操作,就会加上一个插入意向锁,并将状态设置为等待,当间隙锁释放时,插入意向锁状态变为正常,拥有插入意向锁的线程能够执行插入操作;
两个事务不能同时拥有插入意向锁,否则在间隙锁释放时,会起冲突;
注意点
- innoDB在可重复读的场景之下,行锁的默认加锁单位是Next-key lock,一般是一个前开后闭区间;
- 行锁是建立在索引之上的,若是某一个字段没有索引,默认加在主键上;
- 只有访问到的对象才需要加锁
- 当进行索引上的等值查询时,会有两个优化:
a. 给唯一索引加锁时,Next-key lock自动退化为行锁;
b. 向右遍历且最后一个元素不满足条件时,退化为间隙锁;
加锁过程分析
本部分借助一些常见的sql语句来分析背后的加锁过程,以此巩固锁的知识
以下是表t的建表语句
example1
A开启事务;
A进行等值查询,有因为id是唯一索引,触发优化,所以产生间隙锁(5,10);
B要插入id为8的记录,被间隙锁锁住;
C要更新id为10的记录,成功;
example2
A开启事务;
A主动加上行级共享锁,同时进行等值查询,查到c=5,这时会在c的索引上加上(0,5]这一Next-key lock;
由于c是普通索引,可能存在重复值,此时会继续向右查询,直到c=10,所以会加上Next-key lock(5,10];
B想要更新id=5的记录,id上并没有锁,更新成功;
C想要插入c值为7的记录,由于存在(5,10],插入失败;
为什么id上没有锁?
因为锁只被加在访问到的对象上,A的操作使用到了覆盖索引,不需要访问主键,所以id上没有锁;
example3
A开启事务;
A进行select操作,在普通索引c上进行范围查询操作,注意是desc;
查到c=20,加上锁(15,20];
由于c是普通索引,可能有重复,所以还要向右继续查询,查到c=25,确认没有,加上锁(20,25],退化成间隙锁(20,25);
继续向左查询,查到c=15,加上锁(10,15];
继续向左查询,直到c=10,加上锁(5,10);
结束;
这期间,由于是select *,所以访问到的c都会在主键上也加上锁,也就是id=15,20;
在来看B,由于B要插入的记录c=6,处于5到10之间,所以被锁住。
参考文章/书籍
—《Mysql实战45讲》
—《图解Mysql》
— 《Mysql必知必会》
— 《Mysql官方手册》
最后一部分三个example皆来自于《Mysql实战45讲》。
- 作者:Alex
- 链接:https://nextme.one/wureny.eth/article/innodblock
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。