MySql存储引擎InnoDB的锁

InnoDB锁模式

InnoDB实现了以下两种类型的行锁:
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同的数据集的排他锁。
排他锁(X):允许获得排他锁的事务更新数据,阻止其他事务获得相同数据集的共享锁和排他锁。

共享锁就是我读的时候,你可以读,但是不能写。排他锁就是我写的时候,你不能读也不能写。

另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁,这两种意向锁都是表锁。
意向共享锁(IS):事务打算给数据行加行共享锁,事物在给一个数据行加共享锁前必须先取得该表的IS锁。
意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
这里写图片描述

当一个事务请求的锁模式与当前的锁兼容,InnoDB就将请求的锁授予该事务;反之如果请求不兼容,则该事务就等待锁释放。

意向锁是InnoDB自动加的,不需要用户干预。
对于insert、update、delete,InnoDB会自动给涉及的数据加排他锁(X);对于一般的Select语句,InnoDB不会加任何锁,事务可以通过以下语句给显示加共享锁或排他锁。
共享锁:select * from table_name where …..lock in share mode
排他锁:select * from table_name where …..for update

加入共享锁的:
这里写图片描述

加入排他锁:
这里写图片描述

锁的实现

      在MySQL中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql语句操作了主键索引,MySQL就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
      InnoDB行锁是通过给索引项加锁实现的,如果没有索引,InnoDB会通过隐藏的聚簇索引来对记录加锁。也就是说,如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。

行锁分为三种情形:
Record lock :对索引项加锁,即锁定一条记录。
Gap lock:对索引项之间的‘间隙’、对第一条记录前的间隙或最后一条记录后的间隙加锁,即锁定一个范围的记录,不包含记录本身
Next-key Lock:锁定一个范围的记录并包含记录本身(上面两者的结合)。

注意:InnoDB默认级别是repeatable-read级别,所以下面说的都是在RR级别中的。

Next-Key Lock是行锁与间隙锁的组合,这样,当InnoDB扫描索引记录的时候,会首先对选中的索引记录加上行锁(Record Lock),再对索引记录两边的间隙加上间隙锁(Gap Lock)。如果一个间隙被事务T1加了锁,其它事务是不能在这个间隙插入记录的。

假设我们有一张表:

+----+------+
| id | age|
+----+------+
| 1  | 3  |
| 2  | 6  |
| 3  | 9  |
+----+------+

表结构如下:

CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `keyname` (`age`)
) ENGINE=InnoDB AUTO_INCREMENT=302 DEFAULT CHARSET=gbk ;

这样我们age段的索引就分为

(negative infinity, 3],

(3,6],

(6,9],

(9,positive infinity)

我们来看一下几种情况:
1、当事务A执行以下语句:
mysql> select * from fenye where age=6for update ;
不仅使用行锁锁住了相应的数据行,同时也在两边的区间,(5,6]和(6,9] 都加入了gap锁。
这样事务B就无法在这个两个区间insert进新数据,但是事务B可以在两个区间外的区间插入数据。

2、当事务A执行
select * from fenye where age=7 for update ;
那么就会给(6,9]这个区间加锁,别的事务无法在此区间插入或更新数据。

3、如果查询的数据不再范围内
比如事务A执行 select * from fenye where age=100 for update ;
那么加锁区间就是(9,positive infinity)。

行锁防止别的事务修改或删除,GAP锁防止别的事务新增,行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在写数据时的幻读问题。

何时在InnoDB中使用表锁?
InnoDB在绝大部分情况会使用行级锁,因为事务和行锁往往是我们选择InnoDB的原因,但是有些情况我们也考虑使用表级锁。
1、当事务需要更新大部分数据时,表又比较大,如果使用默认的行锁,不仅效率低,而且还容易造成其他事务长时间等待和锁冲突。
2、事务比较复杂,很可能引起死锁导致回滚。

关于这部分内容,有一篇不错的博文,贴上https://blog.csdn.net/qq_27529917/article/details/78447874

MySQL的InnoDB存储引擎行锁是加在索引上的,所以只当增删改查操作是通过索引找到指定数据行的时候,才能对相应数据行的索引加锁,否则只能对整个表加表锁,表共享读锁或表独占写锁。

当一个事务不经过索引查询数据,即顺序读取(全表扫描)时,先获取表的意向共享锁,然后对表添加共享读锁,阻止其他事务对表的更新,新增和删除操作,但不影响查询操作,共享读锁之间是兼容的。

当一个事务不经过索引更新,删除数据,即全表扫描符合条件的数据行时,先获取表的意向独占锁,然后对表添加独占写锁,在执行更新,删除时阻止其他事务对表的读及写操作。

当一个事务使用索引去查询数据,即随机读取时,先获得表的意向共享锁,然后对符合条件的的索引区间加共享读锁(聚集索引肯定会加锁,若用到了非聚集索引一样加锁),共享读锁之间是兼容的,因此不影响其他事务对被锁数据行的访问。当其他事务想要修改,删除加锁的数据行时,若未使用索引则在获取表独占写锁时会失败。若使用索引检索这些数据行,则可能会在非聚集索引处被阻塞(查询事务和修改事务使用同一个索引检索数据行),也可能在聚集索引处被阻塞(通过非聚集索引查询到聚集索引的键,通过此键查询到相应的聚集索引,同时读到数据航,这是读和写,写和写之间的事务串行化保证),只能等待查询事务释放索引区间的共享读锁,然后执行更新,删除操作。当一个事务想要将新的数据行插入到被加锁的数据行中,也需要等待共享读锁的释放。因为InnoDB实现了间隙锁机制,即当一个事务按一个条件(id < 10,id列含有索引)加锁数据行时,其他事务不能在锁释放前将符合此条件的数据行(id = 5)插入到表中,此机制一定程度防止了幻读的出现。猜测实现机制:前一个事务对数据行的索引加了锁(聚集索引和非聚集索引),其他的事务插入数据时,需要新增聚集/非聚集索引,但是此时符合条件的聚集/非聚集索引区间已经被加锁,不能实时插入索引,需要等到索引区间的锁被释放,这个可能是间隙锁的实现原理。

当一个事务通过索引去更新,删除数据行时,先获取表的意向排他锁,然后对符合条件的数据行的索引加锁(聚集/非聚集),阻止其他事务对被锁数据行的读,写。同理实现间隙锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值