MySQL之优化 InnoDB 的主键

发布时间:2020-05-09 浏览次数:223

前言

作为 Percona 的首席架构师,我的主要职责之一是对客户的数据库进行性能方面的优化,这使得工作复杂且非常有趣。在这篇文章中,我想讨论一个最重要的问题:选择最佳的 InnoDB 主键。


InnoDB 主键有什么特别之处?

InnoDB 被称为索引组织型的存储引擎。主键使用的 B-Tree 来存储数据,即表行。这意味着 InnoDB 必须使用主键。如果表没有主键,InnoDB 会向表中添加一个隐藏的自动递增的 6 字节计数器,并使用该隐藏计数器作为主键。InnoDB 的隐藏主键存在一些问题。您应该始终在表上定义显式主键,并通过主键值访问所有 InnoDB 行。

InnoDB 的二级索引也是一个B-Tree。搜索关键字由索引列组成,存储的值是匹配行的主键。通过二级索引进行搜索通常会导致主键的隐式搜索。


什么是 B-Tree?

一个 B-Tree 是一种针对在块设备上优化操作的数据结构。块设备或磁盘有相当重要的数据访问延迟,尤其是机械硬盘。在随机位置检索单个字节并不比检索更大的数据花费的时间更少。这是 B-Tree 的基本原理,InnoDB 使用的数据页为 16KB。

让我们尝试简化 B-Tree 的描述。B-Tree 是围绕这键来组织的数据结构。键用于搜索 B-Tree 内的数据。B-Tree 通常有多个级别。数据仅存储在最底层,即叶子节点。其他级别的页面(节点)仅包含下一级别的页面的键和指针。

如果要访问键值的数据,则从顶级节点-根节点开始,将其包含的键与搜索值进行比较,并找到要在下一级访问的页面。重复这个过程,直到你达到最后一个级别,即叶子节点。理论上,每个 B-Tree 级别的读取都需要一次磁盘读取操作。在实践中,总是有内存缓存节点,因为它们数量较少且经常访问,因此适合缓存。

MySQL之优化 InnoDB 的主键-爱可生


一个简单的三级 B-Tree 结构


有序的插入示例

让我们考虑以下 sysbench 表:

if (opt_bin_log)  tc_log= &mysql_bin_log;

Data_length 值是 B-Tree 主键的大小。B-Tree 的二级索引,即 k_1 索引,Index_length 是其大小。因为 ID 主键自增,所以 sysbench 表数据是顺序插入的。当按主键顺序插入时,即使 innodb_fill_factor 设为 100,InnoDB 最多使用 15KB 的数据填充空间。这导致在初始插入数据之后,需要拆分页面。页面中还有一些页眉和页脚。如果页面太满且无法添加更多数据,则页面将拆分为两个。同样,如果两个相邻页面的填充率低于 50%,InnoDB 将合并它们。例如,这是以 ID 顺序插入的 sysbench 表:

mysql> select count(*), TABLE_NAME,INDEX_NAME, avg(NUMBER_RECORDS), avg(DATA_SIZE) from information_schema.INNODB_BUFFER_PAGE    -> WHERE TABLE_NAME='`sbtest`.`sbtest1`' group by TABLE_NAME,INDEX_NAME order by count(*) desc;+----------+--------------------+------------+---------------------+----------------+| count(*) | TABLE_NAME         | INDEX_NAME | avg(NUMBER_RECORDS) | avg(DATA_SIZE) |+----------+--------------------+------------+---------------------+----------------+|    13643 | `sbtest`.`sbtest1` | PRIMARY    |             75.0709 |     15035.8929 ||       44 | `sbtest`.`sbtest1` | k_1        |           1150.3864 |     15182.0227 |+----------+--------------------+------------+---------------------+----------------+2 rows in set (0.09 sec)mysql> select PAGE_NUMBER,NUMBER_RECORDS,DATA_SIZE,INDEX_NAME,TABLE_NAME from information_schema.INNODB_BUFFER_PAGE    -> WHERE TABLE_NAME='`sbtest`.`sbtest1`' order by PAGE_NUMBER limit 1;+-------------+----------------+-----------+------------+--------------------+| PAGE_NUMBER | NUMBER_RECORDS | DATA_SIZE | INDEX_NAME | TABLE_NAME         |+-------------+----------------+-----------+------------+--------------------+|           3 |             35 |       455 | PRIMARY    | `sbtest`.`sbtest1` |+-------------+----------------+-----------+------------+--------------------+1 row in set (0.04 sec)mysql> select PAGE_NUMBER,NUMBER_RECORDS,DATA_SIZE,INDEX_NAME,TABLE_NAME from information_schema.INNODB_BUFFER_PAGE    -> WHERE TABLE_NAME='`sbtest`.`sbtest1`' order by NUMBER_RECORDS desc limit 3;+-------------+----------------+-----------+------------+--------------------+| PAGE_NUMBER | NUMBER_RECORDS | DATA_SIZE | INDEX_NAME | TABLE_NAME         |+-------------+----------------+-----------+------------+--------------------+|          39 |           1203 |     15639 | PRIMARY    | `sbtest`.`sbtest1` ||          61 |           1203 |     15639 | PRIMARY    | `sbtest`.`sbtest1` ||          37 |           1203 |     15639 | PRIMARY    | `sbtest`.`sbtest1` |+-------------+----------------+-----------+------------+--------------------+3 rows in set (0.03 sec)

