Loading

试解析 SQL 语句在 MySQL InnoDB 引擎中的执行过程

SQL 语句在 MySQL InnoDB 引擎中的执行过程

本文旨在通过解析 SQL 语句在 MySQL InnoDB 引擎中的执行过程,梳理一波 MySQL InnoDB 结构、事务、锁、日志、索引等方面的知识点。实际的执行过程解析请看一和二两个小节,中间基本都是概念讲解。

个人能力有限,错漏之处敬请指出。

前置知识:《MySQL必知必会》等 MySQL 基础。

一点题外话,关于 MySQL 的读音,官方文档 1.2.1 What is MySQL? 里提到说 SQL 其实是三个分开的字母,不过连在一起读或者其他约定俗成的发音也完全可以的:

The official way to pronounce “MySQL” is “My Ess Que Ell” (not “my sequel”), but we do not mind if you pronounce it as “my sequel” or in some other localized way.

执行过程一

假定我们的表名是 t,字段有 id, a, b, cid 是自增主键。

首先泛泛地谈。MySQL 服务器的结构一般分两层,一层是 MySQL server,再往下一层是存储引擎。存储引擎管理单张表的存储,建表时指定,一般默认都是 InnoDB,ENGINE=InnoDB。当然有别的存储引擎,比如 MEMORY、MyISAM,可以执行 show ENGINES; 命令来查看当前 MySQL 支持的所有存储引擎。这是 MySQL 的灵活之处,它在 server 层有统一的 API 来调用不同的存储引擎,实际使用时,可以根据需求选择不同的存储引擎。

MySQL 的架构如下图所示。任何 SQL 语句,被服务端接收到以后都会经过这些组件:

  • MySQL server 层
    • 连接器。服务端收到客户端通过 TCP 连接发送的连接请求,连接器会对该请求进行权限验证及连接资源分配。服务端收到 SQL 语句,验证用户是否有执行该语句的权限。
    • 缓存层。缓存 SQL 语句及查询结果,可以认为是 MySQL 提供的一种缓存机制。可能是因为其它内存型缓存中间件,比如应用服务器的本机缓存 Guava Cache,单独的缓存软件 Memcached、Redis 等的广泛使用,以及 InnoDB 引擎提供的缓存功能,内存的读写比磁盘上的随机读写速度要快得多,MySQL 认为不再需要缓存层了,在版本 8 里移除了这一组件。
    • 解析器。在这里做语法分析和词法分析,生成解析树,主要是 SQL 语句的合法性验证。有没有想到编译原理?是的,SQL 也是一门语言,要执行它当然也需要先进行一波语法分析、词法分析啦。
    • 预处理。验证表名、字段名,以及别名是否有歧义。
    • 优化器。explain 生成的执行计划就是从这里来的。
    • 执行器。它主要负责向存储引擎发送执行请求,与它进行一些交互,最终将执行结果返回给用户。(如果 SQL 执行过程中超过了慢查询阀值,该 SQL 会被记录到慢查询日志中。)
  • 存储引擎层
    • 这一层是存储真实的数据,不同的存储引擎有不同的实现。InnoDB 是 B+ 树,MyISAM 也是 B+ 树。不同之处在于,MyISAM 中,实际的表数据存在单独的连续空间里, B+ 树是另一片空间,它的叶子节点不存行数据,而是存储指向该行数据的指针。InnoDB 中,真实的表数据就放在 B+ 树的叶节点上。
    • 事务、锁、日志、索引这些东西主要集中在存储引擎层以及 server 层的执行器中。

事务

MySQL 中默认是 auto commit 自动提交,每个查询都被当作一个事务执行操作。MySQL 服务器不管理事务,事务由存储引擎实现,有支持事务的存储引擎,有不支持事务的存储引擎。InnoDB 支持事务。

我们的 SQL 语句可以认为就是在各自的事务里执行的。

事务的特性 ACID

  • 原子性(Atomicity):事务中的所有查询一起成功(commit),一起失败(rollback)。所谓「同生共死」。由 回滚日志 undo log 保证。
  • 一致性(Consistency):事务前后,数据库的完整性约束没有被破坏。由其它三个特性以及 回滚日志 undo log 保证。
  • 隔离性(Isolation):各个事务之间相互隔离,不受影响。由锁和 MVCC 实现。
    • 并发场景下:读读(没有数据安全问题)、读写(MVCC)、写写(加锁)
  • 持久性(Durability):已完成的事务能够被保存下来。保证高可靠,而不是高可用。由重做日志 redo log 保证。

