基于COMMIT_ORDER的并行复制只有在有压力的情况下才可能会形成一组,压力不大的情况下在从库的并行度并不会高。但是基于WRITESET的并行复制目标就是在ORDER_COMMIT的基础上再尽可能的降低last commit,这样在从库获得更好的并行度(即便在主库串行执行的事务在从库也能并行应用)。它使用的方式就是通过扫描Writeset中的每一个元素(行数据的hash值)在一个叫做Writeset的历史MAP(行数据的hash值和seq number的一个MAP)中进行比对,寻找是否有冲突的行,然后做相应的处理,后面我们会详细描述这种行为。如果要使用这种方式我们需要在主库设置如下两个参数:
transaction_write_set_extraction=XXHASH64
binlog_transaction_dependency_tracking=WRITESET
它们是在5.7.22才引入的。
一、奇怪的last commit
我们先来看一个截图,仔细观察其中的last commit:
image.png
我们可以看到其中的last commit看起来是乱序的,这种情况在基于COMMIT_ORDER 的并行复制方式下是不可能出现的。实际上它就是我们前面说的基于WRITESET的并行复制再尽可能降低的last commit的结果。这种情况会在MTS从库获得更好的并行回放效果,第19节将会详细解释并行判定的标准。
二、Writeset是什么
实际上Writeset是一个集合,使用的是C++ STL中的set容器,在类Rpl_transaction_write_set_ctx中包含了如下定义:
std::set write_set_unique;
集合中的每一个元素都是hash值,这个hash值和我们的transaction_write_set_extraction参数指定的算法有关,其来源就是行数据的主键和唯一键。每行数据包含了两种格式:
字段值为二进制格式
字段值为字符串格式
每行数据的具体格式为:
主键/唯一键名称
分隔符
库名
分隔符
库名长度
表名
分隔符
表名长度
键字段1
分隔符
长度
键字段2
分隔符
长度
其他字段...
在Innodb层修改一行数据之后会将这上面的格式的数据进行hash后写入到Writeset中。可以参考函数add_pke,后面我也会以伪代码的方式给出部分流程。
但是需要注意一个事务的所有的行数据的hash值都要写入到一个Writeset。如果修改的行比较多那么可能需要更多内存来存储这些hash值。虽然8字节比较小,但是如果一个事务修改的行很多,那么还是需要消耗较多的内存资源的。
为了更直观的观察到这种数据格式,可以使用debug的方式获取。下面我们来看一下。
三、Writeset的生成
我们使用如下表:
mysql> use test
Database changed
mysql> show create table jj10 \G
*************************** 1. row ***************************
Table: jj10
Create Table: CREATE TABLE `jj10` (
`id1` int(11) DEFAULT NULL,
`id2` int(11) DEFAULT NULL,
`id3` int(11) NOT NULL,
PRIMARY KEY (`id3`),
UNIQUE KEY `id1` (`id1`),
KEY `id2` (`id2`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
我们写入一行数据:
insert into jj10 values(36,36,36);
这一行数据一共会生成4个元素分别为:
注意:这里显示的?是分隔符
1. 主键二进制格式
(gdb) p pke
$1 = "PRIMARY?test?4jj10?4\200\000\000$?4"
**注意:\200\000\000$ :为3个八进制字节和ASCII字符 $,
其