《高性能MySQL》读书笔记 第一章 MySQL架构

本文为我阅读《高性能MySQL》第四版(作者为Silvia Botros和Jeremy Tinley)时的笔记,这本书的MySQL版本是到8.0的,所以更新的版本语法和特性可能有所改变,仅供参考。本篇笔记适合有一点点MySQL基础(至少得会增删改查基础语法)的人进行进一步学习。

并发控制

读写锁

处理并发读/写访问的系统通常实现一个由两种锁类型组成的锁系统。这两种锁通常被称为共享锁(shared lock)和排他锁(exclusive lock),也叫读锁(read lock)和写锁(write lock)。

资源上的读锁是共享的,或者说是互不阻塞的。多个客户端可以同时读一个资源而互不打扰。

写锁则是排他的,一个写锁既会阻塞读锁也会阻塞其他写锁,这是处于安全策略的考虑,确保特定的时间点只有一个客户端能写入,并防止其他客户端读取正在写入的数据。

其他锁补充:

记录锁(Record Lock):属于行级锁,只锁具体某一条记录。必须命中索引(主键 / 唯一索引)才会是真正的记录锁,比如有如下一张表,其中id是主键

id name money
1 张三 100
2 李四 200
  • 情况1:条件用主键(命中索引)
1
UPDATE user SET money=100 WHERE id=1;

MySQL 一看:id=1 是主键,直接定位到这一行,就只锁id=1这一行

  • 情况2:条件用没索引的字段(没命中索引)
1
UPDATE user SET money=100 WHERE name='张三';

name 没有索引,MySQL 不知道张三在哪一行,只能全表扫描,为了安全,它会把所有行都锁住,那就变成表锁了


意向锁(Intention Lock):表级别的 “预告锁”,它不是锁住表,而是告诉别人:我准备对表里某些行加锁了。分为:

  • 意向共享锁(IS):准备加 S 锁(共享锁)
  • 意向排他锁(IX):准备加 X 锁(排他锁)

作用是快速判断整张表能不能加表锁。如果有人加了 IX/IS,说明表里有行被锁住,那么别人就不能直接对整张表加 X 锁(比如 ALTER TABLE),这样就可以避免遍历所有行去检查有没有锁。

什么时候会产生意向锁?

对某一行加 共享锁(S 锁)→ 表自动加 意向共享锁 IS

对某一行加 排他锁(X 锁)→ 表自动加 意向排他锁 IX

比如:

1
SELECT * FROM t WHERE id=1 FOR UPDATE;

执行这条语句时会对 id=1 这一行加 X 锁,同时对整张表加 IX 意向排他锁。


总结:

级别 作用 互斥关系
共享锁 S 允许读,禁止写 S-S 兼容,S-X 互斥
排他锁 X 独占,禁止读写 与所有锁互斥
记录锁 锁住某一行具体记录 行级互斥
意向锁 IS/IX 预告要加行锁,方便判断表锁能否加 与表锁互斥,与行锁不互斥

锁的粒度

尽量只锁定包含需要修改的部分数据,而不是所有的资源。更理想的方式是,只对需要修改的数据片段进行精确的锁定。

但问题是加锁也需要消耗资源,锁的各种操作,包括获取锁、检查锁是否空闲、释放锁等,都会增加系统的开销。如果花费大量时间来管理锁,而不是存取数据,那么系统的性能可能会受到影响。

锁定策略就是锁开销和数据安全之间的平衡。大多商业数据库系统一般都是在表中施加行级锁【1】(row level lock),为了在锁比较多的情况下尽可能提供更好的性能,锁的实现方式非常复杂。

MySQL则提供了多种选择。每种MySQL存储引擎都可以实现自己的锁策略和锁粒度。将锁粒度固定在某个级别,可以提高某些应用场景下的性能,但同时会使其不适合另外一些应用场景。

下面是两种最重要的锁策略:

  • 表锁

表锁(table lock)是MySQL中最基本也是开销最小的锁策略。如字面意思,它会锁定整张表。当客户端想对表进行写操作(插入、删除、更新等)时,会获得一个写锁,这会阻塞其他客户端对该表的所有读写操作。只有没有人执行写操作时,其他读取的客户端才能获得读锁,读锁之间不会相互阻塞。

表锁有一些变体,可以在特定情况下提高性能。例如,READ LOCAL表锁支持某些类型的并发写操作。写锁队列和读锁队列是分开的,但写锁队列的优先级绝对高于读队列。

  • 行级锁