四种隔离级别

# 查看当前隔离级别
# MySQL 默认是 REPEATABLE-READ
select @@transaction_isolation;

# session 和 global 的隔离级别
show variables like'%transaction_isolation';
select @@session.transaction_isolation;
select @@global.transaction_isolation;

# 设置 session 和 global 隔离级别
set session transaction isolation level read committed;
set global transaction isolation level read committed;
  • read uncommitted 未提交读:事务中的修改,即使没有提交,对其他事物也是可见的。(读不加锁)
    • dirty read,脏读:事务读到了别的事务中未提交的、正在修改的数据。
    • 从性能上来说,不会好太多,但缺乏很多好处。
    • 读:不加锁不用 MVCC,不阻塞其它事务的读和写;
    • 写:加记录锁,阻塞其它事务的写,不阻塞其它事务的读。也就是读读、读写可以并行,写写不可以并行。
  • read committed 提交读:事务中的修改,提交后其它事务才看得到。(RC)
    • nonrepeatable read,不可重复读:两次执行同样的查询,得到不同的结果。
    • 读:用 MVCC,总是可以读到被锁定行的最新一份快照数据;
    • 写:加记录锁 Record Lock 算法,解决了脏读问题,但仍有幻读问题。读读可并行,读写、写写不可并行。
  • repeatable read 可重复读:解决了脏读问题,保证同一个事务中,多次读取同样记录的结果是一样的。(RR)
    • 是MySQL的默认事务隔离级别。
    • MySQL用 next-key lock 解决了幻读 phantom read 问题;
    • 读:用 MVCC,总是读取事务开始时的行数据版本;与临键锁共同解决了不可重复读问题;
    • 写:加独占锁,加记录锁、间隙锁、临键锁、next-key lock,解决了幻读问题;
  • serializable 可串行化,最高的隔离级别,强制事务串行执行,解决了幻读的问题,在读取的每一行数据上都加锁,可能导致大量的超时和锁争用的问题。实际很少用到,只有在非常需要确保数据一致性,且可以接受没有并发时,考虑用。
    • 实现:所有 select 语句自动加上 lock in share mode,加共享锁。读加共享锁;写加独占锁。读读可并行,读写、写写不可并行。如果有未提交的事务正在修改某些行,所有select这些行的语句都会阻塞。

小结

事务,可以认为是一个比较抽象的概念,ACID 的特性和隔离级别都不是 MySQL 独有的。从事务中可以看到三个关键概念:锁、MVCC、日志。它们是 SQL 执行时真正面对的东西。

SQL 语句可以简单分为两大类:查询和更新。查询就是 select,更新则包括 insert、delete、update在内,因为 insert、delete 的逻辑与update 差不多。下文将分析查询和更新两种语句的具体执行过程。

在 MySQL 默认隔离级别 RR 中,查询语句默认是不加锁的,除非显式地指定 lock in share mode 或者 for update

MVCC 的实现需要 undo log,锁不需要。锁又是锁在索引上的,所以先来看一下索引。

索引

索引的分类

根据不同的分类标准,索引有不同的分类:

  • 索引类型,也就是创建索引时需要选择的:普通索引(normal)唯一索引(unique)、全文索引(full text)、空间索引(spatial)
  • 存储方式,创建索引时需要选择的索引方法:B-Tree(不同存储引擎实现方式有差别)、Hash
  • 依赖列数:单列索引、联合索引(建立在多个字段上的索引,注意索引失效的情形)
  • 数据分布:聚簇索引(InnoDB 下,叶子结点存索引字段和整行数据)、二级索引(辅助索引、索引的索引,InnoDB 下,叶子结点上存索引字段和主键的值)
  • 回表情况:覆盖索引(索引上包含了查询所需要的所有字段时,所有字段指 select 和 where 里出现的所有字段)

