Mysql(Innodb)中锁的机制

   日期:2020-07-15     浏览:102    评论:0    
核心提示:Innodb中的锁机制什么是锁latchlock行级锁与表级锁锁的分类共享锁和排他锁共享锁排他锁意向锁锁的算法实现Record LockGap LockNext-Key Lock加锁规则举例说明场景1:主键索引等值间歇锁场景2:非唯一索引等值锁什么是锁Mysql中主要有两种锁分别是lock和latch,本文主要介绍的是lock,也是我们常说的对于事务的锁。latchlatch 一般称为闩锁(轻量级的锁) ,其应用对象为线程,保护的是内存数据结构。在InnoDB存储引擎中,latch有可以分为mute

Innodb中的锁机制

  • 什么是锁
    • latch
    • lock
  • 行级锁与表级锁
  • 锁的分类
    • 共享锁和排他锁
      • 共享锁
      • 排他锁
    • 意向锁
  • 锁的算法实现
    • Record Lock
    • Gap Lock
    • Next-Key Lock
  • 加锁规则
    • 举例说明
      • 场景1:主键索引等值间歇锁
      • 场景2:非唯一索引等值锁

什么是锁

Mysql中主要有两种锁分别是lock和latch,本文主要介绍的是lock,也是我们常说的对于事务的锁。

latch

latch 一般称为闩锁(轻量级的锁) ,其应用对象为线程,保护的是内存数据结构。在InnoDB存储引擎中,latch有可以分为mutex(互斥锁)和rwlock(读写锁)其目的用来保证并发线程操作临界资源的正确性,并且没有死锁检测的机制,仅通过加锁顺序保证无死锁发生。

lock

lock的对象是事务,用来锁定的是数据库中的UI想,如表、页、行。并且一般lock对象仅在事务commit或rollback后进行释放(不同事务隔离级别释放的时间可能不同),此外lock正如大多数数据库中一样,是有死锁机制的。

行级锁与表级锁

在 InnoDB 中是支持多粒度的锁共存的,比如表锁和行锁。与Oracle不同,mysql的行锁是通过索引加载的,即是行锁是加在索引响应的行上的,要是对应的SQL语句没有走索引,则会全表扫描,

行锁则无法实现,取而代之的是表锁。

表锁:不会出现死锁,发生锁冲突几率高,并发低。
行锁:会出现死锁,发生锁冲突几率低,并发高。

锁的分类

共享锁和排他锁

共享锁和排他锁(Shared and Exclusive Locks)是标准的实现行级别的锁。举例来说,当给 select 语句应用 lock in share mode 或者 for update,或者更新某条记录时,加的都是行级别的锁。

与行级别的共享锁和排他锁类似的,还有表级别的共享锁和排他锁。如 LOCK TABLES ... WRITE/READ 等命令,实现的就是表级锁。我们可以看到,共享锁和排他锁的粒度并不一定必须是行级别,也可以为表级别。

共享锁

共享锁也叫S锁/读锁, 作用是锁住当前事务select的数据行,其他事务可以读这些数据行,但不能写。

使用:在查询语句后面显式增加 LOCK IN SHARE MODE

SELECT ... LOCK IN SHARE MODE;

排他锁

排他锁也叫X锁/写锁,作用是锁住事务中使用了排他锁的数据行,其他事务对这些数据行,既不能读也不能写。

使用:
1、MySql 的 InnoDB 引擎会为insert、update、delete操作中涉及的数据自动加排他锁(根据where条件语句)
2、对于一般的select语句,InnoDB不会加任何锁,可加FOR UPDATE,显式地加排他锁

SELECT ... FOR UPDATE;

ps.加过排他锁的数据行在其他事务中不能修改数据,也不能通过for update和lock in share mode的方式查询数据;但可以直接通过普通select …from…查询数据(但查到的只是已提交过的数据),因为普通查询没有任何锁机制。

意向锁

意向锁是表级锁,也可以分为意向共享锁 intention shared lock (IS) 和意向排他锁 intention exclusive lock (IX) . 但有趣的是,IS 和 IX 之间并不互斥,也就是说可以同时给不同的事务加上 IS 和 IX.

意向锁的目的就是表明有事务正在或者将要锁住某个表中的行。想象这样一个场景,我们使用 select * from t where id=0 for update; 将 id=0 这行加上了写锁。假设同时,一个新的事务想要发起 LOCK TABLES ... WRITE 锁表的操作,这时如果没有意向锁的话,就需要去一行行检测是否所在表中的某行是否存在写锁,从而引发冲突,效率太低。相反有意向锁的话,在发起 lock in share mode 或者 for update 前就会自动加上意向锁,这样检测起来就方便多了。

