在聊 MySQL 的并行原理之前,我们要了解什么会有这个概念,以及这个要解决什么问题。这就不得不提到主从延迟这个概念了。
什么是数据库的主从延迟,如何解决?
数据库的主从延迟指的是主服务器(Master)和从服务器(Slave)之间数据同步的时间差或延迟。导致主从延迟的常见原因包括:
- 网络延迟:主节点和从节点之间的网络延迟导致数据复制延迟。
- 从节点性能问题:从服务器性能不足,如 CPU、内存、磁盘资源不足,无法及时处理复制事件,导致延迟增加。
- 复制线程不足:从节点的复制线程不足或配置不合理,无法有效地处理接收到的复制事件,造成数据回放速度较慢,进而导致主从延迟。
解决主从延迟可以考虑以下几个方面:
- 优化网络:确保主从节点之间的网络连接稳定,尽量在同一城市或同一数据中心部署,以减小网络延迟。
- 提升从服务器性能:增加从服务器的硬件资源,例如增加 CPU 核数、内存容量和改善磁盘性能,以提升从服务器处理复制事件的能力。
- 并行复制:利用 MySQL 提供的并行复制能力,同时处理多个复制事件,提高复制效率,从而降低主从延迟。
这些措施可以有助于减少主从延迟,提升数据库复制的效率和稳定性。
上面提到了并行复制这个概念,接下来我们就简单聊聊并行复制。
在 MySQL 的主从复制中,我们已经介绍过其基本原理。在复制过程中,主库的 binlog 会不断地同步到从库,而从库则通过一个 SQL 线程不断地拉取并重放这些 SQL 语句。然而,当日志内容过多时,单个线程的执行会产生延迟,导致主从延迟。
为了解决这一问题,MySQL 提供了并行复制的方案。在多个版本中,MySQL 相继推出了多种并行复制的方案:
- MySQL 5.6 引入了基于库级别的并行复制。
- MySQL 5.7 推出了基于组提交的并行复制。
- MySQL 8.0 推出了基于 WRITESET 的并行复制。
库级别并行复制
在 MySQL 5.6 中,并行复制是基于 Schema(即基于库)的,可以配置多个库并行进行复制。每个库都可以有自己的复制线程,并行处理来自不同库的写入,从而提升并行复制的性能和效率。
然而,实际上大多数业务都是单库的,这使得这一方案在推出后并未获得广大开发者和 DBA 的认可,认为其实用性不足。
组提交的的并行复制
由于 MySQL 5.6 的并行复制饱受诟病,MySQL 5.7 推出了基于组提交的并行复制,这才是真正意义上的并行复制,即著名的 MTS(Enhanced Multi-Threaded Slave)。可参考官方文档:
https://dev.mysql.com/blog-archive/multi-threaded-replication-performance-in-mysql-5-7/
这里先简单了解下组提交,然后继续往下看。
在介绍组提交时我们提到,一个组中的多个事务在处于 Prepare 阶段之后,才会被优化成组提交。这意味着,如果多个事务能够在同一个组内提交,这些事务在锁上一定是没有冲突的。
binlog_transaction_dependency_tracking = WRITESET # COMMIT_ORDER
transaction_write_set_extraction = XXHASH64
换句话说,这几个事务修改的记录一定不是同一行,因此它们之间才能互不影响地同时进入 Prepare 阶段,并进行组提交。
那么,没有冲突的多条 SQL,是不是就可以在主备同步过程中,在备库上并行执行回放呢?
答案是肯定的。因为一个组中的多条 SQL 之间互不影响,无论先执行哪一条,结果都是相同的。
因此,Slave 可以使用多个 SQL 线程来并行执行一个组提交中的多条 SQL,从而提高效率,减少主从延迟。
基于 WRITESET 的并行复制
前面的组提交大大提升了主从复制的效率,但它有一个特点,即依赖于主库的并行度。如果主库的并发度较高,才可以进行组提交,从而利用组提交的并行复制优化。
如果主库的 SQL 执行并不频繁,时间间隔可能会超过组提交的参数阈值,就不会进行组提交,这样在复制时就无法使用并行复制。
为了解决这个问题,MySQL 8.0 引入了基于 WriteSet 的并行复制。在这种情况下,即使主库是串行提交的事务,只要这些事务之间互不冲突,备库就可以并行回放,从而提升复制效率。
开启 WRITESET:
binlog_transaction_dependency_tracking = WRITESET # COMMIT_ORDER
transaction_write_set_extraction = XXHASH64
实际上,WriteSet 是一个集合,使用的是 C++ STL 中的 set 容器。
std::set<uint64> write_set_unique;
集合中的每一个元素都是哈希值,这个哈希值与 transaction_write_set_extraction 参数指定的算法有关(可选值为 OFF、MURMUR32、XXHASH64,默认值为 XXHASH64),其来源是行数据的主键和唯一键。
WriteSet 通过检测两个事务是否更新了相同的记录来判断事务能否并行回放,因此需要在运行时保存已提交的事务信息以记录历史事务更新了哪些行。在进行更新时,需要进行冲突检测,将新更新的记录计算出的哈希值与 WriteSet 进行比较,如果不存在冲突,则认为是不冲突的,这样就可以共用同一个 last_committed。
last_committed 指的是该事务提交时,上一个事务提交的编号。
就这样,能够确保同一个 write_set 中的变更都是不冲突的,因此可以通过多个线程并行地回放 SQL。