聚簇索引包含了所有的数据,所以它也是一个覆盖索引。(×,不一定)
select * from t where id = 'x' and a = 'a1'
覆盖索引:select 和 where 里出现的列被索引包含,不需要回表,因为从索引里可以得到所有数据。此时 explain 的执行计划列表里, Extra 字段会显示 using index。
聚簇索引不用回表的话,是叶子节点上包含了需要的数据。

B+ 树

提到 mysql,就不能不谈 InnoDB;而聊到 InnoDB,就不能不讲一讲 B+ 树。

B+ 树是 InnoDB 存储引擎中存储数据的数据结构。一张表被指定为 InnoDB 存储引擎时,会创建一个以特定字段为排序规则的 B+ 树。如果有主键,这个特定字段就是主键;没有主键,但是有值不重复的字段,就是这个值不重复的字段;两个都没有,会加一个 6 字节的隐藏字段 db_row_id作为这张表的隐藏主键。

表数据就存在这棵 B+ 树上。非叶子结点存放主键字段和指向下面分叉的指针,叶子结点存放主键字段和字段所在行的整行数据。叶子结点之间有指针连接,单拎出来叶子结点的话,它是一个没有虚拟头结点(dummy节点、sentinel节点)的双向链表,方便全表扫描(从头到尾扫描)和范围查找(比如 id > 5 ,找到 id=5 所在的叶子结点,就可以根据向后的指针一串串起来了)。

一般都会显式地指定自增主键字段。这里涉及到 B+ 树的存盘方式。

  • 在计算机中,磁盘存储数据最小单元是扇区,一个扇区的大小是 512 字节(512 Byte = 512 * 8bit)。
  • 文件系统中,最小单位是块,一个块大小就是 4 kB(=8个扇区)(1 K = 1024 Byte = 2^10 Byte);
  • InnoDB 存储引擎最小储存单元是页,一页大小就是16 k(=4个块);一个叶子节点就是一页。

B+ 树叶子存的是数据,非叶节点存的是键值+指针。通过非叶子节点的二分查找法(B+ 树遵循左小右大的 BST 二叉搜索树规则)以及指针确定数据在哪个页(叶结点)中,再去数据页中找到需要的数据。

# 一页,默认大小是16kByte
show global status like 'Innodb_page_size';
# Innodb_page_size	16384 Byte

select 16384/1024;	-- 16kb --

新增一行数据存储到磁盘上时,如果是按照主键自增方式组织的 B+ 树,新来的数据可以直接放到老数据的后面,类似于链表尾巴上新加一个结点,当然,非叶子结点的一些值可能会有一些变化。这样的磁盘 IO 开销,比起索引字端不自增、新增数据需要插入到老数据里、页空间不够的话、就需要移来移去这些数据,要小得多。

一个页,也就是一个叶子结点,是可以存多条数据的。

不过,极致性能在业务面前也要做出妥协,如果业务确实会要存很多很多很多行数据,超出了 int 的取值范围,那就要用别的了,比如 UUID、雪花算法之类的。

锁的分类

  • 按锁的粒度分:行锁、表锁
  • 按范围(InnoDB 三种行锁的算法):
    • 记录锁(record lock,锁定记录)
    • 间隙锁(gap lock,锁定一个范围,不包括记录本身,防止多个事务将记录插入到同一范围内出现幻读问题)
    • 临键锁(next-key lock,行锁+间隙锁,锁定记录和范围,一般前开后闭)
  • 按读写:排它锁(X Lock 写锁)、共享锁(S Lock 读锁)
  • 按应用场景:悲观锁、乐观锁

行锁和表锁

MySQL 中提供了两种封锁粒度:行级锁以及表级锁

MySQL 服务器会为 alert table 的语句加表锁(table lock),而忽略存储引擎的锁机制。它以统一的 API 调用不同的存储引擎锁实现的行级锁行级锁(row lock)只在存储引擎层面实现,MySQL 服务器层没有实现。

InnoDB支持锁粒度锁定,允许事务在行级和表级上的锁同时存在。

  • 表级锁(意向锁 Intention Locks):

    • 意向共享锁(IS Lock),事务想要获得一张表中某几行的共享锁
    • 意向排它锁(IX Lock),事务想要获得一张表中某几行的排他锁,与 S/X 锁不兼容
  • 行级锁:

    • 共享锁(Shared Lock)S锁 读锁:
    • 排它锁(Exclusive Lock)X锁 写锁:与任何锁不兼容

