数据库基础体系 · 第 58/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。

MariaDB Server:与 MySQL 的差异、存储引擎、复制和迁移边界

MariaDB Server 与 MySQL 都是关系型数据库服务器,使用 SQL 作为主要访问语言,也都采用“服务器层 + 存储引擎”的架构,并通过二进制日志支持复制。

但“MariaDB 是 MySQL 的替代品”只能作为粗略描述,不能作为迁移或复制的技术结论。MariaDB 源自 MySQL,后来在版本、优化器、存储引擎、数据类型、系统表、复制协议和管理工具等方面逐渐分叉。因此:

  • SQL 的常见子集通常可以迁移;
  • 表结构和数据通常可以通过逻辑导出迁移;
  • 二进制日志通常不能被另一产品无条件、无损地直接消费;
  • 物理数据目录不能直接互换;
  • 复制、故障切换和 GTID 需要按产品和版本单独设计。

本文围绕四个问题展开:

  1. MariaDB 与 MySQL 的兼容性边界在哪里;
  2. 存储引擎如何决定事务、锁和崩溃恢复语义;
  3. MariaDB 复制与 MySQL 复制的数据流、GTID 和故障边界是什么;
  4. 什么时候应当使用逻辑迁移、复制迁移或物理迁移。

一、先明确比较对象:服务器、引擎和协议不是一回事

一条 SQL 从客户端到磁盘,至少经过以下层次:

客户端
  │ SQL / 协议
  ▼
MariaDB 或 MySQL 服务器层
  ├─ 连接认证
  ├─ SQL 解析
  ├─ 优化器
  ├─ 执行器
  ├─ 事务协调
  └─ 二进制日志
        │
        ▼
存储引擎
  ├─ InnoDB
  ├─ Aria
  ├─ MyISAM
  ├─ MyRocks
  └─ 其他插件
        │
        ▼
数据文件、日志文件、操作系统

这几层的兼容性并不相同。

1. SQL 兼容性

SQL 兼容性指同一条 SQL 在两个服务器上能否:

  • 成功解析;
  • 得到相同的类型推导;
  • 产生相同的结果;
  • 具有相同的锁和事务行为;
  • 在异常条件下产生相同的错误。

仅仅“能执行”并不代表行为完全相同。例如:

SELECT CAST('2024-01-01' AS DATE);

通常没有问题;但以下因素可能改变结果:

  • sql_mode
  • 字符集和排序规则;
  • 隐式类型转换;
  • 时间类型精度;
  • GROUP BY 非聚合列的处理;
  • 保留字;
  • JSON、空间类型和生成列的实现;
  • 默认值、检查约束和自动时间列行为。

因此,SQL 迁移应验证结果集、错误码和事务行为,而不是只验证脚本是否跑完。

2. 客户端协议兼容性

MariaDB 和 MySQL 都提供常见的客户端连接方式,许多驱动可以连接两者。但协议能连接不代表数据库特性兼容。

应用可能依赖:

  • 特定错误码;
  • information_schema 字段;
  • 预处理语句行为;
  • INSERT ... RETURNING 等产品特性;
  • 用户认证插件;
  • SHOW 命令输出格式;
  • 元数据中的列类型名称。

所以驱动层的“可连接”只能证明连接建立成功,不能证明应用语义没有变化。

3. 物理文件兼容性

物理迁移是直接搬运数据目录、表空间或引擎文件。例如复制某个 InnoDB 表空间文件,或者直接替换整个 datadir

物理文件依赖:

  • 服务器版本;
  • 存储引擎版本;
  • 页格式;
  • 数据字典;
  • redo/undo 日志格式;
  • 系统表结构;
  • 文件路径和权限;
  • 表空间配置。

MariaDB 和 MySQL 即使都使用名为 InnoDB 的引擎,也不应假定它们的内部文件格式和数据字典完全兼容。尤其不能把“同名引擎”理解为“可以互换数据目录”。

结论:跨 MariaDB 和 MySQL 的默认迁移边界是逻辑迁移,而不是物理搬运。


二、MariaDB 与 MySQL 的主要分叉点

1. 共同来源不等于持续同步

MariaDB 最初从 MySQL 代码分支发展而来。早期版本之间存在较高兼容性,但随着两边分别加入新功能,差异持续扩大。

差异主要分布在以下区域:

区域 典型差异
SQL 语法 某些 DDL、查询扩展、返回语法和管理语句不同
数据类型 JSON、时间类型、空间类型、自动生成列等实现不同
系统表 用户、权限、统计信息和数据字典结构不同
存储引擎 MariaDB 维护和集成了一组不同于 MySQL 的引擎
优化器 代价模型、连接算法、统计信息和优化开关不同
复制 GTID 表示、事件能力、并行复制和过滤配置不同
认证 默认认证方式和插件生态可能不同
管理工具 mysqldumpmariadb-dump、升级工具和诊断输出有所区别

这意味着版本号必须同时写全产品名。例如:

MariaDB Server 11.x
MySQL 8.0.x

“MariaDB 版本”和“MySQL 版本”不能仅用相同的主版本数字比较兼容性。即使两个版本都支持某个语法,也可能在执行计划、默认字符集或复制事件上存在差别。

2. JSON 是一个容易误判的例子

在 MySQL 中,JSON 是专门的数据类型,服务器会以其自身的二进制格式存储并提供 JSON 相关操作。

在 MariaDB 中,JSON 的语义和内部实现不同,通常可理解为基于字符串类型的 JSON 约束与函数体系,而不是 MySQL 的同一内部 JSON 类型。

因此下面的表定义不能简单看成等价:

CREATE TABLE t_mysql (
    id BIGINT PRIMARY KEY,
    doc JSON
);

迁移时至少要验证:

  • SHOW CREATE TABLE 的实际结果;
  • INFORMATION_SCHEMA.COLUMNS 中的类型;
  • JSON 路径表达式;
  • 索引定义;
  • 非法 JSON 的处理方式;
  • 导出文件中的转义和字符集。

一个常见失败表现是:表和数据都导入成功,但原来依赖 JSON 专用索引或类型校验的查询性能、错误行为发生变化。

3. 字符集和排序规则不能靠名称猜测

utf8mb4 只是字符集名称,排序规则决定比较和排序语义。例如:

SELECT
    CASE WHEN 'a' = 'A' THEN 'equal' ELSE 'different' END;

结果受排序规则影响。迁移前应明确:

SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';

SELECT
    TABLE_SCHEMA,
    TABLE_NAME,
    COLUMN_NAME,
    CHARACTER_SET_NAME,
    COLLATION_NAME
FROM information_schema.COLUMNS
WHERE CHARACTER_SET_NAME IS NOT NULL;

需要验证的不是只有数据库默认值,还包括:

  • 表级默认排序规则;
  • 字符列的显式排序规则;
  • 连接字符集;
  • 导出工具的连接参数;
  • 索引长度;
  • 大小写敏感性;
  • ORDER BY 和唯一索引的比较规则。

三、存储引擎:事务语义不是服务器层自动提供的

1. 存储引擎负责什么

存储引擎是服务器访问表数据的实现模块。它通常负责:

  • 数据页和索引的组织;
  • 行或表级锁;
  • 事务;
  • redo、undo 或其他恢复日志;
  • 崩溃恢复;
  • 外键;
  • 全文或空间索引等扩展能力。

服务器层负责解析和执行 SQL,但并不自动把所有表变成事务表。表的引擎决定了很多底层语义。

查看当前服务器可用引擎:

SHOW ENGINES;

典型输出包含:

Engine   Support   Transactions   XA   Savepoints
InnoDB   DEFAULT   YES             YES  YES
Aria     YES       ...             ...  ...
MyISAM   YES       NO              NO   NO

具体列值和可用引擎取决于版本、构建方式和插件状态。不要把某个版本上的输出硬编码到部署脚本中。

查看单个表的引擎:

SELECT
    TABLE_SCHEMA,
    TABLE_NAME,
    ENGINE,
    TABLE_ROWS
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'app';

2. InnoDB:跨产品迁移时的默认事务基线

InnoDB 通常是 MariaDB 和 MySQL 中最重要的事务型引擎。它通常提供:

  • 事务;
  • 行级锁;
  • MVCC;
  • 崩溃恢复;
  • 主键聚簇组织;
  • 外键支持;
  • redo/undo 机制。

MVCC 是多版本并发控制。一个简化模型是:更新记录时不立即破坏所有旧版本,而是保留可供已有快照读取的历史版本。

例如:

CREATE TABLE account (
    id BIGINT PRIMARY KEY,
    balance DECIMAL(18,2) NOT NULL
) ENGINE=InnoDB;

INSERT INTO account VALUES (1, 100.00);

会话 A:

START TRANSACTION;
SELECT balance FROM account WHERE id = 1;

得到:

100.00

会话 B:

START TRANSACTION;
UPDATE account
SET balance = balance - 10
WHERE id = 1;
COMMIT;

在默认隔离级别下,会话 A 是否立即看到 90.00,取决于它再次读取时的快照规则;如果使用锁定读:

SELECT balance
FROM account
WHERE id = 1
FOR UPDATE;

则会话 A 需要等待会话 B 提交或回滚。这里体现了两个不同概念:

  • 一致性读:读取符合事务快照的数据;
  • 锁定读:要求当前版本并获取锁。

不能因为两个服务器都叫 InnoDB,就假定执行计划、默认隔离级别配置、锁等待表现和恢复工具完全相同。 但如果迁移以 InnoDB 表为主,通常比混合引擎迁移更容易保持事务语义。

3. MyISAM:可以复制数据,不提供事务回滚

MyISAM 是非事务型引擎。以下示例不会提供 InnoDB 意义上的原子回滚:

CREATE TABLE audit_myisam (
    id BIGINT PRIMARY KEY,
    message VARCHAR(200)
) ENGINE=MyISAM;

START TRANSACTION;

INSERT INTO audit_myisam VALUES (1, 'first');

ROLLBACK;

ROLLBACK 不会撤销已经写入 MyISAM 表的数据,因为该表不参加事务。

这会导致一个重要的反例:

CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    amount DECIMAL(18,2) NOT NULL
) ENGINE=InnoDB;

CREATE TABLE order_audit (
    order_id BIGINT,
    message VARCHAR(200)
) ENGINE=MyISAM;

START TRANSACTION;

INSERT INTO orders VALUES (1, 100.00);
INSERT INTO order_audit VALUES (1, 'created');

ROLLBACK;

回滚后可能出现:

orders      中没有 id=1
order_audit 中仍有 order_id=1

原因不是 SQL 事务失效,而是一个事务同时操作了不同事务能力的引擎。服务器无法把不支持事务的引擎变成原子参与者。

4. Aria、MyRocks 和其他引擎的边界

MariaDB 提供或集成了多种存储引擎,例如:

  • Aria:MariaDB 生态中的通用引擎,强调崩溃安全和较好的读取能力,常用于内部临时表或特定场景;
  • MyISAM:非事务型传统引擎;
  • MyRocks:基于 RocksDB 的写优化型引擎,适用于特定工作负载;
  • Spider:用于分片或远程表访问的引擎;
  • ColumnStore:面向分析场景的独立架构和部署边界,不应简单当作 InnoDB 的替换品。

这些引擎的事务、锁、索引、外键、复制和备份能力并不相同。迁移不能只迁移 CREATE TABLE 文本,还应建立能力矩阵:

SELECT
    TABLE_SCHEMA,
    TABLE_NAME,
    ENGINE
FROM information_schema.TABLES
WHERE TABLE_SCHEMA NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
ORDER BY ENGINE, TABLE_SCHEMA, TABLE_NAME;

对每种引擎分别确认:

  1. 是否支持事务;
  2. 是否支持外键;
  3. 是否支持所需索引;
  4. 是否被当前复制模式正确记录;
  5. 是否被备份工具支持;
  6. 是否存在目标产品中的等价引擎。

“目标服务器能识别这个引擎”不等于“目标服务器拥有相同语义”。


四、事务、二进制日志和复制之间的关系

1. 事务提交不是只写一份文件

以事务型表为例,提交通常涉及两类持久化对象:

事务修改
  ├─ 存储引擎日志和数据页
  └─ 服务器二进制日志

二进制日志记录的是用于复制和恢复的变更事件,存储引擎日志用于本地崩溃恢复。服务器需要协调两者,避免出现:

  • 本地事务已经提交,但二进制日志没有完整记录;
  • 二进制日志显示已提交,但本地数据没有相应提交。

这通常依靠存储引擎与二进制日志之间的提交协调机制完成。具体刷盘时机受配置影响,因此“写入 binlog”与“数据已经在物理介质上持久化”不是同一个概念。

2. 二进制日志的三种基本记录方式

Statement-based replication

记录原始 SQL:

UPDATE account SET balance = balance - 10 WHERE id = 1;

优点是日志较小;风险是语句如果依赖非确定性因素,副本可能执行出不同结果。例如:

UPDATE t
SET value = NOW()
WHERE id = (SELECT id FROM t LIMIT 1);

如果排序不确定、时间不同或依赖本地环境,副本可能得到不同结果。

Row-based replication

记录行变化前后的值或等价行事件,而不是让副本重新推导 SQL 结果。

优点是结果更确定;代价是:

  • 批量更新可能产生大量事件;
  • 表结构和列映射必须兼容;
  • 某些工具无法方便地阅读业务语义。

Mixed-based replication

根据语句特征在语句事件和行事件之间选择。它并不意味着所有语句都自动拥有最强的一致性保证。

复制配置和默认值是版本敏感的,应在目标版本中检查:

SHOW VARIABLES LIKE 'binlog_format';
SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_row_image';

3. 副本的数据流和状态

传统异步复制可以抽象为:

Primary
  └─ binlog
       ↓ 网络
Replica I/O 线程
  └─ relay log
       ↓
Replica SQL/Applier 线程
  └─ 本地存储引擎

副本通常有两个独立进度:

  • 接收进度:已经从主库读到哪里;
  • 应用进度:已经执行到哪里。

诊断时不能只看“连接正常”。MariaDB 和 MySQL 的状态命令名称会随版本和兼容模式变化,常见检查方式包括:

SHOW REPLICA STATUS\G

某些版本仍使用:

SHOW SLAVE STATUS\G

重点观察:

  • IO 线程是否运行;
  • SQL/Applier 线程是否运行;
  • 读取到的日志文件和位置;
  • 已执行的日志文件和位置;
  • 错误号与错误文本;
  • 延迟字段;
  • GTID 接收和执行位置。

“复制线程是 Yes”也不等于业务数据完全一致。可能存在:

  • 尚未应用的 relay log;
  • 非事务表的部分应用;
  • 被过滤的表;
  • 语句在两个服务器上产生不同结果;
  • 应用成功但索引或排序语义不同。

五、MariaDB GTID 与 MySQL GTID 不是同一种坐标系

1. GTID 解决什么问题

GTID 是全局事务标识符。它让复制可以用“已执行哪些事务”描述进度,而不只依赖:

binlog 文件名 + 文件内位置

这样切换副本时,系统可以根据事务集合判断:

  • 副本已经执行了哪些事务;
  • 还需要从哪里继续;
  • 是否存在缺失或冲突事务。

2. MariaDB 的 GTID 结构

MariaDB GTID 通常包含:

domain_id-server_id-sequence

可抽象为:

10-201-345

其中:

  • domain_id:复制域,用于区分独立复制序列;
  • server_id:生成事务的服务器标识;
  • sequence:该域中的递增序号。

不同版本和拓扑对 domain_id 的使用方式不同,配置错误可能导致并行复制判断或故障切换判断不符合预期。

3. MySQL 的 GTID 结构

MySQL 常见 GTID 形式为:

source_uuid:transaction_number

例如:

3e11fa47-71ca-11e1-9e33-c80aa9429562:12345

其集合通常按 UUID 和事务区间表示。

两者的关键差异是:

  • 标识结构不同;
  • 集合表示不同;
  • 自动定位参数不同;
  • 复制状态变量不同;
  • 故障切换工具对 GTID 的处理不同。

因此不能把 MariaDB 的 GTID 字符串直接填入 MySQL 的 GTID 配置,也不能把 MySQL 的 GTID 集合直接当作 MariaDB 的执行集合。

4. GTID 不会修复数据不一致

假设主库执行了事务:

UPDATE account SET balance = balance - 10 WHERE id = 1;

副本因为表结构不同,执行后把金额转换成了另一种精度。只要复制事件成功应用,GTID 仍然可能显示该事务已执行。

GTID 记录的是“事务身份和复制进度”,不是“业务状态已经等价”。

因此切换前仍应进行:

  • 行数校验;
  • 主键范围校验;
  • 校验和或抽样比对;
  • 关键业务查询比对;
  • 复制过滤检查;
  • 非事务表一致性检查。

六、MariaDB 复制与 MySQL 复制的边界

1. 跨产品复制可能可行,但不是默认兼容

