MySQL的锁机制

MySQL 服务端是允许多个客户端连接的,服务端允许客户端并发的对数据进行 CRUD 操作,以提升数据库整体的并发访问性能。但是,为了保证并发访问数据的一致性和完整性,MySQL 服务端内部有它特有的锁机制。

MySQL 支持插件式的存储引擎,不同的存储引擎内部锁实现也大不相同。例如:MyISAM 只支持表锁,而 InnoDB 则支持更细粒度的行锁和间隙锁。

1、InnoDB 的锁机制

InnoDB 相较于其他存储引擎,锁机制较为完善,MVCC 多版本并发控制可以最大程度的减少使用锁而带来更好的并发性能。

1.1、锁的类型

  • 共享锁(S Lock、读锁)
  • 排他锁(X Lock、写锁)

只有共享锁和共享锁才兼容,其他均不兼容。即:对于一行记录 R,事务 A 获得 R 的 S 锁时,事务 B 可以继续获得 R 的 S 锁,但是获得 R 的 X 锁则会阻塞。事务 A 获得 R 的 X 锁时,事务 B 不管获得 S 锁还是 X 锁均会阻塞。

加共享锁:

SELECT ... LOCK IN SHARE MODE

加排他锁:

SELECT ... FOR UPDATE
锁类型 X S
X 不兼容 不兼容
S 不兼容 兼容

  • 意向共享锁(IS Lock)
  • 意向排他锁(IX Lock)

意向锁针对的是表锁,当事务去申请记录 R 的 X 锁时,会同时去申请表的 IX 锁,S 锁也一样。即:事务 A 获得记录 R 的 S 锁,同时也获得了表的 IS 锁,事务 B 去申请表的 IX 锁时就会阻塞。

意向锁的目的:当事务去申请表锁时,无需扫描每一行记录有没有加锁,只需判断意向锁即可。

锁类型 IS IX S X
IS 兼容 兼容 兼容 不兼容
IX 兼容 兼容 不兼容 不兼容
S 兼容 不兼容 兼容 不兼容
X 不兼容 不兼容 不兼容 不兼容

1.2、锁的监测

在 MySQL 自带的数据库information_schema下有三张表:

  • INNODB_TRX(事务情况)
  • INNODB_LOCKS(加锁情况)
  • INNODB_LOCK_WAITS(锁等待的情况)

通过这三张表可以了解当前 MySQL 内部锁的一个争用情况。

当新开启一个事务,且事务没有结束之前,INNODB_TRX 会记录当前事务的一个状态信息。MySQL的锁机制当事务 A 和事务 B 都去对记录 R 加 X 锁时,INNODB_LOCKS 会包含两条加锁记录。MySQL的锁机制由于 X 锁的排他性,只有一个事务会加锁成功,其中一个事务会阻塞,此时 INNODB_LOCK_WAITS 会记录一条锁的等待信息。MySQL的锁机制通过查询这三张表,可以清晰的了解当前 MySQL 内部的一个加锁情况,以及事务的阻塞情况。如果项目运行卡住了,且日志显示是 MySQL 这边发生了阻塞,那么首先应该排查是否事务异常。

1.3、一致性非锁定读

普通的 Select 查询操作不会加任何锁,即非阻塞,InnoDB 通过 MVCC 多版本并发控制来实现。即:事务 A 查询记录 R 时,如果此时事务 B 在对记录 R 进行写操作,事务 A 不用等待事务 B 写完锁释放后才能查询到数据,而是 InnoDB 会去读取记录 R 的一个快照数据。

“非锁定读”大大的提高了数据库的并发性能,需要注意的是:不同的事务隔离级别下,读到的快照版本不同。

对于记录 R,可能存在多个快照版本,在“READ COMMITTED”隔离级别下,读到的总是最新的一份快照。而在“REPEATABLE READ”隔离级别下,读到的总是事务开始时的快照版本。

2、行锁的三种算法

InnoDB 存储引擎有三种行锁算法。

现有数据库表 t,含 4 列,结构数据如下:

a(主键) b(唯一索引) c(普通索引) d
1 1 1 1
5 5 5 5
10 10 10 10

2.1、行锁(Record Lock)

临键锁(Next-Key Lock)是 InnoDB 默认的加锁算法,但是当使用主键或唯一索引时,InnoDB 只会对涉及的数据行加锁,即「临键锁」降级为「行锁」。

如下,由于 a 为主键,InnoDB 只会锁住【1、5、10】数据行,并不会对间隙加锁,效率是最高的。

将列 a 换成列 b 也是同样的效果,因为 b 为唯一索引,也不会加间隙锁。

-- 事务A
begin;
select * from t where a > 0 for update;

-- 事务B
begin;
SELECT * FROM t WHERE a = 2 FOR UPDATE;-- 不会阻塞
SELECT * FROM t WHERE a = 1 FOR UPDATE;-- 会阻塞

2.2、间隙锁(Gap Lock)

当使用范围查询而不是等值查询时,InnoDB 会给符合范围的间隙加锁。

-- 事务A
begin;
select * from t where a between 1 and 5 for update;

-- 事务B
begin;
insert into t values (2,2,2,2);-- 会阻塞

2.3、临键锁(Next-Key Lock)

临键锁(Next-Key Lock)是 InnoDB 默认的加锁算法,结合了 Record Lock 和 Gap Lock,锁定一个范围并且包含记录本身。

对于上表的数据,可能被锁的区间为:

  • (负无穷、1]
  • (1、5]
  • (5、10]
  • (10、正无穷]

    临键锁针对的是普通索引,主键或唯一索引会降级为 Record Lock,并不会锁住范围。

-- 事务A
begin;
select * from t where c = 5 for update;

-- 事务B
begin;
insert into t values (4,4,4,4);-- 会阻塞
insert into t values (6,6,6,6);-- 会阻塞
insert into t values (11,11,11,11);-- 不阻塞

对于上述 SQL,由于列 C 不是主键和唯一索引,因此 innodb 采用临键锁算法,锁住(1、5]区间,需要特别注意的是:InnoDB 还会对下一个键加上 Gap Lock,即(5、10)。所以插入 4 和 6 均会阻塞,而 11 就不会阻塞了。

间隙锁和临键锁的加入,主要是为了解决「幻读」,InnoDB 不同于其他数据库,在「REPEATABLE READ」事务隔离级别下就可以防止「幻读」,其他数据库可能需要在「SERIALIZABLE」隔离级别下才能防止「幻读」。


原文始发于微信公众号(程序员小潘):MySQL的锁机制

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/28642.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!