两阶段锁定协议

MySQL InnoDB 采用的是两阶段锁定协议(two-phase locking protocol)。在事务执行过程中,随时都可以执行锁定,锁只有在执行 COMMIT 或者 ROLLBACK 的时候才会释放,并且所有的锁是在同一时刻被释放

死锁

场景:

# 事务1
update ... where id = 1;
update ... where id = 2;

# 事务2
update ... where id = 2;
update ... where id = 1;

双方各自拿着对方需要的锁,不释放。于是出现死锁。

InnoDB,目前处理死锁的方式是,将持有最少行级排他锁的事物进行回滚,是相对简单的死锁回滚算法。

解决方案:

  • 超时回滚(被动):innodb_lock_wait_timeout,当一个等待时间超过设置的阈值时,一个事务进行回滚,另一个等待的事务就可以继续进行。谁的锁范围更大谁释放锁;事务可能会被误杀。
  • 死锁检测(主动):wait-for graph,采用等待图的方式来检测死锁,图里有回路就是有死锁。

MVCC

MySQL :: MySQL 8.0 Reference Manual :: 15.3 InnoDB Multi-Versioning

MVCC(Multi Version Concurrency Control,多版本并发控制),主要解决并发场景下的读写问题,由三部分实现:隐藏字段、undolog、read-view。

隐藏字段

对于InnoDB 存储引擎,每一行记录都有两个隐藏列 DB_TRX_ID、DB_ROLL_PTR,如果表中没有主键和非NULL唯一键时,则还会有第三个隐藏的主键列DB_ROW_ID

  • db_trx_id,6-byte,创建或最近修改该记录的事务id。begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动,向 MySQL 申请事务 id,创建 read-view。MySQL内部是严格按照事务的启动顺序来分配事务 id 的。

    Also, a deletion is treated internally as an update where a special bit in the row is set to mark it as deleted.

  • db_roll_ptr,7-byte,回滚指针,记录上一个旧版本 undo log

  • db_row_id,6-byte,隐藏主键

undo log

  • 回滚日志,也就是这一行数据的历史记录
  • 更新一条记录时,添加这条记录,原先的记录放到undo log里。新增记录的回滚指针指向这个undolog。
  • 多个事务并行操作某一行数据时,不同事务对该行数据的修改会产生多个版本,然后通过回滚指针(DB_ROLL_PTR)连一条Undo日志链

read view

  • Read View 就是事务执行快照读时,产生的读视图。(快照读:读取的是记录数据的可见版本(有旧的版本),不加锁,普通的select 语句都是快照读。当前读:读取的是记录数据的最新版本,显示加锁(共享锁和排它锁)的都是当前读。)
  • 一致性视图是在事务里执行第一个 SQL 语句时候生成的。有执行查询时未提交事务id的数组(其中最小的min_id)和已创建的最大的事务max_id。则事务分为三部分:小于min_id的,都是已提交事务;大于max_id的,都是未开始事务;大于等于min_id小于等于max_id的,是未提交和已提交事务,只看是不是在数组里。
  • Read View 主要是用来做可见性判断的,即判断当前事务可见哪个版本的数据

这里有一个可见性判断。其实就是找到生成 read view 时,有哪些事务已经提交了,其中最大的那个事务 id 对应的表记录就是这个读视图可以拿到的数据。

只在 read committed 和 repeatetable read 两个隔离级别下工作。因为read uncommitted总是读取最新的数据行,serializable对所有读取的行都加锁。RC 读已提交 和 RR 可重复读 隔离级别不同的原因:RC每次快照读都会生成新的read view;RR只在第一次快照读的时候生成一次read view,之后沿用。

日志

binlog - MySQL server 层

  • 二进制日志,归档日志,逻辑日志,记录 SQL 语句。
  • 在 MySQL 的 server 层实现,引擎通用。
  • 不限大小、追加写入、不会覆盖以前的日志。
  • 主从复制时会用到它。
# 查看binlog是否开启
show variables like "%log_bin%";
# log_bin	ON
# log_bin_basename	C:\ProgramData\MySQL\data\binlog
# log_bin_index		C:\ProgramData\MySQL\data\binlog.index

