聊聊不同隔离级别下,都会使用哪些锁?
对于 MySQL 来说,如果只支持串行访问的话,那么其效率会非常低。因此,为了提高数据库的运行效率,MySQL 需要支持并发访问。而在并发访问的情况下,会发生各种各样的问题,例如:脏读、不可重复读、幻读等问题。为了解决这些问题,就出现了事务隔离级别
本质上,事务隔离级别就是为了解决并发访问下的数据一致性问题的。不同的事务隔离级别,解决了不同程度的数据一致性
我们所说的全局锁、表锁、行级锁等等,其实都是事务隔离级别的具体实现。而 MVCC、意向锁,则是一些局部的性能优化
MySQL 数据库有四大隔离级别:
在 MySQL 中有全局锁、表级锁、行级锁三种类型,其中比较关键的是表级锁、行级锁
MySQL 锁类型:
在 Innodb 存储引擎中,我们可以通过下面的命令来查询锁的情况:
# 开启锁的日志
set global innodb_status_output_locks=on;
# 查看innodb引擎的信息(包含锁的信息)
show engine innodb status\G;
查询结果一般如下图所示:
上面几种不同类型的锁,其各自的关键字为:
通过上面的命令,我们就可以知道不同的事务隔离级别使用了哪些锁了
CREATE TABLE `2022`.`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);
四种隔离级别:
READ UNCOMMITTED
:读未提交READ COMMITTED
:读已提交REPEATABLE READ
:可重复读SERLIALIZABLE
:序列化打开两个命令行窗口,并且都修改事务隔离级别为「读未提交」
// 设置隔离级别
SET session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
// 查看隔离级别
select @@transaction_isolation;
begin;
select * from price_test where id = 1;
begin;
update price_test set price = 20 where id = 1;
select * from price_test where id = 1;
【结论】:在「读未提交」事务隔离级别下,读写是可以同时进行的,不会阻塞。
事务 A 和 事务 B 同时对 id 为 1 的记录进行更新,看看是否能够更新成功
begin;
update price_test set price = 15 where id = 1;
begin;
update price_test set price = 20 where id = 1;
【结论】:在「读未提交」事务隔离级别下,写写不可以同时进行的,会阻塞
通过查看锁信息可以看到,其是加上一个行级别的记录锁,如下图所示:
如果指定了非索引的列作为查询条件,是否会触发间隙锁呢?
插入一条数据:
INSERT INTO `2022`.`price_test` (`id`, `name`, `price`) VALUES (2, 'orange', 30);
在事务 A 执行如下命令:select * from price_test where price > 15 for update;
,查询 price > 15 的记录:
接着,我们在事务 B 执行如下命令:select * from price_test where price > 5 for update;
,查询 price > 5 的记录。从如下结果可以看到,事务 B 阻塞住了:
从上图可以看出,MySQL 只是加上了一个记录锁,并没有加间隙锁
在「读未提交」隔离级别下,读写操作可以同时进行,但写写操作无法同时进行。与此同时,该隔离级别下只会使用行级别的记录锁,并不会用间隙锁
设置一下隔离级别为「读已提交」
SET session TRANSACTION ISOLATION LEVEL READ COMMITTED;
begin;
select * from price_test where id = 1;
begin;
update price_test set price = 20 where id = 1;
select * from price_test where id = 1;
测试同时对 id 为 1 的数据进行更新:
begin;
update price_test set price = 15 where id = 1;
begin;
update price_test set price = 20 where id = 1;
可以看到,其锁是一个行级别的记录锁,结果和「读未提交」的是一样的
继续看看范围的查询是否会触发间隙锁
begin;
select * from price_test where price > 5 for update;
begin;
select * from price_test where price > 15 for update;
在「读已提交」隔离级别下,只会使用行级别的记录锁,并不会用间隙锁。
脏读:是在并发事务中,一个事务可以读取到另一个未提交事务的数据
在“读已提交”隔离级别下,事务只能读取已经提交的数据,而不能读取未提交的数据。这意味着当一个事务正在进行修改时,其他事务无法读取到该事务所做的修改,直到该事务提交
为了实现"读已提交"隔离级别,数据库管理系统通常使用锁机制或多版本并发控制(MVCC)。锁机制可以确保一个事务在修改数据时对其他事务进行阻塞,以防止脏读。而MVCC则通过为每个事务创建不同的数据版本来实现隔离,并且只允许事务读取已提交的数据版本
我们设置一下隔离级别为「可重复读」
SET session TRANSACTION ISOLATION LEVEL REPEATABLE READ;
begin;
select * from price_test where id = 1;
begin;
update price_test set price = 20 where id = 1;
select * from price_test where id = 1;
同时对 id 为 1 的数据进行更新,看看会发生什么
begin;
update price_test set price = 15 where id = 1;
begin;
update price_test set price = 20 where id = 1;
继续看看范围的查询是否会触发间隙锁?
begin;
select * from price_test where price > 5 for update;
begin;
select * from price_test where price > 15 for update;
可以看到,在这里就变成了 Next-Key 锁,就是记录锁和间隙锁结合体
在「可重复读」隔离级别下,使用了记录锁、间隙锁、Next-Key 锁三种类型的锁
可重复读存在幻读的问题,但实际上在 MySQL 中,因为其使用了间隙锁,所以在「可重复读」隔离级别下,可用加锁解决幻读问题。因此,MySQL 将「可重复读」作为了其默认的隔离级别
对于任何隔离级别,表级别的表锁、元数据锁、意向锁都是会使用的,但对于行级别的锁则会有些许差别