两万字详解InnoDB的锁( 四 )


3. 一条SQL是如何加锁的呢?介绍完InnoDB的七种锁后,我们来看下一条SQL是如何加锁的哈,可以分9种情况进行:

  • 组合一:查询条件是主键,RC隔离级别
  • 组合二:查询条件是唯一索引,RC隔离级别
  • 组合三:查询条件是普通索引,RC隔离级别
  • 组合四:查询条件上没有索引,RC隔离级别
  • 组合五:查询条件是主键,RR隔离级别
  • 组合六:查询条件是唯一索引,RR隔离级别
  • 组合七:查询条件是普通索引,RR隔离级别
  • 组合八:查询条件上没有索引,RR隔离级别
  • 组合九:Serializable隔离级别
3.1 查询列是主键 + RC隔离级别在RC(读已提交) 的隔离级别下,对查询条件列是主键id的话,会加什么锁呢?
我们搞个简单的表,初始化几条数据:
create table t1 (id int,name varchar(16),primary key ( id));insert into t1 values(1,'a'),(3,'c'),(6,'b'),(9,'a'),(10,'d');假设给定SQL:delete from t1 where id = 6;,id是主键 。在RC隔离级别下,只需要将主键上id = 6的记录,加上X锁即可 。
两万字详解InnoDB的锁

文章插图
 
我们来验证一下吧,先开启事务会话A,先执行以下操作:
begin;//删除id=6的这条记录delete from t1 where id = 6;接着开启事务会话B
begin;update t1 set name='b1' where id =6;//发现这个阻塞等待,最后超时释放锁了验证流程图如下:
两万字详解InnoDB的锁

文章插图
 
事务会话B对id=6的记录执行更新时,发现阻塞了,打开看下加了什么锁,发现是因为id=6这一行加了一个X型的记录锁
两万字详解InnoDB的锁

文章插图
 
如果我们事务B不是对id=6执行更新,而是其他记录的话,是可以顺利执行的,如下:
两万字详解InnoDB的锁

文章插图
 
结论就是,在RC(读已提交) 的隔离级别下,对查询条件是主键id的场景,会加一个排他锁(X锁),或者说加一个X型的记录锁 。
3.2 查询条件是唯一索引+RC隔离级别如果查询条件id,只是一个唯一索引呢?那在RC(读提交隔离级别下),又加了什么锁呢?我们搞个新的表,初始化几条数据:
create table t2 (name varchar(16),id int,primary key (name),unique key(id));insert into t2 values('a',1),('c',3),('b',6),('d',9);id是唯一索引,name是主键的场景下,我们给定SQL:delete from t2 where id = 6; 。在RC隔离级别下,SQL需要加两个X锁,一个对应于id 唯一索引上的id = 6的记录,另一把锁对应于聚簇索引上的[name=’b’,id=6]的记录 。
两万字详解InnoDB的锁

文章插图
 
为什么主键索引上的记录也要加锁呢?
如果并发的一个SQL,是通过主键索引来更新:update t2 set id = 666 where name = 'b';此时,如果delete语句没有将主键索引上的记录加锁,那么并发的update就会感知不到delete语句的存在,违背了同一记录上的更新/删除需要串行执行的约束 。
3.3 查询条件是普通索引 + RC隔离级别如果查询条件是普通的二级索引,在RC(读提交隔离级别下),又加了什么锁呢?
若id列是普通索引,那么对应的所有满足SQL查询条件的记录,都会加上锁 。同时,这些记录对应主键索引,也会上锁 。
我们初始化下表结构和数据
create table t3 (name varchar(16),id int,primary key (name),key(id));insert into t3 values('a',1),('c',3),('b',6),('e',6),('d',9);加锁示意图如下:
两万字详解InnoDB的锁

文章插图
 
我们来验证一下,先开启事务会话A,先执行以下操作:
begin;//删除id=6的这条记录delete from t3 where id = 6;接着开启事务会话B
begin;update t3 set id=7 where name ='e';//发现这个阻塞等待,最后超时释放锁了实践流程如下:
两万字详解InnoDB的锁

文章插图
 
事务B为什么会阻塞等待超时,是因为事务A的delete语句确实有加主键索引的X锁


推荐阅读