Mysql(Innodb)中锁的机制

什么是锁

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 就会被阻塞了。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值