Mysql学习(七)InnoDB行锁的原理分析

文章目录

  • 一条简单的SQL语句加锁分析
    • SQL1
    • SQL2
      • 组合一:id主键+RC
      • 组合二:id唯一索引+RC
      • 组合三:id非唯一索引+RC
      • 组合三:id无索引+RC
      • 组合五:id主键+RR
      • 组合六:id唯一索引+RR
      • 组合七:id非唯一索引+RR
      • 组合八:ID无索引+RR级别
  • 复杂SQL语句的行锁分析

一条简单的SQL语句加锁分析

SQL1

select * from t1 where t1.id = 1;

问题:
上述SQL语句加锁么,加的是什么锁?
答案:
分析加锁需要考虑当前的隔离级别,该语句在串行化下MVCC会降级成Lock-Based CC,是加锁的,加的是读锁。在其他三种隔离级别下,由于MVCC的快照读,所以是不加锁的。

SQL2

delete from t1 where t1.id = 10;

同样是上述问题,那这个答案就有很多了,首先得分析下面几个事情

  • 前提一:id列是不是主键?
  • 前提二:当前系统的隔离级别是什么?
  • 前提三:id列如果不是主键,那么id列上有索引吗?
  • 前提四:id列上如果有二级索引,那么这个索引是唯一索引吗?
  • 前提五:两个SQL的执行计划是什么?索引扫描?全表扫描?

那么根据这些前提分析一下在下列组合下是如何加锁的

组合一:id主键+RC

Mysql学习(七)InnoDB行锁的原理分析_第1张图片
id是主键时,此SQL只需要在id=10这条记录上加写锁即可。

组合二:id唯一索引+RC

分析过程如下:
Mysql学习(七)InnoDB行锁的原理分析_第2张图片
过程如下:

  1. 由于id是唯一索引时,where会走id列的索引进行过滤,在找到id=10的记录后对唯一索引id=10的记录加X锁。
  2. 同时会回表查询主键索引name=d的数据,并对name=d的数据也加上X锁。

为什么唯一索引与主键索引都要加上锁
试想一下,如果在删除的过程中并发了一条更新语句,where用的是name作为条件,那么如果删除操作没有对主键索引加锁,那么更新语句是感知不到删除语句的存在从而进行更新,这违背了同一记录上的写操作需要串行化执行的原则。

组合三:id非唯一索引+RC

分析过程如下:
Mysql学习(七)InnoDB行锁的原理分析_第3张图片
过程如下:

  1. 对ID索引中符合条件的数据加上X锁。
  2. 再把对应的主键索引上的数据也加上X锁。

与组合二的区别在于ID是非唯一索引会对满足条件的多条数据都加上X锁。而组合二只会对一条数据加上X锁。

组合三:id无索引+RC

分析过程如下:
Mysql学习(七)InnoDB行锁的原理分析_第4张图片
过程如下:

  1. 由于没有索引,所以走的是全表扫描,所以会对主键索引上每一条记录施加X锁。

为什么会对所有记录施加X锁,而不是表锁或者说符合条件的数据加X锁呢
这是由于InnoDB的实现决定的,由于没有索引, 无法在存储引擎层过滤(执行计划里的Using Where),所以存储引擎对每一条数据加锁后返回给Sql Server进行过滤。而且实际实现中是有一些改进的,Sql Server在进行过滤的过程中对不满足条件的数据会立即执行unlock_row方法,把不满足条件的记录放锁(违背2PL的约束),这样做,保证了最后只有满足条件的数据持有X锁。但是对每条数据加锁的步骤是没法省略的。

组合五:id主键+RR

与组合一是相同的

组合六:id唯一索引+RR

与组合二是相同的

组合七:id非唯一索引+RR

这一组合与RC级别区别就很大了。

  • RC级别下是允许幻读的出现。
  • 而MySql在RR级别下是不允许幻读的出现的。
    所以为了解决幻读的问题,InnoDB会施加间隙锁来解决这一问题。
    分析图如下:
    Mysql学习(七)InnoDB行锁的原理分析_第5张图片
    过程如下:
  1. 首先对ID索引中符合条件的数据施加X锁。
  2. 对符合条件施加X锁的数据前后间隙施加间隙锁。
  3. 对ID索引对应的主键索引的数据施加X锁。
    施加了GAP锁以后,数据的前后都不会插入新的数据,就可以保证两次当前读的结果完全一致。

组合八:ID无索引+RR级别

分析图如下:
Mysql学习(七)InnoDB行锁的原理分析_第6张图片
这一点就非常恐怖了,由于没有索引所以会对全表加锁,还会对所有的间隙加锁。这个时候对于该表除了不加锁的快照读,其他所有的并发操作全部锁死。
当然对于这一点与组合四类似,MySql也有自己的优化
组合四:id无索引, RC类似,就是所谓的semi-consistent read。semi-consistent read开启的情况下,对于不满足查询条件的记录,MySQL会提前放锁。针对上面的这个用例,就是除了记录[d,10],[g,10]之外,所有的记录锁都会被释放,同时不加GAP锁。semi-consistent read如何触发:要么是read committed隔离级别;要么是Repeatable Read隔离级别,同时设置了 innodb_locks_unsafe_for_binlog 参数。

复杂SQL语句的行锁分析

SQL如下
Mysql学习(七)InnoDB行锁的原理分析_第7张图片
首先我们根据SQL分析一下这些条件,首先检索条件中

  1. index key:puptime > 1 and puptime < 20这个条件确定了idx_t1_pu上面的值
  2. Index Filter:userid = ‘hdc’ 。此条件,可以在idx_t1_pu索引上进行过滤,但不属于Index Key。
  3. Table Filter:comment is not NULL。此条件,在idx_t1_pu索引上无法过滤,只能在聚簇索引
    上过滤。

分析完这些后我们看一下加锁情况
Mysql学习(七)InnoDB行锁的原理分析_第8张图片

从图中可以看出,在RR隔离级别下,由Index Key所确定的范围,被加上了GAP锁;
Index Filter锁给定的条件 (userid = ‘hdc’)何时过滤,视MySQL的版本而定,在MySQL 5.6版本之前,不支持Index Condition Pushdown(ICP),因此Index Filter在MySQL Server层过滤,在5.6后支持了Index Condition Pushdown,则在index上过滤。
若不支持ICP,不满足Index Filter的记录,也需要加上记录X锁;若支持ICP,则不满足IndexFilter的记录,无需加记录X锁 (图中,用红色箭头标出的X锁,是否要加,视是否支持ICP而定); 而Table Filter对应的过滤条件,则在聚簇索引中读取后,在MySQL Server层面过滤,因此聚簇索引上也需要X锁。
最后,选取出了一条满足条件的记录[8,hdc,d,5,good],但是加锁的数量,要远远大于满足条件的
记录数量。

结论:

  • 在RR隔离级别下,针对一个复杂的SQL,首先需要提取其where条件。
  • Index Key确定的范围,需要加上GAP锁;
  • Index Filter过滤条件,视MySQL版本是否支持ICP,若支持ICP,则不满足Index Filter的记录,不加X锁,否则需要X锁;
  • Table Filter过滤条件,无论是否满足,都需要加X锁,加锁的数量,要远远大于满足条件的记录数量。

你可能感兴趣的:(#,mysql)