# 新增一个binlog
flush logs;

# 删除整张表
truncate t_table;

# sync_binlog 设为1,表示每次事务binlog都将持久化到磁盘

 # 查看binlog
 mysqlbinlog --no-defaults binlog.000176

redo log - InnoDB

  • 重做日志、物理日志,也可以认为是个二进制日志,记录对每个页的物理修改操作。

每条 redo 记录由“表空间号+数据页号+偏移量+修改数据长度+具体修改的数据”组成

  • WAL(Write Ahead Logging),预写日志,顺序读写和随机读写,先写日志再写磁盘。
  • 写在文件 ib_logfile0 和 ib_logfile1。文件写满,切换到另一个文件。write pos 是当前记录的位置,checkpoint 是当前要擦除的位置,会将重做日志里的数据同步到数据表文件里,再擦除数据。
  • 每次写入 redo log 文件后,InnoDB 引擎都会调一次 fsync 刷盘操作。
  • redo log 在事务进行中不断地被写入。binlog 是事务提交以后写入一次。

二阶段提交:redolog 和 binlog 会同时记录。innoDB生成一个redo log(放到 redo log buffer,内存里),是prepare状态;server 写binlog;redo log 持久化(事务提交时)commit 状态。如果写入 binlog 失败,视为该事务失败。

为什么 redo log 要引入 prepare 预提交状态?反证法:

  • 先写 redo log 直接提交,然后写 binlog,假设写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器 InnoDB 会通过 redo log 恢复数据,但是这个时候 bin log 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。
  • 先写 binlog,然后写 redo log,假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。

如果采用 redo log 两阶段提交的方式就不一样了,写完 binglog 后,然后再提交 redo log 就会防止出现上述的问题,从而保证了数据的一致性。那么问题来了,有没有一个极端的情况呢?假设 redo log 处于预提交状态,binglog 也已经写完了,这个时候发生了异常重启会怎么样呢? 这个就要依赖于 MySQL 的处理机制了,MySQL 的处理过程如下:

  • 判断 redo log 是否完整,如果判断是完整的,就立即提交。
  • 如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log, 不完整就回滚事务。

这样就解决了数据一致性的问题。

undo log - InnoDB

  • 是逻辑日志,记录了数据的历史版本。
  • 没有专门的文件来存,是跟数据存在一起的。把磁盘分段,段分成区,区再分页。有一个特殊的段(segment)来存 undo log,被叫做 undo 段(undo segment)。
  • MVCC 的实现依赖于 undo log。
  • 它的产生随着 redo log 的产生。
  • UNDO LOG 存储在 UNDO PAGE 中。UNDO LOG 也存在两种类型:insert undo 和 update undo。其中 insert undo 记录 insert 语句中生成的 undo log,而 update undo 则记录 update/delete 等语句中生成的 undo log。每个undo page只存储一种类型的UNDO LOG。

insert 产生的 undolog,只有在事务回滚时才需要,在事务提交之后直接清除(purge 线程);
update 和 delete产生的 undo log,事务回滚、快照读时都需要,只有在确定不会再这样操作时才会清除。

集群

主从复制 读写分离

多台 MySQL 服务器,分一台主服务器 master 和多台从服务器 slave。master 负责写命令,更新操作;slave 负责读命令,查询操作。 适合读多写少的业务场景。

服务器之间数据同步的逻辑如下:

  1. master 更新了数据,写入 binlog
  2. 发现更新,通知 slave
  3. IO 线程将更新写入 relaylog
  4. SQL 线程将更新写入 slave

强制路由

如果因为网络问题或其它原因,添加了一条数据,主服务器里有了记录,但是从服务器没办法查得到:

# 从slave从服务器取数据;查不到
select * from t where id = 1;
# 添加注解,强制路由到 master 主服务器去查
/*master*/select * from t where id = 1;

在应用层的实现

数据库底层变了,在应用程序里的 SQL 语句要怎么变呢?一般有两种解决方案。

  1. proxy(代理层):加一层代理。以前,war 程序发送 SQL 给 MySQL 服务器,现在直接丢给代理了,由代理来判断出读操作和写操作,分别发送给相应的服务器。如 altas 开源中间件。
  2. jdbc增强(应用层):改程序,分别向不同的 MySQL 服务器发读写请求。