在实际中,手动锁表的情况并不常见,所以意向锁并不常用。特别是之后 MySQL 引入了 MDL 锁,解决了 DML 和 DDL 冲突的问题,意向锁就更不被提起来了。

锁的算法实现

Record Lock

record lock ,就是常说的行锁。InnoDB 中,表都以索引的形式存在,每一个索引对应一颗 B+ 树,这里的行锁锁的就是 B+ 中的索引记录。之前提到的共享锁和排他锁,就是将锁加在这里。

Gap Lock

Gap Locks, 间隙锁锁住的是索引记录间的空隙,是为了解决幻读问题被引入的。有一点需要注意,间隙锁和间隙锁本身之间并不冲突,仅仅和插入这个操作发生冲突。

Next-Key Lock

Next-key lock 是行锁(Record)和间隙锁的并集。在 RR 级别下,InnoDB 使用 next-key 锁进行树搜索和索引扫描。记住这句话,加锁的基本单位是 next-key lock.

加锁规则

规则包括:两个“原则”、两个“优化”和一个“bug”。

原则1:加锁的基本单位是 next-key lock。next-key lock 是前开后闭区间。
原则2:查找过程中访问到的对象才会加锁。
优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

对优化 2 的说明:

从等值查询的值开始,向右遍历到第一个不满足等值条件记录结束,然后将不满足条件记录的 next-key 退化为间隙锁。

等值查询和遍历有什么关系?

在分析加锁行为时,一定要从索引的数据结构开始。通过树搜索的方式定位索引记录时,用的是"等值查询",而遍历对应的是在记录上向前或向后扫描的过程。

举例说明

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);

场景1:主键索引等值间歇锁

session A: update t set d=d+1 where id=7; (T1)
session B: insert into t values(8,8,8); (T2)
session C: update t set d=d+1 where id=10; (T3)
T1 < T2 < T3
其中,B被阻塞,C查询正常。

我们可以看到,id 列为主键索引,并且 id=7 的行并不存在。

对于 Session A 来说:
根据原则1,加锁的单位是 next-key, 因为 id=7 在 5 - 10 间,next-key 默认是左开右闭。所以范围是 (5,10].
根据优化2,但因为 id=7 是等值查询,到 id=10 结束。next-key 退化成间隙锁 (5,10).

对于 Session B 来说:
插入操作与间隙锁冲突,所以失败。

对于 Session C 来说:
根据原则1,next-key 加锁 (5,10].
根据优化1:给唯一索引加锁时,退化成行锁。范围变为:id=10 的行锁
Session C 和 Session A (5,10) 并不起冲突,所以成功。
这里可以看出,行锁和间隙锁都是有 next-key 锁满足一定后条件后转换的,加锁的默认单位是 next-key.

场景2:非唯一索引等值锁

session A: select id from t where c=5 lock in share mode;(T1)
session B: update t set d=d+1 where id=5; (T2)
session C: insert into t values(7,7,7); (T3)
T1 < T2 < T3
其中,B查询正常,C被阻塞。

c为非唯一索引,查询的字段仅有 id,lock in share mode 给满足条件的行加上读锁。

Session A:
c=5,等值查询且值存在。先加 next-key 锁,范围为 (0,5].
由于 c 是普通索引,因此仅访问 c=5 这一条记录不会停止,会继续向右遍历,到 10 结束。根据原则2,这时会给 id=10 加 next-key (5,10].
但 id=10 同时满足优化2,退化成间隙锁 (5,10).
根据原则2,该查询使用覆盖索引,可以直接得到 id 的值,主键索引未被访问到,不加锁。

Session B:
根据原则1 和优化1,给 id=10 的主键索引加行锁,并不冲突,修改成功。

Session C:
由于 Session A 已经对索引 c 中 (5,10) 的间隙加锁,与插入 c=7 冲突, 所以被阻塞。

可以看出,加锁其实是在索引上,并且只加在访问到的记录上,如果想要在 lock in share mode 下避免数据被更新,需要引入覆盖索引不能包含的字段。

假设将 Session A 的语句改成 select id from t where c=5 for update;, for update 表示可能当前事务要更新数据,所以也会给满足的条件的主键索引加锁。这时 Session B 就会被阻塞了。

通过这两个场景,我们已经可以了解到对于索引加锁的规则,还有其他的场景大家可以通过底下的参考资料学习。
参考资料

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服