MySQL如何使用锁解决幻读

MySQLREPEATABLE READ级别解决了幻读问题,解决方案有两种,一种是MVCC版本控制链,具体可以参考这个,MVCC 多版本控制链,还有就是通过加锁的方式。这篇文章简要介绍一下MySQL是如何通过加锁来解决幻读问题的。

准备工作

还是一样,先创建一张表,如下所示:

CREATE TABLE hero (
    number INT,
    name VARCHAR(100),
    country varchar(100),
    PRIMARY KEY (number)
) Engine=InnoDB CHARSET=utf8;

然后插入如下数据:

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

此时MySQL中的示意图简化如下所示:

前置知识

MySQL行锁分为两种,共享锁S,以及独占锁X。当使用select * from ...或者是select * from ... in share mode这两种语法时,会对进行搜索的行记录加上S锁。当使用select * from ... for update这个语法时就会对记录加X锁。

当一个事务获取了一条记录的S锁,其他事务可以获取该记录的S锁,但不可以获取X锁;当一个事务获取了一条记录的X锁,其他事务不可以继续获取该条记录的X锁或者S锁。

分析

Record Locks

当开启一个事务,使用select * from语句时,InnoDB会对搜索的记录进行加锁,官方名称为LOCK_REC_NOT_GAP,具体是S锁还是X锁,具体视select语句而定。例如现在查找number值为8的记录,

select * from hero where number=8;

此时示意图如下所示:

Gap Locks

接下来就涉及到了MySQL如何利用加锁来解决REPEATABLE READ级别解决幻读了。上面说了,当使用select 语句查询时,InnoDB会对记录加记录锁,但这就出现一个问题了,这个事务执行第一次查询时,另一个事务要插入的数据此时还不存在,这个事务就无法对这些幻影记录加上记录锁啊,那该怎么办呢?为了解决这个问题,InnoDB引入了一种新的锁机制,称之为LOCK_GAP间隙锁。例如,我们可以在number值为8的那条记录上添加一个gap锁,如下所示:

当一条记录被加上gap锁时,就意味着不允许其他事务在number值8的前面的间隙,即(3,8)这个区间内插入新的记录。比如说,现在有另一个事务想要插入一条记录,(4,"张飞",“蜀”)。此时,定位到该条记录的下一条记录的number值为8,而这条记录上又有一个gap锁,因此这个插入操作就会被阻塞住,直到拥有这个gap锁的事务提交之后,number列的值在(3,8)中的新记录才能被插入。

gap锁的主要作用就是为了防止插入幻影记录,如果你对一条记录加了gap锁,并不会限制其他事务对这条记录继续加记录锁或者gap锁。

Next-Key Locks

当我们既想锁住某条记录,又想阻止其他事务在该记录前边的间隙插入新纪录,此时该怎么办呢?这个时候,InnoDB又出现了一个新的锁结构,LOCK_ORDINARY,也被称之为next-key锁,临键锁。它其实是record锁和gap锁的结合体。如下所示,给number值为8的记录加一个next-key锁。

总结

  • InnoDB中存在着不同的锁,当使用select语句查询某条记录时,InnoDB会对该记录加记录锁,即record锁;
  • 当一个事务对某条记录加上gap锁时,另一个事务此时向这条记录前面间隙插入新记录的操作将会被阻塞;
  • next-key锁,又称为临键锁,是记录锁和间隙锁的结合体。既锁住某条记录,又会将该记录前面的间隙一同锁住,解决了MySQLREPEATABLE READ级别下的幻读问题。
原文地址:https://www.cnblogs.com/reecelin/p/13469969.html