在某些产品和版本组合中,MariaDB 可以作为 MySQL 的复制源或副本,反之亦然。但是否可行取决于:

  • 源端和目标端具体版本;
  • binlog 格式;
  • GTID 是否启用;
  • 认证插件;
  • DDL 类型;
  • 表结构;
  • 存储引擎;
  • 字符集和排序规则;
  • 复制过滤;
  • 事件是否使用目标端不认识的扩展。

必须把“协议上可以建立复制连接”和“业务数据可以长期保持等价”分开验证。

2. 不应直接转发所有二进制日志

二进制日志不是通用的跨产品数据交换格式。以下内容都可能造成边界问题:

  • 一端产生的 GTID 目标端无法识别;
  • 目标端不认识某种 binlog event;
  • DDL 在目标端的语法或数据字典语义不同;
  • row event 的列元数据不匹配;
  • JSON、空间类型或生成列无法等价重放;
  • 使用 statement 格式时依赖本地环境;
  • sql_mode 不同导致同一条语句结果不同。

在跨产品复制迁移中,通常应:

  1. 选择明确支持的版本组合;
  2. 优先使用行格式或按官方限制配置;
  3. 先在测试环境完成全量导入和持续变更验证;
  4. 记录源端和目标端的复制位置;
  5. 用业务数据比对而非只看复制线程;
  6. 设计无法继续复制时的回滚路径。

3. Galera 与传统异步复制不是一回事

MariaDB 生态中还存在基于 Galera/wsrep 的同步或近同步集群方案。它与传统的 binlog 主从复制在机制上不同:

传统复制:
主库提交 → binlog → 副本接收 → 副本应用

Galera 类方案:
节点事务认证/写集合传播 → 多节点应用或提交

Galera 关注的是多主节点间的写集认证、冲突检测和状态转移;传统复制关注的是二进制日志事件传输和顺序应用。

两者不能互换地理解为:

  • Galera 不是“自动拥有 MySQL GTID”;
  • 传统异步复制不是“多节点同步提交”;
  • 集群节点可见不等于每个读请求都读到同一时刻的数据;
  • wsrep 配置、状态转移和应用约束会引入新的边界。

如果架构使用 Galera,应单独验证:

  • 节点加入和状态转移;
  • 网络分区;
  • 冲突事务;
  • 自增主键分配;
  • DDL 行为;
  • 与外部异步副本的衔接方式。

七、迁移方式:逻辑、物理和复制迁移

1. 逻辑迁移

逻辑迁移把数据库转换为 SQL 或其他逻辑记录:

表结构 DDL
  ↓
INSERT 或批量数据
  ↓
索引、约束、触发器、事件等对象

常见工具包括 mariadb-dumpmysqldump 和面向大数据集的并行导出工具。具体参数应以目标版本工具的帮助信息为准:

mariadb-dump --help
mysqldump --help

一个最小示例:

mariadb-dump \
  --single-transaction \
  --routines \
  --events \
  --triggers \
  app > app.sql

--single-transaction 的成立条件是:主要数据表使用支持一致性读的事务型引擎,通常是 InnoDB,并且导出期间不发生破坏快照一致性的 DDL。

它不能自动保证:

  • MyISAM 或其他非事务表的一致快照;
  • 所有引擎的统一时间点;
  • 外部文件、对象存储和应用缓存的一致性;
  • 目标端能接受源端所有 DDL。

导入前先检查文件头和目标兼容性,再执行:

mariadb app < app.sql

导入后检查:

SELECT COUNT(*) FROM important_table;
SHOW CREATE TABLE important_table\G
CHECK TABLE important_table;

逻辑迁移的优点是跨版本、跨产品边界最清晰;缺点是耗时较长,并且需要重新建立索引、统计信息和权限。

2. 物理迁移

物理迁移可能包括:

  • 复制整个数据目录;
  • 搬运共享表空间;
  • 搬运单表表空间;
  • 使用引擎级备份工具恢复。

它通常要求源和目标满足严格的版本、引擎和配置条件。跨 MariaDB 与 MySQL 时,不应把“文件能复制过去”当作“数据库可以启动”。

错误表现可能包括:

  • 服务器启动失败;
  • 表不存在或表空间无法打开;
  • 数据字典与文件不一致;
  • redo/undo 日志无法恢复;
  • 启动后部分表报错;
  • 数据看似可读但后续写入损坏。

