MySQL InnoDB如何保证事务特性( 二 )

  • 排它锁(写锁 X),允许事务删除一行数据或者更新一行数据 。事务拿到某一行记录的排它X锁,才可以修改或者删除这一行 。排他锁的目的是为了保证数据的一致性 。
  • 行级锁中,除了S和S兼容,其他都不兼容 。
    意向锁:
    • 意向共享锁(读锁 IS ),事务想要获取一张表的几行数据的共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁 。
    • 意向排他锁(写锁 IX),事务想要获取一张表中几行数据的排它锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁 。
    • 解释一下意向锁
    The main purpose of IX and IS locks is to show that someone is locking a row, or going to lock a row in the table.意向锁的主要用途是为了表达某个事务正在锁定一行或者将要锁定一行数据 。e.g:事务A要对一行记录r进行上X锁,那么InnoDB会先申请表的IX锁,再锁定记录r的X锁 。在事务A完成之前,事务B想要来个全表操作,此时直接在表级别的IX就告诉事务B需要等待而不需要在表上判断每一行是否有锁 。意向排它锁存在的价值在于节约InnoDB对于锁的定位和处理性能 。另外注意了,除了全表扫描以外意向锁都不会阻塞 。
    锁的算法
    InnoDB有三种行锁的算法:
    • Record Lock:单个行记录上的锁
    • Gap Lock:间隙锁,锁定一个范围,而非记录本身
    • Next-Key Lock:结合Gap Lock和Record Lock,锁定一个范围,并且锁定记录本身 。主要解决的问题是REPEATABLE READ隔离级别下的幻读 。可以参考文章了解事务隔离级别的相关知识点 。
    这里主要讲一下Next-Key Lock,利用Next-key Lock锁定的不是单个值而是一个范围,他的目的就是为了阻止多个事务将记录插入到同一范围内从而导致幻读 。
    注意了,如果走唯一索引,那么Next-Key Lock会降级为Record Lock,即仅锁住索引本身,而不是范围 。也就是说Next-Key Lock前置条件为事务隔离级别为RR且查询的索引走的非唯一索引、主键索引 。
    下面我们用个例子详细说一下 。
    首先建立一张表:
    CREATE TABLE T (id int ,f_id int,PRIMARY KEY (id), KEY(f_id)) ENGINE=InnoDB DEFAULT CHARSET=utf8insert into T SELECT 1,1;insert into T SELECT 3,1;insert into T SELECT 5,3;insert into T SELECT 7,6;insert into T SELECT 10,8;事务A执行如下语句:
    SELECT * FROM T WHERE f_id = 3 FOR UPDATE这时SQL语句走非唯一索引,因此使用Next-Key Locking加锁,并且有2个索引,其需要分别进行锁定 。
    对于聚集索引,其仅对id等于5的索引加上Record Lock 。而对于辅助索引,其加上Next-Key Lock,锁定了范围(1,3),特别需要注意的是,InnoDB存储引擎还会对辅助索引下一个键值加上Gap Lock,即范围(3.6)的锁 。
    所以如果在新session中执行如下语句都会报错[Err] 1205 - Lock wait timeout exceeded; try restarting transaction:
    select * from T where id = 5 lock in share MODE -- 不能执行,因为事务A已经给id=5的值加上了X锁,执行会被阻塞INSERT INTO T SELECT 4,2 -- 不能执行,辅助索引的值为2,在(1,3)的范围内,执行阻塞INSERT INTO T SELECT 6,5 -- 不能执行,gap锁会锁住(3,6)的范围,执行阻塞此时想象一下,事务A锁定了f_id =5 的记录, 正常会有个gap lock,锁住(5,6),那么如果没有(5,6)的gap锁,那么用户可以插入索引 f_id 为5的记录,这样事务A再次查询就会返回一个不同的记录,也就导致了幻读的产生 。
    同理,如果我们事务A执行的是select * from T where f_id = 10 FOR UPDATE,在表里查不到数据,但是基于Next-Key Lock会锁住(8,+∞),我们执行INSERT INTO T SELECT 6,11是无法插入成功的,这就从根本上解决了幻读问题 。




    推荐阅读