PolarDB DDL MDL
转载数据库内核那些事|深度解析PolarDB DDL锁的优化和演进 - 知乎 (zhihu.com)
概述
Request lock问题
MySQL 拿锁 即乐观等待的方式来拿锁MDL-X。问题:会导致DDL后续DML的阻塞。
第三方插件 pt-osc / gh-ost 采用copying method,空表然后select + insert,最后rename操作切换。问题:1. 饥饿。2.无脑copying,有一些instance DDL 秒级可完成。
Holding Lock问题
MySQL DDL 分为三类:
- Instant DDL
- Inplace DDL
- Copy DDL:执行期间全程锁表,只能读不能写
Online DDL通常是instant 和大部分 inplace,特点是:执行DDL期间绝大多数时刻不锁表, 只有修改元数据的时候会锁表。
PolarDB & MySQL 架构
基于Binlog的MySQL主备集群
-
Binlog简介
Mysql
中有一个binlog
二进制日志,这个日志会记录下主服务器
所有修改了的SQL
语句,从服务器
把主服务器上的binlog
二进制日志,在指定的位置开始复制主服务器
所有修改的语句,在从服务器
上执行一遍。简而言之就是,
主服务器
会把create、update、delete
语句都记录到一个二进制文件中(binlog),从服务器
读取这个文件,执行一遍文件中记录的create、update、delete
语句。从而实现主从数据同步。
Shared-Storage PolarDB MySQL
以一写多读为例,写节点在做DDL操作时,多个只读节点都会看到DDL过程中的实时数据。
目标
- 保证业务能力,避免雪崩
- 避免饥饿
- 针对共享存储的特点,需要满足多节点在数据变更、表结构变更和文件操作这三者的一致性要求
- 逐渐实现DDL DML的MVCC能力,
Non-Blocking DDL
避免长事务 + DDL导致 QPS为0的解决方案:DDL拿锁失败,进入短暂sleep,以允许DML进行,接着重新请求拿锁。
Preemptive DDL
背景是分布式场景下,只读节点存在大查询、长事务导致DDL 拿不到MDL锁,需要手动kill拿到MDL锁的事务。或者开启Preemptive DDL,当只读节点通过物理复制,解析到当前表上有DDL操作时,只读节点会尝试获取表的MDL锁。如果此时表上存在大查询或长事务时,开启Preemptive DDL后,如果只读节点在预期时间内无法获得MDL锁,便会尝试kill掉占有MDL锁的线程,从而保证MDL锁同步的成功,解决DDL的饥饿问题。
DDL & DML的MVCC能力
实现DDL与DML更细粒度的并发控制,类似于InnoDB MVCC能力,DDL涉及到:文件操作/表数据变更/元信息变更/表缓存处理等一系列流程。
目前第一阶段,满足高频DDL和DML的MVCC能力,例如Instant Add Column。
- Session A,建表,插入更新数据不提交
- Session B,体现DDL不会被未提交的事务所堵塞,发现MDL 被t1拿到,但是仍然可以对表进行DDL,加一列c。
- 跨DDL的事务可以选择访问表时使用的隔离级别,可以选择rr 可重复读,读不到新加的列,选择 rc可以读到已经提交的DDL新添加的列C。
全链路优化的分布式MDL锁
目前云原生数据库基本都是存算分离+共享存储的架构,提供一写多读。云原生数据库依赖redo log (物理复制)来完成不同节点之间的数据同步,DDL做出的修改也需要进行同步(元数据/表数据/文件变更),同时又依赖分布式MDL锁提供实时和一致性的保证,MDL锁和物理复制(redo log耦合)会产生一系列问题。
异步元数据同步
- **异步MDL锁复制:**将分布式MDL锁与物理复制相互解耦,实现了即使在等待MDL锁时,只读节点仍能继续解析并应用物理日志,保证了物理复制的实时性;
- **并行MDL锁:**为了优化高频DDL场景下分布式MDL锁的性能,我们采用一组线程池来并发响应MDL锁的需求。即使某个MDL锁被堵塞,也不会影响其它线程去获取MDL锁,并且这部分线程池会随着DDL的情况动态调整,保证了MDL锁同步的高并发。
DDL物理复制优化
主节点加快DDL写日志速度
只读节点加快物理复制速度