物理方式适合受支持的同产品、兼容版本和明确文档约束下的快速恢复,不适合作为跨产品默认迁移手段。

3. 基于复制的在线迁移

在线迁移通常分为:

阶段 1:源库全量导出
阶段 2:目标库导入
阶段 3:目标库追平增量
阶段 4:停止写入或短暂切换
阶段 5:校验并让应用切换

抽象流程如下:

Source
  ├─ 全量快照 ───────────────► Target
  └─ binlog 增量 ─────────────► Target

切换前需要同时满足:

  1. 全量数据已经导入;
  2. 增量事件已经应用到目标;
  3. 目标没有复制错误;
  4. 关键表和关键业务数据已比对;
  5. 应用连接配置可以切换;
  6. 源端写入可以停止或被可靠隔离;
  7. 已知无法兼容的对象已经改写。

如果使用 GTID,应分别保存:

  • 源端已生成的 GTID 集合;
  • 目标端已接收的 GTID;
  • 目标端已执行的 GTID;
  • 切换时最后一次确认的业务时间点。

如果不使用 GTID,则必须记录 binlog 文件和位置,并确保导出快照对应的起始位置准确。位置记录错误会造成:

  • 重复执行事务;
  • 跳过事务;
  • 目标端复制报主键冲突;
  • 表面追平但实际缺数据。

八、一个具体迁移算例

假设源库存在以下表:

CREATE TABLE customer (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    profile JSON
) ENGINE=InnoDB;

CREATE TABLE customer_log (
    id BIGINT PRIMARY KEY,
    customer_id BIGINT NOT NULL,
    message VARCHAR(200)
) ENGINE=MyISAM;

迁移到 MariaDB 时,不能只执行:

CREATE DATABASE new_app;

还应先回答四个问题。

第一步:确认引擎边界

SELECT TABLE_NAME, ENGINE
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'app';

如果目标要求订单和日志具有同一事务原子性,则应考虑将日志表也迁移为事务型引擎:

CREATE TABLE customer_log (
    id BIGINT PRIMARY KEY,
    customer_id BIGINT NOT NULL,
    message VARCHAR(200)
) ENGINE=InnoDB;

但这已经是语义变更,不是机械迁移。需要验证:

  • 写入性能是否可接受;
  • 索引是否完整;
  • 原有依赖 MyISAM 的表级锁行为是否仍被应用依赖;
  • 外键是否需要补充。

第二步:确认 JSON 语义

profile 字段执行:

SELECT id, JSON_VALID(profile)
FROM customer
WHERE profile IS NOT NULL
  AND JSON_VALID(profile) = 0;

如果源端允许非标准 JSON 字符串,而目标端约束更严格,导入可能失败或应用行为改变。迁移前应决定是:

  • 清理非法数据;
  • 把字段改为普通文本;
  • 使用目标产品支持的 JSON 约束和索引;
  • 修改应用访问方式。

第三步:验证事务结果

迁移后执行同样的业务事务:

START TRANSACTION;

UPDATE customer
SET name = 'new-name'
WHERE id = 1;

INSERT INTO customer_log
VALUES (100, 1, 'renamed');

ROLLBACK;

预期是两张表都不发生变化。若日志表仍然有记录,说明目标仍使用非事务引擎,不能满足该事务边界。

第四步:验证复制结果

如果迁移期间建立复制,不能只执行:

SHOW REPLICA STATUS\G

还应在源端和目标端比较:

SELECT COUNT(*) FROM customer;
SELECT COUNT(*) FROM customer_log;

SELECT MIN(id), MAX(id) FROM customer;
SELECT MIN(id), MAX(id) FROM customer_log;

对关键业务表,可以按主键范围计算摘要:

SELECT
    MIN(id),
    MAX(id),
    COUNT(*),
    SUM(CRC32(CONCAT_WS('#', id, name)))
FROM customer;

这个摘要不是通用强校验,存在碰撞和 NULL 处理问题,但可以用于发现明显差异。高风险迁移应采用更严格的逐行或分片校验。


九、常见误解与失败路径

误解一:能用同一个客户端连接,就能无缝替换

失败原因:

  • SQL 方言不同;
  • 错误码不同;
  • 默认排序规则不同;
  • 权限系统不同;
  • JSON 或空间类型不同;
  • 依赖 MySQL 专有优化器行为。