使用行级锁(row lock)可以最大程度地支持并发处理(也带来了最大的锁开销)。这种策略允许多人同时编辑不同的行,而不会阻塞彼此。就是 MySQL 只锁住你正在操作的那一行数据,别的行别人照样能改。

比如你有一张用户表:

id name money
1 张三 100
2 李四 200
3 王五 300

事务A在修改id=1这一行,其他事务可以正常修改id=2id=3的行,不会阻塞

这使得服务器可以执行更多的并发写操作,带来的代价则是需要承担更多开销,以跟踪谁拥有这些行级锁、已经锁定了多长时间、行级锁的类型,以及何时该清理不再需要的行级锁。

行级锁是在存储引擎而不是服务器中实现的。服务器【1】通常不清楚存储引擎中锁的实现方式。因为每种存储引擎都以自己的方式来实现锁。

【1】:服务器指的是MySQL Server(MySQL 服务层)。MySQL整体分成两层,分别是MySQL Server 层(服务层)和存储引擎层(InnoDB、MyISAM 等)。下图是MySQL服务器架构的逻辑视图:

image-20260330200901726

简单来说服务器负责接收 SQL、解析 SQL、优化 SQL、执行计划、权限校验、连接管理、调用存储引擎去读写数据。

存储引擎负责真正把数据写到磁盘、管理索引、实现锁、事务、崩溃恢复。

事务

事务就是一组SQL语句,作为一个工作单元以原子方式进行处理。如果数据库引擎能够成功地对数据库应用整组语句,那么就执行该组语句。如果其中有任何一条语句因为崩溃或其他原因无法执行,那么整组语句都不执行。也就是说,作为事务的一组语句,要么全部执行成功,要么全部执行失败。

银行应用是解释事务必要性的经典例子。假设一个银行的数据库有两张表:支票表(checking)和储蓄表(savings)。现在要从用户Jane的支票账户转移200美元到她的储蓄账户,那么需要至少三个步骤:

  1. 确保支票账户的余额高于200美元。
  2. 从支票账户的余额中减去200美元。
  3. 在储蓄账户的余额中增加200美元。

以上三步操作必须打包在一个事务中,以保证一旦其中任何一步失败,都能够回滚所有的操作。

可以用START TRANSACTION语句启动事务,然后要么成功执行到COMMIT提交事务将修改的数据持久保留,要么中途失败,使用ROLLBACK撤销所有的修改。本例中的事务的SQL语句可能如下:

1
2
3
4
5
START TRANSACTION;
SELECT balance FROM checking WHERE customer_id = 10233276;
UPDATE checking SET balance = balance - 200.00 WHERE customer_id = 10233276;
UPDATE savings SET balance = balance + 200.00 WHERE customer_id = 10233276;
COMMIT;

执行上面语句时,若没有事务可能会发生什么?

试想一下,如果数据库在执行第4条语句时崩溃了,客户可能会损失200美元。再假如,在执行到第3条语句和第4条语句之间时,另外一个进程要删除支票账户的所有余额,结果可能是银行不知不觉白白给了Jane 200美元。

在这一系列操作中,有更多的失败可能性。连接可能会断开、会超时,甚至数据库服务器在操作执行过程中会崩溃。这就是存在高度复杂且缓慢的两阶段提交系统的典型原因:为了应对各种失败场景。

除非系统通过严格的ACID测试,否则空谈事务的概念是不够的。ACID代表原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。一个确保数据安全的事务处理系统,必须满足这些密切相关的标准。

  • 原子性(atomicity)

一个事务必须被视为一个不可分割的工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。

  • 一致性(consistency)

数据库总是从一个一致性状态转换到下一个一致性状态。在前面的例子中,一致性确保了,即使在执行第3、4条语句之间时系统崩溃,支票账户中也不会损失200美元。如果事务最终没有提交,该事务所做的任何修改都不会被保存到数据库中。

  • 隔离性(isolation)

通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的,这就是隔离性带来的结果。在前面的例子中,当执行完第3条语句、第4条语句还未开始时,此时有另外一个账户汇总程序开始运行,其看到的支票账户的余额并没有被减去200美元。

  • 持久性(durability)

一旦提交,事务所做的修改就会被永久保存到数据库中。此时即使系统崩溃,数据也不会丢失。持久性是一个有点模糊的概念,实际上持久性也分很多不同的级别。有些持久性策略能够提供非常强的安全保障,而有些则未必。而且不可能有100%的持久性保障(如果数据库本身就能做到真正的持久性,那么备份又怎么能增加持久性呢?)。

