【笔记】行锁以及加锁流程

52 阅读3分钟

首先准备一张表

CREATE TABLE hero (
    number INT,
    name VARCHAR(100),
    country VARCHAR(100),
    PRIMARY KEY (number),
    KEY idx_name (name)
) ENGINE=InnoDB CHARSET=utf-8;

INSERT INTO hero VALUES
    (1, 'l刘备', '蜀'),
    (3, 'z诸葛亮', '蜀'),
    (8, 'c曹操', '魏'),
    (15, 'x荀彧', '魏'),
    (20, 's孙权', '吴');

表中当前两个索引如下: hero 表索引树简单示意

record lock

  • 即记录锁,其作用就是仅仅把一条记录锁上
  • 有 S 锁和 X 锁之分,X 锁与 X 锁互斥,S 锁与 S 锁兼容,S 锁与 X 锁互斥

gap lock

  • 解决幻读的一种方式,仅仅是为了锁住幻影记录
  • 官方名称为: LOCK_GAP,作用是锁住不存在的幻影记录(非真实表记录)
  • 有共享 GAP 锁和独占 GAP 锁之分,但具体作用都是一样的
  • 如果对一条记录加上 GAP 锁之分,并不影响对该记录加 Record LOCK

GAP lock

如何锁(20, +∞)呢?借助数据页中的伪记录

  • Infimum 记录,表示该页面中最小的记录
  • Supermum 记录,表示该页面中最大的记录

给 number=20 所在数据页中的 Supermum 记录加上 GAP LOCK,则可以锁住 (20, +∞),如图 锁住无穷区间

Next-key lock

  • 等于 GAP lock + Record lock

Next-key lock

Insert Intention lock

  • 很没用的一种锁,就是为了表达当前事务期望插入数据,并不阻止其他事务获取同样的锁

插入意向锁

图中,T1 已获取 GAP 锁,T2 / T3 期望做修改操作,只能等待 T1 事务结束,释放锁

隐式锁

即根据 trx_id,其他事务帮忙加的锁

InnoDB 加锁步骤

  • 步骤1. 定位扫描区间的第一条记录;

  • 步骤2. 如果扫描区间是从右到左扫描,那么需要给扫描区间最右边的记录的下一条记录添加一个gap锁(在隔离级别不小于REPEATABLE READ并且也没有开启innodb_locks_unsafe_for_binlog系统变量的情况下);

  • 步骤3. 对于Infimum记录是不加锁的,对于Supremum记录加next-key锁(在隔离级别不小于REPEATABLE READ并且也没有开启innodb_locks_unsafe_for_binlog系统变量的情况下);

  • 步骤4. 对于精确匹配的扫描区间来说,当扫描区间中的记录都被读完后,需对扫描区间后的第一条记录加一个gap锁即可,并且向server层返回可结束本扫描区间的查询的信息(在隔离级别不小于REPEATABLE READ并且也没有开启innodb_locks_unsafe_for_binlog系统变量的情况下);

  • 步骤5. 事务的隔离级别不大于READ COMMITTED,开启innodb_locks_unsafe_for_binlog系统变量,唯一性搜索并且该记录的 delete_flag 不为1,对于 >= 主键的这种边界条件来说,当前记录恰好是开始边界记录,则对记录加正经记录锁,否则添加next-key锁;

  • 步骤6. 判断 ICP 条件是否成立。如果当前记录是二级索引记录,并且已经不在扫描区间中,则向server层返回可结束本扫描区间的查询的信息;

  • 步骤7. 如果对二级索引记录进行加锁,还需要对相应的聚簇索引记录加正经记录锁(使用覆盖索引,并且加S型锁的记录可跳过此步骤);

  • 步骤8. 判断当前记录是否已不在扫描区间中,如果不在的话,则向server返回可结束本扫描区间的查询的信息;

  • 步骤9. 如果server层收到可结束本扫描区间的查询的信息,则结束本扫描区间的查询,否则继续向InnoDB要下一条记录,InnoDB根据记录所在的链表获取到下一条记录后,从步骤3开始新一轮的轮回。