vlambda博客
学习文章列表

RDB | MySQL的事务隔离级别和锁的机制

【MySQL总结/Edison Zhou

对于后端开发面试,关系型数据库是一个必问考点。而在目前主流的关系型数据库中,MySQL无疑是互联网圈中最火热的一个。掌握MySQL的核心知识点,对于面试取得一个好成绩很重要!

1四个事务隔离级别

我们都知道目前MySQL的事务隔离级别主要有四个,分别是:读未提交、读已提交、可重复读 和 串行化,如下图所示:

RDB | MySQL的事务隔离级别和锁的机制

按隔离水平高低排序:读未提交<读已提交<可重复读<串行化

(1)读未提交:读未提交:隔离级别最低,隔离度最弱,理论存在,实际中几乎没有人用,但性能最高。该级别存在的问题包括 脏读、不可重复读 和 幻读。

(2读已提交:保证了事务不出现中间状态的数据,所有数据都是已提交且更新的。该级别存在的问题有 不可重复读 和 幻读。

(3可重复读:MySQL InnoDB引擎的默认隔离级别,保证了一个事务中多次读取数据的一致性。该级别存在的问题有 幻读,但它是MySQL的默认事务隔离级别。

(4可串行化:读数据需要加共享锁,写数据需要加排它锁,安全性最高,性能最低,但该级别解决了幻读的问题。

悲观锁与乐观

在关系型数据库中,按使用方式来说,有两种典型的锁,分别是 悲观锁 与 乐观锁。

悲观锁认为认为数据出现冲突的可能性很大,例子:SELECT ... FOR UPDATE。

乐观锁认为数据出现冲突的可能性不大,例子:对比数据行的时间戳或版本号。

RDB | MySQL的事务隔离级别和锁的机制

表级锁和行级锁

在关系型数据库中,按粒度来划分的话,有三种类型的锁,分别是 表级锁、行级锁 和 页级锁。我们常用的锁有行级锁和表级锁。

MySQL中表级锁有两类:一是表锁,二是元数据锁。表锁的语法为:lock tables ... read/write。它限制了别的线程,也限定了本线程接下来的操作对象

表锁一般是在数据库引擎不支持行锁的时候才会被用到,比如MyISAM引擎。

MySQL 5.5引入了MDL(元数据锁),不需要显示使用,在访问一个表的时候会自动被加上。

  • 当对一个表做增删改查时,自动加MDL读锁(读锁之间不互斥)

  • 当对一个表做表结构变更时,自动加MDL写锁(读写锁与写锁之间互斥)

MDL的作用:保证读写的正确性

MDL带来的问题:修改表结构时申请MDL写锁,可能会导致锁表,后续业务操作均会阻塞。

在MySQL中,行锁是引擎层由各个引擎自己实现的,但并不是所有引擎都支持行锁例如MyISAM就不支持,所以我们默认会使用InnoDB引擎

所谓行锁,就是针对数据表中行记录的锁。比如,事务A更新了一行,这时事务B也要更新同一行,那么事务B需要等待事务A的操作完成后才能进行

更多关于表锁和行锁的内容请阅读《》。

2理解脏读、不可重复读与幻读

脏读

所谓脏读,就是指 读到了未提交事务的数据。

RDB | MySQL的事务隔离级别和锁的机制

脏读对应的隔离级别是“读未提交”,只有该隔离级别才会出现脏读。

脏读的解决办法是升级事务隔离级别,比如“读已提交”。

不可重复读

所谓不可重复读,就是两次读取的数据中途被修改。

RDB | MySQL的事务隔离级别和锁的机制

不可重复读对应的隔离级别是“读未提交”或“读已提交”

不可重复读的解决办法就是升级事务隔离级别,比如“可重复读”。

幻读

所谓幻读,是指在同一个事务内,同一条查询语句在不同的时间段执行会得到不同的结果集。

RDB | MySQL的事务隔离级别和锁的机制

解决幻读不能简单升级事务隔离级别到可串行化,因为会让MySQL失去并发处理能力

行锁解决不了幻读,因为即使锁住所有记录,还是阻止不了插入新数据。

解决幻读的办法是锁住记录之间的“间隙”,为此InnoDB引入了新的间隙锁 (Gap Lock) 和next-key lock锁。

顾名思义,间隙锁,锁的就是两个值之间的空隙

比如下图所示的某张表t,在该表主键索引(id)上插入了6个记录(0,5,10,15,20,25),因此产生了7个间隙

CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`)) ENGINE=InnoDB;
insert into t values(0,0,0),(5,5,5),(10,10,10),(15,15,15),(20,20,20),(25,25,25);

RDB | MySQL的事务隔离级别和锁的机制

图片来源:林晓斌《MySQL实战45讲》

间隙锁是专门用于解决幻读这种问题的锁,它锁了行与行之间的间隙,这样就能够阻塞新插入的操作。

因此,划重点:当MySQL InnoDB引擎在一行行扫描的过程中,不仅会给行加上行锁,还会给行的两边的空隙也加上间隙锁。

由于间隙锁(gap lock)仍在存在一些问题,可能会降低并发度和仍然可能导致死锁。因此,MySQL InnoDB为间隙锁引入了一个补充:next-key lock。

那么,问题来了:啥是next-key lock?

所谓next-key lock,它是间隙锁和行锁的合体,每个next-key lock都是前开后闭区间,如 (0,5]

间隙锁都是开区间,如 (0,5)。

next-key lock帮助MySQL在默认隔离级别下解决了幻读问题,因此它也是MySQL加锁的基本单位。

更多关于间隙锁和next-key lock的介绍请阅读《》

3正确理解死锁

死锁是如何产生的?

死锁一般发生在多线程(两个或两个以上)执行的过程中,因为争夺资源造成线程之间相互等待。

RDB | MySQL的事务隔离级别和锁的机制

死锁的产生需要具备4个条件:

(1)互斥:多个线程不能同时使用一个资源;

(2)持有并等待:在已经持有了资源1的情形下又申请资源2,但是资源2已经被其他事务占用;

(3)不可剥夺:在事务已经获取到资源1之后,在使用完之前都不可以被其他事务抢占;

(4)循环等待:发生死锁时,必然会存在一个事务,也就是资源的环形链;

如何避免死锁发生?

对于死锁,我们需要有一个基本认知:死锁只有同时满足四个条件时才会发生,并发场景下一旦发生死锁,并没有什么特别好的办法,最好是规避死锁。

因此,要避免死锁,我们需要至少破坏四个条件中的一个(互斥必须满足,可以从其他三个条件出发)。

(1)破解持有并等待:我们可以一次性申请所有的资源;

(2)破解不可剥夺:如果申请不到资源,可以主动释放占有的资源;

(3)破解循环等待:按序申请资源来预防(即资源有序分配原则);

End总结

本文总结了MySQL的事务隔离级别和锁的机制相关的知识点,希望能对你有所帮助!不过,本文只是高层概览,更详细的细节还是需要你参考更多扩展资料才能应对面试的需求。下一篇,我们会聚焦MySQL如何优化数据查询的方案,即在读多写少的场景下如何优化查询性能。


参考资料

丁奇,《MySQL实战45讲》(极客时间)

刘海丰,《架构设计面试精讲》(拉勾教育)

施瓦茨,《高性能MySQL(第三版)》(图书)

狼爷,《》(博客园)

RDB | MySQL的事务隔离级别和锁的机制
年终总结:
数字化转型:
C#刷题:
.NET面试:
.NET大会:

👇扫码关注EdisonTalk

EdisonTalk
勤于学习 • 善于总结 • 乐于分享
372篇原创内容
Official Account