隔离级别

隔离性在实际操作中比看起来复杂得多。ANSI SQL标准定义了4种隔离级别。

READ UNCOMMITTED(未提交读)

这个级别,在事务中可以查看其他事务中还没有提交的修改。这个隔离级别,在性能并没有比其他级别好多少的情况下,不仅导致了很多问题,还缺乏其他级别的很多好处。除非有非常必要的理由,在实际应用中一般很少使用。读取未提交的数据,也称为脏读(dirty read)。

READ COMMITTED(提交读)

大多数数据库系统的默认隔离级别是这个(但MySQL不是)。这个级别满足前面提到的隔离性的简单定义:一个事务可以看到其他事务在它开始之后提交的修改,但在该事务提交之前,其所做的任何修改对其他事务都是不可见的。这个级别仍然允许不可重复读(nonrepeatable read),这意味着同一事务中两次执行相同语句,可能会看到不同的数据结果,比如第一次查询和第二次查询间,数据被其他事务修改了。

REPEATABLE READ(可重复读)

可重复读解决了提交读级别的不可重复读问题,保证了在同一个事务中多次读取相同行数据的结果是一样的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(phantom read)的问题。幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行【1】(phantom row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。

SERIALIZABLE(可串行化)

这是最高的隔离级别。该级别通过强制事务按序执行,使不同事务之间不可能产生冲突,从而解决了前面说的幻读问题。简单来说,这个级别会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中很少用到这个隔离级别,除非需要严格确保数据安全且可以接受并发性能下降的结果。

隔离级别 脏读 不可重复读 幻读 加锁读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

【1】:指在同一个事务里,前后两次执行相同的查询,第二次查询结果里多出了之前不存在的行(这些行是其他事务在这期间新插入的)。

死锁

死锁是指两个或多个事务相互持有和请求相同资源上的锁,产生了循环依赖(下面会举例说明)。当多个事务试图以不同的顺序锁定资源时会导致死锁。当多个事务锁定相同的资源时,也可能会发生死锁。

例如,设想运行下面两个针对主键为(stock_id,date)的StockPrice表的事务:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 事务1
START TRANSACTION;
UPDATE StockPrice SET close = 45.50 WHERE stock_id = 4 and date = '2020-05-01';
UPDATE StockPrice SET close = 19.80 WHERE stock_id = 3 and date = '2020-05-02';
COMMIT;

# 事务2
START TRANSACTION;
UPDATE StockPrice SET high = 20.12 WHERE stock_id = 3 and date = '2020-05-02';
UPDATE StockPrice SET high = 47.20 WHERE stock_id = 4 and date = '2020-05-01';
COMMIT;

上面两个事务都开始执行第一个查询,在处理过程中会更新一行数据,同时在主键索引和其他唯一索引中将该行锁定。然后,每个事务将在第二个查询中尝试更新第二行数据,却发现该行已经被锁定。这两个事务将永远等待对方完成,除非有其他因素介入解除死锁。

为了解决这个问题,数据库系统实现了各种死锁检测和锁超时机制。更复杂的系统,比如InnoDB存储引擎,检测到循环依赖后会立即返回一个错误信息。

还有一种方式,当超过锁等待超时的时间限制后直接终止查询,这样做通常来说不太好。InnoDB目前(MySQL8.0版本)处理死锁的方式是将持有最少行级排他锁的事务回滚(这是一种最容易回滚的近似算法)。

一旦发生死锁,如果不回滚其中一个事务(部分或全部),就无法打破死锁。对于事务型的系统【1】,这是无法避免的,所以应用程序在设计时必须考虑如何处理死锁。大多数情况下只需要重新从头开始执行被回滚的事务即可,除非又遇到另一个死锁。

【1】事务型系统(Transaction Processing System, TPS),就是以事务为核心操作单元的业务系统,核心要求是保证数据操作的原子性、一致性、隔离性、持久性(ACID)。可以先简单理解为:必须保证要么全做,要么全不做的系统,如银行转账系统、电商订单系统、股票交易系统等。

非事务型系统(Non-transactional System)是指不依赖事务(ACID)保障,对数据一致性要求相对宽松的系统。它不需要保证 “要么全做、要么全不做”,更侧重高吞吐、低延迟、简单高效。比如日志系统、内容发布系统、临时数据系统、统计系统等。

维度 事务型系统 非事务型系统
核心目标 强一致性、数据安全 高吞吐、低延迟、简单
一致性要求 必须满足 ACID 允许最终一致或短暂不一致
死锁风险 高(行级锁 + 并发高) 低(表锁 / 无锁,无复杂锁等待)
典型业务 银行转账、电商订单、股票交易 日志系统、统计报表、内容发布
代表存储引擎 InnoDB、PostgreSQL MyISAM、CSV、部分 NoSQL

事务日志

事务日志有助于提高事务的效率。存储引擎只需要更改内存中的数据副本,而不用每次修改磁盘中的表,这会非常快。然后再把更改的记录写入事务日志中,事务日志会被持久化保存在硬盘上。因为事务日志采用的是追加写操作,是在硬盘中一小块区域内的顺序I/O,而不是需要写多个地方的随机I/O,所以写入事务日志是一种相对较快的操作。顺序读写和随机读写的速度差距在机械硬盘上巨大,即使在固态硬盘上也有一定差距。

最后会有一个后台进程在某个时间去更新硬盘中的表。因此,大多数使用这种技术(write-ahead logging,预写式日志【1】)的存储引擎修改数据最终需要写入磁盘两次【2】

如果修改操作已经写入事务日志,那么即使系统在数据本身写入硬盘之前发生崩溃,存储引擎仍可在重新启动时恢复更改。具体的恢复方法则因存储引擎而异。

【1】: WAL(预写式日志) 的保险机制:

  1. 修改先在内存完成:执行 UPDATE 时,InnoDB 只改内存里的数据页,这一步非常快。
  2. 立刻把变更写入日志文件:内存里的修改会被记录到 Redo Log(重做日志),并持久化到磁盘。这一步是顺序 I/O,很快。
  3. 后台慢慢刷盘:有个后台线程(比如 InnoDB 的 checkpoint 线程)会在系统空闲时,把内存里的脏数据页批量刷到数据库表文件里。如果用户在刷盘前读数据,会优先从内存中读取。
  4. 崩溃恢复:如果系统在后台刷盘前崩溃了,重启时数据库会读取 已经持久化的 Redo Log,把所有没来得及刷到磁盘的修改重新应用到数据文件里,保证数据不丢失。

完整流程拆解:

  1. 应用发起修改:UPDATE user SET money=100 WHERE id=1;
  2. 内存修改:InnoDB 找到 id=1 所在的数据页,加载到内存,直接修改内存中的值。这一步极快,因为是内存操作。
  3. 写 Redo Log:把这次修改的内容(比如 “把 id=1 的 money 从 200 改成 100”)写入 Redo Log Buffer,然后刷到磁盘上的 Redo Log 文件。这一步是顺序 I/O,很快。(到这里,事务就可以提交了,客户端收到 “执行成功” 的响应)
  4. 后台刷盘(Checkpoint):后台线程会定期把内存中修改过的数据页(脏页)批量写入到数据库的 .ibd 数据文件里。这一步是批量的、异步的,不影响用户操作的响应速度。
  5. 崩溃恢复:如果在第 4 步之前系统崩溃,内存里的脏页会丢失,但重启后,数据库会读取磁盘上的 Redo Log,把所有已提交但未刷盘的修改重新执行一遍,把数据恢复到崩溃前的状态。

【2】:为什么要设计成 “写两次磁盘”?其实是性能和安全的平衡:

  • 如果不写日志,直接改数据:每次修改都要做随机 I/O,速度极慢,高并发下系统会卡死。
  • 如果只写日志,不改数据:日志会无限膨胀,而且查询时要从日志里回放所有修改,查询速度会变得无法忍受。
  • WAL 方案:用一次快速的顺序 I/O(写日志)保证数据安全和事务响应速度,再用后台批量的随机 I/O(刷数据)保证查询性能和磁盘空间。

MySQL中的事务

存储引擎是驱动如何从硬盘中存储和检索数据的软件。虽然MySQL传统上提供了许多支持事务的存储引擎,但InnoDB现在已经成为金标准,是推荐使用的引擎。这里描述的事务原语将基于InnoDB引擎中的事务。

理解AUTOCOMMIT

默认情况下,单个INSERT、UPDATE或DELETE语句会被隐式包装在一个事务中并在执行成功后立即提交,这称为自动提交(AUTOCOMMIT)模式。通过禁用此模式,可以在事务中执行一系列语句,并在结束时执行COMMIT提交事务或ROLLBACK回滚事务。

基于这些内容,就能理解START TRANSACTION(或BEGIN)的工作原理了,就是临时关闭AUTOCOMMIT,把接下来的多条 SQL 打包成一个事务,等到 COMMIT 时一次性提交。不过START TRANSACTION不只是简单关 AUTOCOMMIT,它还会:

  • 生成一个事务视图【1】(Read View)
  • 用于 MVCC【2】 实现可重复读
  • 记录 undo log 信息,方便回滚

在当前连接中,可以使用SET命令设置AUTOCOMMIT变量来启用或禁用自动提交模式。启用可以设置为1或者ON,禁用可以设置为0或者OFF。如果设置了AUTOCOMMIT=0,则当前连接总是会处于某个事务中,直到发出COMMIT或者ROLLBACK,然后MySQL会立即启动一个新的事务。

还有一些命令,当在活动的事务中发出时,会导致MySQL在事务的所有语句执行完毕前提交当前事务。这些通常是进行重大更改的DDL命令【3】,如ALTER TABLE,但LOCKTABLES和其他一些语句也具有同样的效果。

MySQL可以通过执行SET TRANSACTION ISOLATION LEVEL命令来设置隔离级别。新的隔离级别会在下一个事务开始的时候生效。可以在配置文件中设置整个服务器的隔离级别,也可以只改变当前会话的隔离级别:

1
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

隐式锁定和显式锁定

InnoDB使用两阶段锁定协议(two-phase locking protocol)。在事务执行期间,随时都可以获取锁,但锁只有在提交或回滚后才会释放,并且所有的锁会同时释放。前面描述的锁定机制都是隐式的。InnoDB会根据隔离级别自动处理锁。

另外,InnoDB还支持通过特定的语句进行显式锁定(应当尽量避免使用),如:

1
2
SELECT ... FOR SHARE; # 对查询到的行加 共享锁(S 锁)
SELECT ... FOR UPDATE; # 对查询到的行加 排他锁(X 锁)

简单来说,数据库自动加的就是隐式锁定,自己写SQL手动加锁就是显示锁定。

【1】:事务视图相当于数据库在开启事务那一刻,拍的一张 “数据快照”。作用是决定你在这个事务里,能看见哪些数据,看不见哪些数据。

比如,在执行START TRANSACTION时生成 Read View,从这一刻起别的事务已经提交的修改能看见,别的事务还没提交或之后提交的修改就都看不见,这就是可重复读的核心,即在事务里看到的世界,永远停留在 BEGIN 那一刻的样子。


【2】:全称Multi-Version Concurrency Control,即多版本并发控制。它是让读不加锁、写也不加锁,还能保证数据不乱的一套机制。

没有MVCC的时候,读写数据都要加锁,高并发下很慢,而有了MVCC后,每条数据会保留多个历史版本,读事务读旧版本,写事务写新版本,读写互不阻塞。这是靠undo log来保存历史版本,read view决定能看见哪个版本,两者共同实现的。


【3】:全称Data Definition Language,即数据定义语言。简单来说DDL 就是用来定义、修改、删除表结构的命令,不操作表里的数据,只操作 “表长什么样”。

常见的DDL命令有:

  • CREATE:创建(库、表、索引、视图……)
  • ALTER:修改表结构(加字段、删字段、改字段类型)
  • DROP:删除(表、库、索引)
  • TRUNCATE:清空整张表(删数据但保留表结构,比 DELETE 快)
  • RENAME:重命名表
  • CREATE INDEX / DROP INDEX:创建 / 删除索引

多版本并发控制

MySQL的大多数事务型存储引擎使用的都不是简单的行级锁机制,会结合多版本并发控制(MVCC)技术使用。不就是MySQL,包括Oracle、PostgreSQL以及其他一些数据库系统也都是用了MVCC,但各种的实现方式都不太一样,因为MVCC如何工作并没有统一的标准。

MVCC的工作原理是使用数据在某个时间点的快照来实现的。这意味着,无论事务运行多长时间,都可以看到数据的一致视图,也意味着不同的事务可以在同一时间看到同一张表中的不同数据。

下方的序列图解释了InnoDB的行为,以此来展示MVCC的一种实现方式: image-20260331233138105

InnoDB通过为每个事务在启动时分配一个事务ID来实现MVCC。该ID在事务首次读取任何数据时分配。在该事务中修改记录时,将向Undo日志写入一条说明如何恢复该更改的Undo记录,并且事务的回滚指针指向该Undo日志记录。这就是事务如何在需要时执行回滚的方法【1】

当不同的会话读取聚簇主键索引记录时,InnoDB会将该记录的事务ID与该会话的读取视图进行比较。如果当前状态下的记录不应可见(更改它的事务尚未提交),那么Undo日志记录将被跟踪并应用,直到会话达到一个符合可见条件的事务ID。这个过程可以一直循环到完全删除这一行的Undo记录,然后向读取视图发出这一行不存在的信号【2】

事务中的记录可以通过在记录的“info flags”中设置“deleted”位来删除。这在Undo日志中也被作为“删除标记”进行跟踪。

值得注意的是,所有Undo日志写入也都会写入Redo日志,因为Undo日志写入是服务器崩溃恢复过程的一部分,并且是事务性的。这些Redo日志和Undo日志的大小也是高并发事务工作机制中的重要影响因素。

MVCC仅适用于REPEATABLE READ和READ COMMITTED隔离级别。

【1】:InnoDB 不会直接覆盖原数据,而是先把修改操作写到 Undo 日志里,给这条 Undo 日志打上 A 的事务 ID(txn ID A)。原数据行上的回滚指针,会指向这条新的 Undo 日志。这样 A 如果要回滚,顺着指针就能把数据改回去。同时把修改操作写到 Redo 日志,防止数据库突然断电,数据丢了。

【2】:当事务B来读数据InnoDB 给它分配事务 ID(比如 ID=101),生成它自己的「读取视图」。此时如果A已经提交了,B就能看见,否则看不见。既然最新版本不可见,InnoDB 就顺着数据行的「回滚指针」,找到 A 写的 Undo 日志,将数据恢复成上个版本,也就是修改前的样子,再检查这个版本对B是否可见,若还不可见,则继续循环这个操作,直到对B可见为止。

复制

MySQL被设计用于在任何给定时间只在一个节点上接受写操作。这在管理一致性方面具有优势,但在需要将数据写入多台服务器或多个地区时,会导致需要做出取舍。MySQL提供了一种原生方式来将一个节点执行的写操作分发到其他节点,这被称为复制。在MySQL中,源节点为每个副本节点提供一个线程,该线程作为复制客户端登录,当写入发生时会被唤醒,发送新数据。

下图为简单的MySQL服务器拓扑树:

image-20260331233119680

对于在生产环境中运行的任何数据,都应该使用复制并至少有三个以上的副本,理想情况下应该分布在不同的地区(在云托管环境中,称为region)用于灾难恢复计划。

数据文件结构

在8.0版本中,MySQL将表的元数据重新设计为一种数据字典,包含在表的.ibd文件中。这使得表结构上的信息支持事务和原子级数据定义更改。

InnoDB引擎

InnoDB是MySQL的默认事务型存储引擎,也是最重要、使用最广泛的引擎。它是为处理大量短期事务而设计的,这些事务通常是正常提交的,很少会被回滚。InnoDB的性能和自动崩溃恢复特性,使得它在非事务型存储需求中也很流行。

InnoDB是MySQL默认的通用存储引擎。InnoDB使用MVCC来实现高并发性,并实现了所有4个SQL标准隔离级别,默认为REPEATABLE READ隔离级别,并且通过间隙锁(next-key locking)策略来防止在这个隔离级别上的幻读:InnoDB不只锁定在查询中涉及的行,还会对索引结构中的间隙进行锁定,以防止幻行被插入。

JSON文档支持

JSON类型在5.7版本被首次引入InnoDB,它实现了JSON文档的自动验证,并优化了存储以允许快速读取。除了支持新的数据类型,InnoDB还引入了SQL函数来支持在JSON文档上的丰富操作。MySQL 8.0.7的进一步改进增加了在JSON数组上定义多值索引的能力。

数据字典的变化

MySQL 8.0的另一个主要变化是删除了基于文件的表元数据存储,并将其转移到使用InnoDB表存储的数据字典中。这给所有类似修改表结构这样的操作带来了InnoDB的崩溃恢复事务的好处。

原子DDL

MySQL 8.0引入了原子数据定义更改。这意味着数据定义语句现在要么全部成功完成,要么全部失败回滚。这是通过创建DDL特定的Undo日志和Redo日志来实现的,InnoDB便依赖这两种日志来跟踪变更。

本站于2025年3月26日建立
使用 Hugo 构建
主题 StackJimmy 设计