执行过程二

server - 加表锁

在第一小节之后,经过了 「SQL 在事务里执行,默认一条语句就是一个事务」,以及那么多的概念,我们终于又回到了本文主旨:SQL 的执行过程。

对于查询语句来说,有没有锁都会继续执行,它会通过 MVCC 读数据。

对于更新语句,select ... lock in share modeselect ... for update 也被归在更新语句里的。执行器根据优化器生成的执行计划,查看表上是否有意向排它锁(IX)。如果有,事务被阻塞,等待意向排它锁被释放之后继续执行。如果没有 IX 锁,会根据语句的性质和表上是否有意向共享锁(IS)来决定事务是否执行。如果语句是要加共享锁,表上有没有 IS 锁都会继续执行,因为读读可并行;对于要加排它锁的语句,因为它需要先在表上加意向排它锁,如果表上没有 IS 锁,会加 IX 锁,继续执行,如果表上已经有了 IS 锁,事务也会被阻塞。事务阻塞时间由 lock_wait_timeout 这个参数决定。

select @@lock_wait_timeout
# 31536000

表锁是在 sever 层的执行器上加的。执行器确定语句可以继续执行,就会交给存储引擎来执行啦。

InnoDB 缓存区

这里要提一下 InnoDB 的缓存机制。结构大概如图所示。

Buffer Pool,缓存区,默认大小是 128 M,以页为单位进行组织,是一个类似数组的连续空间,存放实际缓存的数据。用额外的 free 链表来管理空的格子,有基节点(虚拟头节点),之后的节点存储空白格子的地址。

InnoDB - 查询

对于查询语句,先看缓冲区里有没有,有的话直接拿到返回;没有的话,再去磁盘里读数据。

这里就是 MVCC 要上场啦。注意,普通的查询语句是不加锁的,什么锁都不加。

当前事务真正开始执行时,会分配一个事务 ID;在查询语句触发快照读的时候,会生成一个一致性读视图 read view。RC 隔离级别下,每个查询语句都会生成新的 read view,这也是它会出现重复读问题的原因;默认的 RR 隔离级别下,该事务的每一个查询语句都会从这个视图里读数据,也就是根据可见性算法判断读取该行数据的哪个版本,同一个快照中数据相同,解决了重复读的问题。

InnoDB - 更新

对于更新语句,也是先查缓冲区里有没有,没有的话,从磁盘里查询出数据所在的页(也就是 B+ 树的叶子节点),放到缓冲区里。然后修改缓冲区里的数据。

要加行锁的,在默认隔离级别 RR 下:

  • 会锁住行数据(记录锁,比如说锁了5<=id<=10这几条数据),
  • 锁住数据前后的空隙(间隙锁,比如说,假定id=10之后就是id=15,那么会锁住(10,15) 之间的这个空隙,在这个锁还存在,也就是当前事务在运行时,如果有另外的事务想要修改或者插入一条id=12的记录,会被阻塞。)
  • 记录锁+间隙锁加起来就被称作是临键锁。

加完锁,拿完数据,修改好,就该日志出场啦。

首先, InnoDB 会生成一个 redo log,这个 redo log 放到内存的 redo log buffer 里,被标记为 prepare 状态;然后告诉 server 层,我这边更新好了;

接着,server 层的执行器收到成功信息,会写一波 bin log。如果 MySQL 部署了主从复制,那么,主服务器 master 会有一个线程 log dump 去通知从服务器 slave,bin log 有更新需要写入;slave 的 I/O 线程去 master 请求更新的 bin log,保存在 slave 的 relay log 中;slave 的 SQL 线程检测到 relay log 有更新,会将更新的内容写入 slave。

最后,bin log 写完,事务就该提交啦,InnoDB 会在事务提交时更改 redo log 的状态为 commit。

更新操作会导致缓冲区和磁盘的数据不一致,出现脏页。可以看到,后序再有对该数据的操作,一律在缓冲区里查询或更新,所以暂时不存磁盘也没关系。有一个后台线程,会定期查找脏页,持久化到磁盘。

posted @ 2021-12-02 08:24  vvwantspeed  阅读(338)  评论(0编辑  收藏  举报