在计算机中,锁是协调多个进程或线程并发访问某一资源的一种机制。在数据库当中,数据也是一种供许多用户共享访问的资源。如何保证数据并发访问的一致性、有效性,是全部数据库必须解决的一个问题,锁的冲突也是影响数据库并发访问性能的一个重要因素。java
MDL
锁?
MDL
锁的全称是Meta Data Lock
,即元数据锁。它是MySQL
内置级别的锁,供MySQL
预防共享资源冲突的场景。mysql
MDL
锁的类型:锁名称 | 简称 | 锁类型 | 说明 | 使用语句 |
---|---|---|---|---|
MDL_INTENTION_EXCLUSIVE | S锁 | 意向锁,锁住一个范围 | 任何语句都会获取MDL意向锁,而后再获取更强级别的MDL锁。 | |
MDL_SHARED | S | S锁 | 共享锁,表示只访问表结构 | |
MDL_SHARED_HIGH_PRIO | SH | S锁 | 共享锁,只访问表结构 | show create table 等 只访问INFORMATION_SCHEMA的语句 |
MDL_SHARED_READ | SR | S锁 | 访问表结构而且读表数据 | select语句 LOCK TABLE ... READ |
MDL_SHARED_WRITE | SW | S锁 | SELECT ... FOR UPDATE DML语句 |
|
MDL_SHARED_UPGRADABLE | SU | S锁 | 可升级锁,访问表结构而且读写表数据 | Alter语句中间过程会使用 |
MDL_SHARED_NO_WRITE | SNW | S锁 | 可升级锁,访问表结构而且读写表数据,而且禁止其它事务写。 | Alter语句中间过程会使用 |
MDL_SHARED_NO_READ_WRITE | SNRW | S锁 | 可升级锁,访问表结构而且读写表数据,而且禁止其它事务读写。 | LOCK TABLES ... WRITE |
MDL_EXCLUSIVE | X | X锁 | 禁止其它事务读写。 | CREATE/DROP/RENAME TABLE等DDL语句。 |
S
锁表明共享锁,X
锁表明排他锁。sql
MDL
的兼容性矩阵(对象维度)Request type | S | SH | SR | SW | SU | SNW | SNRW | X |
---|---|---|---|---|---|---|---|---|
S | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✘ |
SH | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✘ |
SR | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✘ | ✘ |
SW | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ | ✘ | ✘ | ✘ |
SU | ✔️ | ✔️ | ✔️ | ✔️ | ✘ | ✘ | ✘ | ✘ |
SNW | ✔️ | ✔️ | ✔️ | ✘ | ✘ | ✘ | ✘ | ✘ |
SNRW | ✔️ | ✔️ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ |
X | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ | ✘ |
横向表示其它事务已经持有的锁,纵向表示事务想加的锁。数据库
属性 | 含义 | 范围/对象 |
---|---|---|
GLOBAL | 全局锁 | 范围 |
COMMIT | 提交保护锁 | 范围 |
SCHEMA | 库锁 | 对象 |
TABLE | 表锁 | 对象 |
FUNCTION | 函数锁 | 对象 |
PROCEDURE | 存储过程锁 | 对象 |
TRIGGER | 触发器锁 | 对象 |
EVENT | 事件锁 | 对象 |
select
语句操做MDL
锁流程
DML
语句操做MDL
锁流程
alter
操做MDL
锁流程
注:
DML
(UPDATE
、INSERT
、DELETE
);DDL
(CREATE
、ALTER
、DROP
);DQL
(SELECT
)。缓存
select
与alter
是否会相互阻塞当执行
select
语句时,只要select
语句在获取MDL_SHARED_READ
锁以前,alter
没有执行到rename阶段,那么select
获取MDL_SHARED_READ
锁成功,后续有alter
执行到rename阶段,请求MDL_EXCLUSIVE
锁时,就会被阻塞。bash
DML
与alter
是否会相互阻塞
alter
在opening阶段会将锁升级到MDL_SHARED_NO_WRITE
,rename阶段再将升级为MDL_EXCLUSIVE
,因为MDL_SHARED_NO_WRITE
与MDL_SHARED_WRITE
互斥,因此先执行alter
或先执行DML
语句,都会致使语句阻塞在opening tables阶段。session
select
与DML
是否会相互阻塞因为
MDL_SHARED_WRITE
与MDL_SHARED_READ
兼容,因此它们不会由于MDL
而致使等待的状况。并发
flush tables with read lock;
复制代码
unlock tables
复制代码
全局锁的典型使用场景是,作全库逻辑备份。把整库每一个表都select出来存成文本。若是不加锁会不会出现问题呢?函数
表数据变动状态 | 备份状态 |
---|---|
account表a用户有200元余额account(a,200) course表没有任何数据 |
|
备份account表 获得account(a,200) | |
用户买了一门Java课,花了100元。 | |
account表a用户有100元余额 course表数据为course(a,java) |
|
这个时候我备份跑到course表了,那么备份结果就是course(a,java) |
获得最终备份结果即是account(a,200) course(a,java),这显然是不对的,由于不加锁的话中间有业务变动,所得数据是不一致。工具
InnoDB
,隔离级别在RR
下,开启个事务,就能拿到一致性视图。MySQL
官方提供的备份工具mysqldump
当
mysqldump
使用参数–single-transaction
的时候,导数据以前就会启动一个事务,来确保拿到一致性视图。
MySQL
里面表级别的锁有两种:一种是表锁,一种是MDL
锁(TABLE
范围)。
lock tables … read/write
。与 FTWRL 相似,能够用 unlock tables
主动释放锁,也能够在客户端断开的时候自动释放。MDL
锁,当属性为TABLE,做用范围为表级别的时候,它也是一把表锁。正如咱们上面几种典型语句的加(释放)锁分析的过程当中那样。它不须要显示的使用,由于MySQL
会根据你的执行语句来分析是否加锁和加何种锁。行锁的功与过
两阶段锁协议:在
InnoDB
事务中,行锁是须要时候才加锁,但不会不须要了就释放掉,而是等待事务提交结束时才释放。
如图多个用户都点击下单的时候,产生锁竞争的主要场所是影院帐户余额新增票价,两阶段锁协议特性是事务结束才释放锁,那么将这步骤放在最后是锁暂用时间最短。
事务A | 事务B |
---|---|
update t set k = k+1 where id = 1; | |
update t set k = k+3 where id = 2; | |
update t set k = k+1 where id = 2; | |
update t set k = k+5 where id = 1; |
上面会出现死锁,解决方式即是按顺序加锁来避免死锁。
事务A | 事务B |
---|---|
update t set k = k+1 where id = 1; | |
update t set k = k+3 where id = 1; | |
update t set k = k+1 where id = 2; | |
update t set k = k+5 where id = 2; |
对于
insert
,update
,delete
操做,InnoDB
会自动给涉及到的数据加排他锁,只有select
须要咱们手动设置加锁级别。
-- 读锁(S锁)
select * from t where id = 1 lock in share mode;
-- 写锁(X锁)
select * from t where id = 1 for update;
复制代码
InnoDB
的主键索引和辅助索引 主键索引(聚簇)Record Lock
加锁策略对于主键索引,会在主键索引标上锁标记。对于普通索引,不仅在普通索引标上锁标记,并且也会在主键索引标上。
Gap Lock
加锁策略间隙锁它锁的是索引与索引之间的间隙。
Next-Key Lock
加锁策略由图能够发现
Next-Key Lock
等于Record Lock
加上Gap Lock
。左开右闭。
Next-Key Lock
的须要知道的几个小事Next-Key Lock
。 原则2:查找过程当中访问到的对象才会加锁。 优化1:索引上的等值查询,给惟一索引加锁的时候,Next-Key Lock
退化为行锁。 优化2:索引上的等值查询,向右遍历时且最后一个值不知足等值条件的时候,Next-Key Lock
退化为间隙锁。写到这,须要明确一个事儿,
Next-Key Lock
是InnoDB RR
隔离级别下的锁,是内置锁,是MySQL帮我解决某种场景锁引入的锁,那么这个场景是什么?其实它想解决的是某种状况下的幻读场景。
幻读仅专指“新插入的行”
id | c | d(key) |
---|---|---|
0 | 0 | 0 |
5 | 5 | 5 |
10 | 10 | 10 |
15 | 15 | 15 |
20 | 20 | 20 |
25 | 25 | 25 |
再看下面这个场景:
session A | sessionB | |
---|---|---|
T1 | begin; update t set d=100 where d=5 |
|
T2 | insert into t values(1,1,5); | |
T3 | commit; |
select * from t where d = 5 for update
复制代码
由于本想锁住d=5,这句话的语义被破坏了,“新插入”了一行新的d=5的数据(1,1,5)。
id | c | d(key) |
---|---|---|
0 | 0 | 0 |
5 | 5 | 100 |
10 | 10 | 10 |
15 | 15 | 15 |
20 | 20 | 20 |
25 | 25 | 25 |
1 | 1 | 5 |
咱们再分析下bin log
中的记录内容
insert into t values(1,1,5);
update t set d=100 where d=5;
复制代码
能够发现
bin log
发生与原执行不一样的结果,出现了数据不一致。为了解决这个问题,InnoDB RR
级别下,行锁并不会阻止当前读状况下的幻读问题,才引入了上面所提到的Next-Key Lock
。
写到这,还须要明确的一件事
Next-Key Lock
只会阻止往当前范围的insert
动做。并且间隙锁是内置锁,InnoDB RR
级别的行锁默认加的。