面试官:MySQL不同隔离级别,都使用了什么锁?

📅 发布时间:2026/7/4 18:28:39 👁️ 浏览次数:
面试官:MySQL不同隔离级别,都使用了什么锁?
今天就让我带着大家来聊聊不同隔离级别下都会使用什么锁文章思维导图说透 MySQL 锁机制在深入探讨不同隔离级别的锁内容之前我们需要先回顾一下关于 MySQL 锁的本质以及一些基础内容这样有利于我们后续的理解。对于 MySQL 来说如果只支持串行访问的话那么其效率会非常低。因此为了提高数据库的运行效率MySQL 需要支持并发访问。而在并发访问的情况下会发生各种各样的问题例如脏读、不可重复读、幻读等问题。为了解决这些问题就出现了事务隔离级别。本质上事务隔离级别就是为了解决并发访问下的数据一致性问题的。不同的事务隔离级别解决了不同程度的数据一致性。而我们所说的全局锁、表锁、行级锁等等其实都是事务隔离级别的具体实现。而 MVCC、意向锁则是一些局部的性能优化。上面这段话基本上就是对 MySQL 锁机制很透彻的理解。当我们懂了这些概念之间的关系之后我们才能更加清晰地理解知识点。事务隔离级别相信大家都知道MySQL 的事务隔离级别有如下 4 个分别是读未提交读已提交READ COMMITTED)可重复读REPEATABLE READ串行化读未提交可以读取到其他事务还没提交的数据。在这个隔离级别下由于可以读取到未提交的值因此会产生「脏读」问题。举个例子A 事务更新了 price 为 30但还未提交。此时 B 事务读取到了 price 为 30但后续 A 事务回滚了那么 B 事务读取到的 price 就是错的脏的。读已提交只能读到其他事务已经提交的数据。这个隔离级别解决了脏读的问题不会读到未提交的值但是却会产生「不可重复读」问题。「不可重复读」指的是在同一个事务范围内前后两次读取到的数据不一样。举个例子A 事务第 1 次读取了 price 为 10。随后 B 事务将 price 更新为 20接着 A 事务再次读取 price 为 30。A 事务前后两次读取到的数据是不一样的这就是不可重复读。思考题MySQL 读已提交可以解决脏读问题那它具体是如何解决的可重复读指的是同一事务范围内读取到的数据是一致的。这个隔离级别解决了「不可重复读」的问题只要是在同一事务范围内那么读取到的数据就是一样的。对于 MySQL Innodb 来说其实通过 MVCC 来实现的。但「可重复读」隔离级别会产生幻读问题即对于某个范围的数据读取前后两次可能读取到不同的结果。举个例子数据库中有 price 为 1、3、5 三个商品此时 A 事务查询 price 10 的商品查询到了 3 个商品。随后 B 事务插入了一条 price 为 7 的商品。接着 A 事务继续查询 price 10 的商品这次却查询到了 4 个商品。可以看到「幻读」与「不可重复读」是有些类似的只是「不可重复读」更多指的是某一条记录而「幻读」指的则是某个范围数据。对于 MySQL Innodb 来说其通过行级锁级别的 Gap Lock 解决了幻读的问题。串行化指的是所有事务串行执行。这个就最简单了不用去竞争一个个去执行但是效率也是最低的。MySQL 锁类型在 MySQL 中有全局锁、表级锁、行级锁三种类型其中比较关键的是表级锁盒行级锁。对于表级锁而言其又分为表锁、元数据锁、意向锁三种。对于元数据锁而言基本上都是数据库自行操作我们无须关心。在 Innodb 存储存储引擎中表锁也用得比较少。对于行级锁而言其又记录锁、间隙锁、Next-Key 锁。记录锁就是某个索引记录的锁间隙锁就是两个索引记录之间的空隙锁Next-Key 则是前面两者的结合。在 Innodb 存储引擎中我们可以通过下面的命令来查询锁的情况。// 开启锁的日志 set global innodb_status_output_lockson; // 查看innodb引擎的信息(包含锁的信息) show engine innodb status\G;查询结果一般如下图所示上面几种不同类型的锁其各自的关键字为表级的意向排它锁IXlock mode IX。表级的插入意向锁LOCK_INSERT_INTENTION: lock_mode X locks gap before rec insert intention行级的记录锁LOCK_REC_NOT_GAP: lock_mode X locks rec but not gap行级的间隙锁LOCK_GAP: lock_mode X locks gap before rec行级的 Next-key 锁LOCK_ORNIDARY: lock_mode X通过上面的命令我们就可以知道不同的事务隔离级别使用了哪些锁了。接下来我们一个个来看看不同事务隔离级别都使用了哪些锁来实现。读未提交首先我们创建一个 price_test 表并插入一些测试数据。// 创建 price_test 表 CREATE TABLE test.price_test ( id BIGINT(64) NOT NULL AUTO_INCREMENT, name varchar(32) not null, price INTEGER(4) NULL, PRIMARY KEY (id)); // 插入测试数据 INSERT INTO price_test(name,price) values(apple, 10);接着我们打开两个命令行窗口并且都修改事务隔离级别为「读未提交」。// 设置隔离级别 SET session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED; // 查看隔离级别 select transaction_isolation;接着事务 A 执行如下命令查询出 id 为 1 记录的 price 值。// 执行命令 beign; select * from price_test where id 1; // 执行结果 ------------------ | id | name | price | ------------------ | 1 | apple | 10 | ------------------ 1 row in set (0.00 sec)接着事务 B 执行如下命令修改 price 为 20。begin; update price_test set price 20 where id 1;接着事务 A 再次读取 id 为 1 记录的 price 值。select * from price_test where id 1;从下图可以看到事务 A 读取到了事务 B 未提交的数据这其实就是脏读了。从这个例子我们可以得出一些结论在「读未提交」事务隔离级别下读写是可以同时进行的不会阻塞。看到这里我突然想到了一个问题那么写写是否会阻塞阻塞呢接下来我们继续做一个测试事务 A 和 事务 B 同时对 id 为 1 的记录进行更新看看是否能够更新成功。如上图所示我先用如下命令在事务 A上边的窗口执行将 price 修改为 15。begin; update price_test set price 15 where id 1;结果执行成功了但此时事务 A 还未提交。接着我先用如下命令在事务 B下边的窗口执行将 price 修改为 20。从图中可以看到事务 B 阻塞卡住了。从这个例子我们可以得出结论在「读未提交」事务隔离级别下写写不可以同时进行的会阻塞。此时我们通过查看锁信息可以看到其是加上一个行级别的记录锁如下图所示。当我使用 rollback 命令回滚事务 A 之后事务 B 立刻就执行了并且事务 A 还读取到了事务 B 设置的值如下图所示。有些小伙伴会说如果指定了非索引的列作为查询条件是否会触发间隙锁呢接下来我们测试一下。我们往 price_test 表再插入一条数据此时数据库中的数据如下所示。接着我们在事务 A 执行如下命令查询 price 15 的记录。mysql begin; Query OK, 0 rows affected (0.00 sec) mysql select * from price_test where price 15 for update; ------------------- | id | name | price | ------------------- | 2 | orange | 30 | ------------------- 1 row in set (0.00 sec)接着我们在事务 B 执行如下命令查询 price 5 的记录。begin; select * from price_test where price 5 for update;从如下结果可以看到事务 B 阻塞住了。此时我们在事务 A 查看锁的情况如下图所示。从上图可以看出MySQL 只是加上了一个记录锁并没有加间隙锁。最后我们总结一下在「读未提交」隔离级别下读写操作可以同时进行但写写操作无法同时进行。与此同时该隔离级别下只会使用行级别的记录锁并不会用间隙锁。读已提交在「读已提交」隔离级别下我们按之前的方式进行测试。首先我们设置一下隔离级别为「读已提交」。// 设置隔离级别 SET session TRANSACTION ISOLATION LEVEL READ COMMITTED; // 查看隔离级别 select transaction_isolation;接着我们测试同时对 id 为 1 的数据进行更新看看会发生什么。事务 A 执行如下命令begin; update price_test set price 15 where id 1;事务 B 执行如下命令begin; update price_test set price 20 where id 1;事务 B 阻塞了。查看下锁信息如下图所示。可以看到其锁是一个行级别的记录锁结果和「读未提交」的是一样的。接下来我们继续看看范围的查询是否会触发间隙锁。事务 A 执行begin; select * from price_test where price 5 for update;事务 B 执行begin; select * from price_test where price 15 for update;事务 B 会阻塞查看锁信息如下图所示。可以看到还是只有一个行级别的记录锁并没有间隙锁。看到这里你会发现「读已提交」和「读未提交」非常相似。那么它们具体有啥区别呢其实他们的最大区别就是「读已提交」解决了脏读的问题。可重复读在「读已提交」隔离级别下我们按之前的方式进行测试。首先我们设置一下隔离级别为「读已提交」。// 设置隔离级别 SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ; // 查看隔离级别 select transaction_isolation;接着我们测试同时对 id 为 1 的数据进行更新看看会发生什么。事务 A 执行如下命令begin; update price_test set price 15 where id 1;事务 B 执行如下命令begin; update price_test set price 20 where id 1;事务 B 阻塞了。查看下锁信息毫无疑问其实这里还是只会有间隙锁因为指定了索引。接下来我们继续看看范围的查询是否会触发间隙锁。事务 A 执行begin; select * from price_test where price 5 for update;事务 B 执行begin; select * from price_test where price 15 for update;事务 B 会阻塞查看锁信息如下图所示。可以看到在这里就变成了 Next-Key 锁就是记录锁和间隙锁结合体。总结一下在「可重复读」隔离级别下使用了记录锁、间隙锁、Next-Key 锁三种类型的锁。值得一提的是我们前面说过可重复读存在幻读的问题但实际上在 MySQL 中因为其使用了间隙锁所以在「可重复读」隔离级别下其实不存在幻读问题。因此MySQL 将「可重复读」作为了其默认的隔离级别。总结看到这里我想我们可以对文章开头提出的问题做个解答了MySQL 不同隔离级别都使用了什么样的锁对于任何隔离级别表级别的表锁、元数据锁、意向锁都是会使用的但对于行级别的锁则会有些许差别。在「读未提交」和「读已提交」隔离级别下都只会使用记录锁不会用间隙锁当然也不会有 Next-Key 锁了。而对于「可重复读」隔离级别来说会使用记录锁、间隙锁和 Next-Key 锁。今天我们是从隔离级别这个角度来看锁的应用但什么时候会用上记录锁什么时候会用上间隙锁后面有机会我们将聊聊这部分的问题。