该表不适合缓冲池,但查询为我们提供了很好的解释。 B-Tree 主键的页面平均有 75 条记录,并存储少于 15KB 的数据。sysbench 以随机顺序插入索引 k_1。sysbench 在插入行之后创建索引并且 InnoDB 使用排序文件来创建它。

您可以轻松估算 InnoDB B-Tree 中的级别数。上表需要大约 40K 页(3M / 75)。当主键是四字节整数时,每个节点页面保持大约 1200 个指针。因此叶子上层大约有 35 页,然后在 B-Tree 上的根节点(PAGE_NUMBER = 3)我们总共有三个层级。


一个随机插入的例子

如果你是一个敏锐的观察者,你意识到以主键的随机顺序插入页面通常是不连续的,平均填充系数仅为 65-75% 左右。我修改了 sysbench 以随机的 ID 顺序插入并创建了一个表,也有 3M行。结果表格要大得多:

mysql> show table status like 'sbtest1'G*************************** 1. row ***************************           Name: sbtest1         Engine: InnoDB        Version: 10     Row_format: Dynamic           Rows: 3137367 Avg_row_length: 346    Data_length: 1088405504Max_data_length: 0   Index_length: 47775744      Data_free: 15728640 Auto_increment: NULL    Create_time: 2018-07-19 19:10:36    Update_time: 2018-07-19 19:09:01     Check_time: NULL      Collation: latin1_swedish_ci       Checksum: NULL Create_options:        Comment:1 row in set (0.00 sec)

虽然以 ID 的顺序插入 B-Tree 主键的大小是 644MB,但是以随机顺序插入的大小约为 1GB,多了 60%。显然,我们的页面填充系数较低:

mysql> select count(*), TABLE_NAME,INDEX_NAME, avg(NUMBER_RECORDS), avg(DATA_SIZE) from information_schema.INNODB_BUFFER_PAGE    -> WHERE TABLE_NAME='`sbtestrandom`.`sbtest1`'group by TABLE_NAME,INDEX_NAME order by count(*) desc;+----------+--------------------------+------------+---------------------+----------------+| count(*) | TABLE_NAME               | INDEX_NAME | avg(NUMBER_RECORDS) | avg(DATA_SIZE) |+----------+--------------------------+------------+---------------------+----------------+|     4022 | `sbtestrandom`.`sbtest1` | PRIMARY    |             66.4441 |     10901.5962 ||     2499 | `sbtestrandom`.`sbtest1` | k_1        |           1201.5702 |     15624.4146 |+----------+--------------------------+------------+---------------------+----------------+2 rows in set (0.06 sec)

随机顺序插入时,主键页现在只填充了大约 10KB 的数据(~66%)这是正常和预期的结果。对于某些工作负载情况而言,这很糟糕。


确定工作负载类型

第一步是确定工作负载类型。当您有一个插入密集型工作负载时,很可能顶级查询是在一些大型表上插入的,并且数据库会大量写入磁盘。如果在 MySQL 客户端中重复执行“show processlist;”,则会经常看到这些插入。这是典型的应用程序记录大量数据。有许多数据收集器,他们都等待插入数据。如果等待时间过长,可能会丢失一些数据。如果您在插入时间上有严格的等级协议,而在读取时间上有松弛的等级协议,那么您显然有一个面向插入的工作负载,您应该按主键的顺序插入行。