诊断方法:

SELECT VERSION();
SHOW VARIABLES LIKE 'sql_mode';
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';

同时收集应用实际执行的 SQL,而不是只测试登录。

误解二:两个服务器都支持 InnoDB,所以可以直接搬数据目录

失败原因是数据字典、日志格式和版本实现可能不同。正确做法是:

  • 使用同产品、受支持版本的物理备份方案;
  • 跨产品优先使用逻辑导出;
  • 迁移前在隔离环境做完整恢复测试。

误解三:复制无报错就代表完全一致

复制无报错只能说明事件被接受并应用,不能证明:

  • 过滤规则没有遗漏;
  • 非事务表没有中间状态;
  • 语句在两端结果相同;
  • 字符集和排序规则一致;
  • 所有对象都已迁移。

应结合复制状态、业务校验和关键查询结果判断。

误解四:GTID 可以自动解决跨产品切换

GTID 只能帮助标记事务进度,不能转换:

  • MariaDB GTID 与 MySQL GTID 的格式;
  • 不同 binlog event;
  • 不同 DDL 语义;
  • 不同数据类型;
  • 不同存储引擎行为。

跨产品切换必须把 GTID 当作进度工具,而不是兼容性层。

误解五:--single-transaction 可以保证整个数据库一致

它主要依赖事务型引擎的一致性快照。若数据库中有 MyISAM 等非事务表,导出期间这些表可能继续变化,导致不同表不属于同一个时间点。

恢复方法包括:

  • 对非事务表单独锁定;
  • 在业务低峰或停写窗口导出;
  • 将表改为事务型引擎;
  • 使用支持该场景的一致性备份方案;
  • 在恢复后执行跨表业务校验。

十、如何判断迁移是否越过了边界

可以按以下顺序做技术判定。

1. 先判断数据模型是否等价

检查:

SHOW CREATE TABLE table_name\G

比较:

  • 列类型;
  • 长度和精度;
  • 字符集和排序规则;
  • 主键、唯一键和普通索引;
  • 生成列;
  • 默认值;
  • 检查约束;
  • 外键;
  • 触发器。

2. 再判断事务边界是否等价

确认每张业务表的引擎,并测试:

  • 提交;
  • 回滚;
  • savepoint;
  • 锁定读;
  • 并发更新;
  • 死锁处理;
  • 崩溃恢复后的结果。

3. 再判断复制事件是否等价

确认:

  • binlog 是否启用;
  • binlog 格式;
  • GTID 机制;
  • 复制用户权限;
  • DDL 事件;
  • row event 列映射;
  • 过滤规则;
  • 目标端是否支持源端事件。

4. 最后判断切换是否可回退

至少要明确:

切换前最后写入点
目标端最后应用点
应用连接切换方式
源端重新接收写入的条件
双写或回切时的冲突处理

如果切换后源端继续写入,而目标端也开始写入,两个方向的增量就可能产生冲突。传统单向复制并不会自动合并双主写入。


十一、适用边界的最终判断

可以把 MariaDB 与 MySQL 的关系概括为三层结论:

通常可以复用的部分

  • 常规 SQL 查询;
  • 基本表结构;
  • InnoDB 上的常见事务代码;
  • 标准客户端连接方式;
  • 通过逻辑导出迁移的大部分普通数据。

必须验证的部分

  • JSON、空间类型和生成列;
  • 字符集、排序规则和索引长度;
  • sql_mode 和默认值;
  • 用户权限与认证插件;
  • 存储引擎能力;
  • binlog 格式和复制状态;
  • GTID 与自动定位;
  • 触发器、事件、存储过程和调度任务;
  • 优化器产生的执行计划。

不应默认假设兼容的部分

  • 直接替换数据目录;
  • 直接搬运引擎表空间;
  • 直接互换 GTID;
  • 不经验证地转发二进制日志;
  • 使用复制线程状态代替数据一致性校验;
  • 在混合存储引擎上假设全局事务原子性。

MariaDB Server 与 MySQL 的迁移,本质上不是“换一个同类进程”,而是同时迁移四种语义:

SQL 语义
存储引擎语义
复制进度语义
运维与故障切换语义

只有当这四层分别验证通过时,迁移才不仅是“服务启动了”,而是目标系统真正保持了所需的数据、事务和故障行为。


系列导航与关联阅读

官方资料

本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。