Mysql锁系列(4):算法深入

发布于:2022-12-23 ⋅ 阅读:(492) ⋅ 点赞:(0)

在前面介绍过Mysql的锁,及其算法,这里详细分析一下它的算法

锁的算法

行锁有3种算法

  • Record Lock: 单个行记录上的锁
  • Gap Lock :间隙锁,锁定一个范围,但不包含记录本身
  • Next-Key Lock:Gap Lock + Record Lock,锁定一个范围,并且包含记录本身

Record Lock会锁住索引记录,如果建表时没有设置添加索引,Innodb会去锁定隐式的主键。

Next-Key Lock算法的锁定称为Next-Key Locking。采用它可以解决幻读问题。要理解它,就要先理解下事物的隔离级别

事物的隔离级别

SQL标准的隔离级别有4种

  • Read unCommited
  • Read Commited
  • Repeatable Read
  • Serializable 

不同的隔离级别,会有不同的问题及并发效率

隔离级别
脏读 不可重复读 幻读 并发效率
Read unCommited 最高
Read Commited 稍高
Repeatable Read
Serializable  最低

算法分析

Phantom Problem,幻读问题。Mysql通过Next-Key Locking锁定技术解决了这个问题。

在Next-Key Lock算法下,Innodb对于行的查询都是采用这种锁定算法。例如有一个索引有,10,11,13,20这4个值,那么该索引可能被Next-Key Locking的区间为:

  • (-\infty, 10]
  • (10, 11]
  • (11, 13]
  • (13, 20]
  • (20, +\infty)

看一个例子,先建一张表

CREATE TABLE t
(
a INT PRIMARY KEY
);

INSERT INTO t VALUES(1), (2), (5);

然后执行以下步骤

  1. 先对a=5进行X锁
  2. BEGIN;
    -- 对a=5进行X锁
    SELECT * FROM t WHERE a = 5 FOR UPDATE;

    通过Next-Key Locking ,理论上会锁定(2,5]的索引区间;如果这时插入a为4的值,理论上应该会阻塞

    BEGIN;
    -- 插入4
    INSERT INTO t SELECT 4;

  3. 执行上面的sql,会发现成功了。这是为什么呢?

Innodb存储引擎会对Next-Key Locking进行优化,如果查询的索引含有唯一属性时(唯一索引),上面的t表的a字段为主键,所以会降级为Record Lock,仅锁住索引本身,而不是范围。

但如果是辅助索引,则情况会不同,即不会发生锁降级的情况。修改一下上面的表,增加一个列b,并添加对应的索引:

ALTER TABLE t ADD COLUMN b INT;
-- 建立索引
CREATE INDEX `idx_b` ON t(b);

TRUNCATE TABLE t;
-- 插入数据
INSERT INTO t VALUES(1,1), (3,1),(5,3),(7,6),(10,8);

这时如果执行下面的sql,对b=3这一列加X锁

-- 对b=3这一列加X锁
SELECT * FROM t WHERE b = 3 FOR UPDATE;

因为myslq是行级锁,并且由于b=3这一行上有2个索引,a列的唯一索引,b列的辅助索引,所以同时也会对a=5进行Record Lock锁定。这时如果执行下面的sql

BEGIN;
-- 对a=5加共享锁
SELECT * FROM t WHERE a = 5 LOCK in SHARE MODE;

会发现在超时,是因为a=5已经被锁定了。再看下面的插入sql

还是超时。这是由于,对b=3获取独占锁时,根据Next-Key Locking,锁定的不仅是一条记录,而是一个范围,(1,3),(3,6),所以对于b=2或b=5,都在锁定范围中,不能执行成功。

解决幻读

Mysql默认的隔离级别是Repeatable read。通过Next-Key Locking解决了幻读问题。

解释一下幻读:同一个事物中,连续执行2次相同的sql,可能会导致不同的结果;第2次可能会返回之前不存在的行。

如果前面的例子,改为Read commited,则不会采用Next-Key Locking,产生幻读问题,可以验证。

执行顺序 会话A 会话B
-- 设置隔离级别
set session transaction isolation level read committed;
BEGIN;
SELECT * FROM t WHERE b > 3 FOR UPDATE;

a    b

7    6
10    8

BEGIN;

INSERT INTO t VALUES(4,2),(6,5);

COMMIT;

a    b

6    5
7    6
10    8

锁相关信息查询

Mysql在5.7以后版本,information_schema架构下,提供了3张表锁及事物的信息

  • INNODB_LOCK_WAITS
  • INNODB_LOCKS
  • INNODB_TRX

比如上面的insert语句执行了不提交,INNODB_TRX下会有相关的事物记录,至到commit之后删除。

如果把隔离级别改加Repeatable read,重复上面的操作,INNODB_LOCKS表会详细记录锁的信息,其中lock_mode明确表示 是什么锁类型,X,GAP锁!

而INNODB_LOCK_WAITS,则记录的锁等待

关于表字段的详细含义可以参考INNODB_LOCKS,INNODB_TRX

参考文献:mysql技术内幕


网站公告

今日签到

点亮在社区的每一天
去签到