也可以在大型表上具有不错的插入速率,但这些插入是按批处理排队并执行的。没有人真的在等待这些插入完成,服务器可以轻松跟上插入的数量。对于您的应用程序而言,重要的是大量的读取查询将进入大型表,而不是插入。您已经完成了查询调优,即使您有良好的索引,数据库也会以非常高的速率从磁盘读取数据。

当您查看 MySQL 进程列表时,您会在大表上看到多次相同的选择查询表单。唯一的选择似乎是添加更多内存来降低磁盘读取次数,但是这些表正在快速增长,并且您无法永久地添加内存。

如果您无法确定是否存在插入量大或读取繁重的工作负载,那么您可能只是没有大的工作量。在这种情况下,默认是使用有序插入,而使用 MySQL 实现此目的的最佳方法是通过自动增量整数主键。这是许多 ORM 的默认行为。


读密集型工作负载

我曾看到了很多读密集型工作负载,主要是在线游戏和社交网络应用程序。最重要的是,一些游戏具有社交网络功能,例如:在游戏进行过程中观看朋友的分数。在我们进一步讨论之前,我们首先需要确认读取效率低下。当读取效率低下时,顶部选择查询表单将访问许多不同的 InnoDB 页面,这些页面接近于检查的行数。用 pt-query-digest 工具对 MySQL 慢日志进行分析,详细级别包括 “InnoDB” 时,会暴露这两个数量。这是一个示例输出(我删除了一些行):

Query_log_event("BEGIN");DML产生的events;               Xid_log_event;

该 friends 表的定义是:

CREATE TABLE `friends` (  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,  `user_id` int(10) unsigned NOT NULL,  `friend_user_id` int(10) unsigned NOT NULL,  `created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,  `active` tinyint(4) NOT NULL DEFAULT '1',  PRIMARY KEY (`id`),  UNIQUE KEY `uk_user_id_friend` (`user_id`,`friend_user_id`),  KEY `idx_friend` (`friend_user_id`)) ENGINE=InnoDB AUTO_INCREMENT=144002 DEFAULT CHARSET=latin1

我在测试服务器上构建了这个简单的例子。该表很容易适合内存,因此没有磁盘读取。这里重要的是“page distin”和“Rows Examine”之间的关系。如您所见,该比率接近 1 。这意味着 InnoDB 很少每页访问一行。对于给定的 user_id 值,匹配的行分散在 B-Tree 主键上。我们可以通过查看示例查询的输出来确认这一点:

mysql> select * from friends where user_id = 1234 order by id limit 10;+-------+---------+----------------+---------------------+--------+| id    | user_id | friend_user_id | created             | active |+-------+---------+----------------+---------------------+--------+|   257 |    1234 |             43 | 2018-07-19 20:14:47 |      1 ||  7400 |    1234 |           1503 | 2018-07-19 20:14:49 |      1 || 13361 |    1234 |            814 | 2018-07-19 20:15:46 |      1 || 13793 |    1234 |            668 | 2018-07-19 20:15:47 |      1 || 14486 |    1234 |           1588 | 2018-07-19 20:15:47 |      1 || 30752 |    1234 |           1938 | 2018-07-19 20:16:27 |      1 || 31502 |    1234 |            733 | 2018-07-19 20:16:28 |      1 || 32987 |    1234 |           1907 | 2018-07-19 20:16:29 |      1 || 35867 |    1234 |           1068 | 2018-07-19 20:16:30 |      1 || 41471 |    1234 |            751 | 2018-07-19 20:16:32 |      1 |+-------+---------+----------------+---------------------+--------+10 rows in set (0.00 sec)

行通常由数千个 ID 值分开。虽然行很小,大约 30 个字节,但 InnoDB 页面不包含超过 500行。随着应用程序变得流行,用户越来越多,表大小也越来越接近用户数的平方。一旦表格超过 InnoDB 缓冲池限制,MySQL 就开始从磁盘读取。更糟糕的情况是,没有缓存,我们需要每个 friend 的 IOPS。如果这些要求的速率是平均 300条/秒而言,每个用户有 100 个朋友,则 MySQL 需要每秒访问多达 30000 个页面。显然,这不符合长期规划。

我们需要确定访问表的所有条件。为此,我使用 pt-query-digest 并且我提高了返回的查询表单数量的限制。假设我发现:

  • 93% 访问 userid

  • 5% 访问 friendid

  • 2% 访问 id

上述比例非常普遍。当存在显性访问模式时,我们可以做一些事情。朋友表关系是多对多的。使用 InnoDB,我们应该将这些表定义为:

Query_log_event("BEGIN");DML产生的events;Query_log_event("COMMIT");

现在,行在 B-Tree 主键由 user_id 排序分组,但按随机顺序插入。换句话说,我们减慢了插入速度,使得表中的 select 语句受益。要插入一行,InnoDB 可能需要一个磁盘读取来获取新行所在的页面和一个磁盘写入以将其保存回磁盘。我们使表变得更大,InnoDB 页数不够多,二级索引更大,因为主键更大。我们还添加了二级索引。现在我们 InnoDB 的缓冲池中数据更少了。

我们会因为缓冲池中的数据较少而感到恐慌吗?不,因为现在当 InnoDB 从磁盘读取页面时,它不会只获得一个匹配的行,而是获得数百个匹配的行。IOPS 的数量不再与朋友数量与 select 语句的速率相关联。它现在只是 select 语句传入速率的一个因素。没有足够的内存来缓存所有表的影响大大减少了。只要存储可以执行比 select 语句的速率更多的 IOPS 次数。使用修改后的表,pt-query-digest 输出的相关行:

# Attribute    pct   total     min     max     avg     95%  stddev  median# ============ === ======= ======= ======= ======= ======= ======= =======# Rows examine 100   1.23k      16      34   23.72   30.19    4.19   22.53# pages distin 100     111       2       5    2.09    1.96    0.44    1.96

使用新的主键,而不是读 30k 的 IOPS,MySQL 只需要执行大约读 588 次的 IOPS(~300 * 1.96)。这是一个更容易处理的工作量。插入的开销更大,但如果它们的速率为100次/秒,则在最坏的情况下它意味着读 100次 的 IOPS 和写入 100次的 IOPS。

当存在明确的访问模式时,上述策略很有效。最重要的是,这里有一些其他例子,其中通常有显着的访问模式:

  • 游戏排行榜(按用户)

  • 用户偏好(按用户)

  • 消息应用程序(来自或来自)

  • 用户对象存储(按用户)

  • 喜欢物品(按项目)

  • 项目评论(按项目)

当您没有显性访问模式时,您可以做些什么?一种选择是使用覆盖指数。覆盖索引需要涵盖所有必需的列。列的顺序也很重要,因为第一个必须是分组值。另一种选择是使用分区在数据集中创建易于缓存的热点。

我们在本文中看到了用于解决读密集型工作负载的常用策略。此策略不能始终有效 - 您必须通过通用模式访问数据。但是当它工作时,你选择了好的 InnoDB 主键,你就成了此刻的英雄!


- innodb_flush_log_at_trx_commit

0 - 每N秒将Redo Log Buffer的记录写入Redo Log文件,并且将文件刷入硬件存储1次。N由

innodb_flush_log_at_timeout 控制。

1 - 每个事务提交时,将记录从Redo Log Buffer写入Redo Log文件,并且将文件刷入硬件存储。
2 - 每个事务提交时,仅将记录从Redo Log Buffer写入Redo Log文件。Redo Log何时刷入硬件存储由操作系统和innodb_flush_log_at_timeout 决定。这个选项可以保证在MySQL宕机,而操作系统正常工作时,数据的完整性。


2 - MySQL的Two Phase Commit(2PC)
在开启Binlog后,MySQL内部会自动将普通事务当做一个XA事务来处理:

- 自动为每个事务分配一个唯一的ID

- COMMIT会被自动的分成Prepare和Commit两个阶段。

- Binlog会被当做事务协调者(Transaction Coordinator),Binlog Event会被当做协调者日志。

想了解2PC,可以参考文档:【https://en.wikipedia.org/wiki/Two-phase_commit_protocol。】


- 分布式事务ID(XID)
使用2PC时,MySQL会自动的为每一个事务分配一个ID,叫XID。XID是唯一的,每个事务的XID都不相同。XID会分别被Binlog和InnoDB记入日志中,供恢复时使用。MySQ内部的XID由三部分组成:
- 前缀部分
前缀部分是字符串"MySQLXid"
- Server ID部分
当前MySQL的server_id

query_id部分
为了保证XID的的唯一性,数字部分使用了query_id。MySQL内部会自动的为每一个语句分配一个query_id,全局唯一。
参考代码:sql/xa。hstruct xid_t结构。
- 事务的协调者Binlog
Binlog在2PC中充当了事务的协调者(Transaction Coordinator)。由Binlog来通知InnoDB引擎来执行prepare,commit或者rollback的步骤。事务提交的整个过程如下:
1. 协调者准备阶段(Prepare Phase)
告诉引擎做Prepare,InnoDB更改事务状态,并将Redo Log刷入磁盘。
2. 协调者提交阶段(Commit Phase)
2.1 记录协调者日志,即Binlog日志。
2.2 告诉引擎做commit。

注意:记录Binlog是在InnoDB引擎Prepare(即Redo Log写入磁盘)之后,这点至关重要。


在MySQ的代码中将协调者叫做tc_log。在MySQL启动时,tc_log将被初始化为mysql_bin_log对象。参考sql/binlog.cc中的init_server_components()

MYSQL_BIN_LOG::recovery()MYSQL_BIN_LOG::new_file_impl()MYSQL_BIN_LOG::inc_prep_xids()MYSQL_BIN_LOG::dec_prep_xids()

而在事务提交时,会依次执行:

tc_log->prepare();

tc_log->commit();

参考代码:sql/binlog.cc中的ha_commit_trans()。当mysql_bin_logtc_log时,prepare和commit的代码在sql/binlog.cc中:

MYSQL_BIN_LOG::prepare();

MYSQL_BIN_LOG::commit();


-协调者日志Xid_log_event

作为协调者,Binlog需要将事务的XID记入日志,供恢复时使用。Xid_log_event有以下几个特点:
- 仅记录query_id

因为前缀部分不变,server_id已经记录在Event Header中,Xid_log_event中只记录query_id部分。

- 标志事务的结束 
在Binlog中相当于一个事务的COMMIT语句。
一个事务在Binlog中看起来时这样的:

Query_log_event("BEGIN");DML产生的events;               Xid_log_event;

- DDL没有BEGIN,也没有Xid_log_event 

- 仅InnoDB的DML会产生Xid_log_event

因为MyISAM不支持2PC所以不能用Xid_log_event ,但会有COMMIT Event。

Query_log_event("BEGIN");DML产生的events;Query_log_event("COMMIT");


问题:Query_log_event("COMMIT")和Xid_log_event 有不同的影响吗?

Xid_log_event 中的Xid可以帮助master实现CrashSafe。
- Slave的CrashSafe不依赖Xid_log_event

事务在Slave上重做时,会重新产生XID。所以Slave服务器的CrashSafe并不依赖于Xid_log_event Xid_log_event Query_log_event("COMMIT"),只是作为事务的结尾,告诉Slave Applier去提交这个事务。因此二者在Slave上的影响是一样的。
3 - 恢复(Recovery)
这个机制是如何保证MySQL的CrashSafe的呢,我们来分析一下。这里我们假设用户设置了以下参数来保证可靠性:


- 恢复前事务的状态
在恢复开始前事务有以下几种状态:
- InnoDB中已经提交
根据前面2PC的过程,可知Binlog中也一定记录了该事务的的Events。所以这种事务是一致的不需要处理。
- InnoDB中是prepared状态,Binlog中有该事务的Events。
需要通知InnoDB提交这些事务。
- InnoDB中是prepared状态,Binlog中没有该事务的Events。
因为Binlog还没记录,需要通知InnoDB回滚这些事务。
- Before InnoDB Prepare
事务可能还没执行完,因此InnoDB中的状态还没有prepare。根据2PC的过程,Binlog中也没有该事务的events。 需要通知InnoDB回滚这些事务。


- 恢复过程
从上面的事务状态可以看出:恢复时事务要提交还是回滚,是由Binlog来决定的。
- 事务的Xid_log_event 存在,就要提交。
- 事务的Xid_log_event 不存在,就要回滚。

恢复的过程非常简单:
- 从Binlog中读出所有的Xid_log_event

- 告诉InnoDB提交这些XID的事务
- InnoDB回滚其它的事务
疑问1:如果事务的Binlog Event只记录了一部分怎么办?
只有最后一个事务的Event会发生这样的情况。在恢复时,binlog会自动的将这个不完整的事务Events从Binlog文件中给清除掉。


疑问2:随着长时间的运行,Binlog中会积累了很多Xid_log_event ,读取所有的Xid_log_event 会不会效率很低?

当然很低,所以Binlog中有一个机制来保证恢复时只用读取最后一个Binlog文件中的Xid_log_event 。这种机制很像一个简单的Xid_log_event 的checkpoint机制。


- Xid_log_event Checkpoint

这个机制和binlog的文件切换有关,在切换到一个新的Binlog文件前:
- 要等待当前Binlog文件中的所有事务都已经在InnoDB中提交了。
- 告诉InnoDB刷Redo Log到硬件存储。
通过这个机制可以保证在做恢复时,除了最后一个Binlog文件中的事务,其他文件中的事务在InnoDB中一定是已经提交的状态。


参考代码:

sql/binlog.cc中:

MYSQL_BIN_LOG::recovery()MYSQL_BIN_LOG::new_file_impl()MYSQL_BIN_LOG::inc_prep_xids()MYSQL_BIN_LOG::dec_prep_xids()


4 - CrashSafe的写盘次数
前面说道要想保证CrashSafe就要设置下面两个参数为1:
sync_binlog=1

innodb_flush_log_at_trx_commit=1下面我们来看看这两个参数的作用。
- sync_binlog
sync_binlog是控制Binlog写盘的,1表示每次都写。由于Binlog使用了组提交(Group Commit)的机制,它代表一组事务提交时必须要将Binlog文件写入硬件存储1次。
- innodb_flush_log_at_trx_commit的写盘次数
这个变量是用来控制InnoDB commit时写盘的方法的。现在commit被分成了两个阶段,到底在哪个阶段写盘,还是两个阶段都要写盘呢?
- Prepare阶段时需要写盘
2PC要求在Prepare时就要将数据持久化,只有这样,恢复时才能提交已经记录了Xid_log_event 的事务。
- Commit阶段时不需要写盘
如果Commit阶段不写盘,会造成什么结果呢?已经Cmmit了的事务,在恢复时的状态可能是Prepared。由于恢复时,Prepared的事务可以通过Xid_log_event 来提交事务,所以在恢复后事务的状态就是正确的。因此在Commit阶段不需要写盘。


总的来说保证MySQL服务的CrashSafe需要写两次盘。在2PC的过程中,InnoDB只在prepare阶段时,写一次盘。Binlog在commit阶段,会设置一个参数告诉InnoDB不要写盘。这个参数是thd->durability_property= HA_IGNORE_DURABILITY;代码在sql/binlog.ccMYSQL_BIN_LOG:ordered_commit()中。


- Prepare阶段写盘优化

我们知道Binlog使用了Group Commit机制来减少IO,提高性能。Prepare有没有可能做Group Commit呢?只要我们能保证任何事务的Redo Log是在它的Binlog Event写入Binlog文件前,被刷入了持久存储就可以。优化后的做法是:
1. 协调者准备阶段(Prepare Phase)
设置thd->durability_property告诉InnoDB不写盘。 告诉引擎做Prepare,InnoDB更改事务状态。
2. 协调者提交阶段(Commit Phase)
2.1.1 获取一组事务。
2.1.2 通知InnoDB将Redo Log写入硬件存储。
2.1.3 将这组事务的Binlog Event写入Binlog文件。
2.2 告诉引擎做commit。

这个结合了Binlog Group Commit机制的改进对性能的提升还是很显著的。而且这个改进是中国的社区用户阿里云的翟卫祥同学提出并提供的代码补丁。详情可参考MySQL的bug页面:【http://bugs.mysql.com/bug.php?id=73202】。

参考代码:sql/binlog.cc中的MYSQL_BIN_LOG:process_flush_stage_queue()


5 - 总结

MySQL通过两阶段提交的方式来保证CrashSafe。CrashSafe需要Server层、Binlog和InnoDB的协同工作才能完成。由于DDL和MyISAM不支持事务性,因此没办法保证CrashSafe。

注意:本文的代码都是指MySQL-5.7中的代码,其他版本可能会有不一致。

上一篇: MySQL的delete大表slave回放巨慢的问题分析

下一篇: MySQL的CrashSafe和Binlog的关系

产品试用 产品试用
400-820-6580 免费电话