面试官:MySQL 是如何实现 ACID 的?

2023年 8月 28日 30.1k 0

在面试中,面试官只要问MySQL的ACID,然后可以立马背出来八股文来(还有部分人估计都还回答不上来)。更可恶的是,有些面试官不按套路出牌,会继续问了,MySQL到底是如何实现ACID的呢?

蒙圈了吧,实话实说,这道题能劝退95%的人。

今天,本文主要探讨MySQL InnoDB 引擎下ACID的实现原理,对于诸如什么是事务,隔离级别的含义等基础知识不做过多阐述。

ACID

MySQL 作为一个关系型数据库,以最常见的 InnoDB 引擎来说,是如何保证 ACID 的。

  • (Atomicity)原子性: 事务是最小的执行单位,不允许分割。原子性确保动作要么全部完成,要么完全不起作用;
  • (Consistency)一致性: 执行事务前后,数据保持一致;
  • (Isolation)隔离性: 并发访问数据库时,一个事务不被其他事务所干扰。
  • (Durability)持久性: 一个事务被提交之后。对数据库中数据的改变是持久的,即使数据库发生故障。

隔离性

先说说隔离性,首先是四种隔离级别。

隔离级别 说明
读未提交 一个事务还没提交时,它做的变更就能被别的事务看到
读提交 一个事务提交之后,它做的变更才会被其他事务看到
可重复读 一个事务中,对同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。InnoDB默认级别。
串行化 事务串行化执行,每次读都需要获得表级共享锁,读写相互都会阻塞,隔离级别最高,牺牲系统并发性。

不同的隔离级别是为了解决不同的问题。也就是脏读、幻读、不可重复读。

隔离级别 脏读 不可重复读 幻读
读未提交 可以出现 可以出现 可以出现
读提交 不允许出现 可以出现 可以出现
可重复读 不允许出现 不允许出现 可以出现
序列化 不允许出现 不允许出现 不允许出现

那么不同的隔离级别,隔离性是如何实现的,为什么不同事物间能够互不干扰?答案是 锁 和 MVCC。

先来说说锁, MySQL 有多少锁。

粒度

从粒度上来说就是表锁、页锁、行锁。表锁有意向共享锁、意向排他锁、自增锁等。行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如 MyISAM 引擎就不支持行锁。

行锁的种类

在 InnoDB 事务中,行锁通过给索引上的索引项加锁来实现。这意味着只有通过索引条件检索数据,InnoDB才使用行级锁,否则将使用表锁。行级锁定同样分为两种类型:共享锁和排他锁,以及加锁前需要先获得的意向共享锁和意向排他锁。

  • 共享锁:读锁,允许其他事务再加S锁,不允许其他事务再加X锁,即其他事务只读不可写。select...lock in share mode 加锁。
  • 排它锁:写锁,不允许其他事务再加S锁或者X锁。insert、update、delete、for update加锁。

行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

行锁的实现算法

Record Lock

单个行记录上的锁,总是会去锁住索引记录。

Gap Lock

间隙锁,想一下幻读的原因,其实就是行锁只能锁住行,但新插入记录这个动作,要更新的是记录之间的“间隙”。所以加入间隙锁来解决幻读。

Next-Key Lock

Gap Lock + Record Lock, 左开又闭。

锁之于隔离性

大致介绍了下锁,可以看到。有了锁,当某事务正在写数据时,其他事务获取不到写锁,就无法写数据,一定程度上保证了事务间的隔离。但前面说,加了写锁,为什么其他事务也能读数据呢,不是获取不到读锁吗?

MVCC

前面说到,有了锁,当前事务没有写锁就不能修改数据,但还是能读的,而且读的时候,即使该行数据其他事务已修改且提交,还是可以重复读到同样的值。这就是MVCC,多版本的并发控制,Multi-Version Concurrency Control。

版本链

Innodb 中行记录的存储格式,有一些额外的字段:DATA_TRX_ID和DATA_ROLL_PTR。

  • DATA_TRX_ID:数据行版本号。用来标识最近对本行记录做修改的事务 id。
  • DATA_ROLL_PTR:指向该行回滚段的指针。该行记录上所有旧版本,在 undo log 中都通过链表的形式组织。

undo log : 记录数据被修改之前的日志,后面会详细说。

面试官:MySQL 是如何实现 ACID 的?

ReadView

在每一条 SQL 开始的时候被创建,有几个重要属性:

  • trx_ids: 当前系统活跃(未提交)事务版本号集合。
  • low_limit_id: 创建当前 read view 时“当前系统最大事务版本号+1”。
  • up_limit_id: 创建当前read view 时“系统正处于活跃事务最小版本号”
  • creator_trx_id: 创建当前read view的事务版本号;

面试官:MySQL 是如何实现 ACID 的?

开始查询

现在开始查询,一个 select 过来了,找到了一行数据。

  • DATA_TRX_ID = low_limit_id:

    说明该数据是在当前read view 创建后才产生的,数据不显示。

    • 不显示怎么办,根据 DATA_ROLL_PTR 从 undo log 中找到历史版本,找不到就空。
  • up_limit_id

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论