数据库基础体系 · 第 58/139 篇。文章以各产品官方稳定版本的公开语义为准;示例会明确引擎、事务与部署边界。
MariaDB Server:与 MySQL 的差异、存储引擎、复制和迁移边界
MariaDB Server 与 MySQL 都是关系型数据库服务器,使用 SQL 作为主要访问语言,也都采用“服务器层 + 存储引擎”的架构,并通过二进制日志支持复制。
但“MariaDB 是 MySQL 的替代品”只能作为粗略描述,不能作为迁移或复制的技术结论。MariaDB 源自 MySQL,后来在版本、优化器、存储引擎、数据类型、系统表、复制协议和管理工具等方面逐渐分叉。因此:
- SQL 的常见子集通常可以迁移;
- 表结构和数据通常可以通过逻辑导出迁移;
- 二进制日志通常不能被另一产品无条件、无损地直接消费;
- 物理数据目录不能直接互换;
- 复制、故障切换和 GTID 需要按产品和版本单独设计。
本文围绕四个问题展开:
- MariaDB 与 MySQL 的兼容性边界在哪里;
- 存储引擎如何决定事务、锁和崩溃恢复语义;
- MariaDB 复制与 MySQL 复制的数据流、GTID 和故障边界是什么;
- 什么时候应当使用逻辑迁移、复制迁移或物理迁移。
一、先明确比较对象:服务器、引擎和协议不是一回事
一条 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 表示、事件能力、并行复制和过滤配置不同 |
| 认证 | 默认认证方式和插件生态可能不同 |
| 管理工具 | mysqldump、mariadb-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. 事务提交不是只写一份文件
以事务型表为例,提交通常涉及两类持久化对象:
事务修改
├─ 存储引擎日志和数据页
└─ 服务器二进制日志
二进制日志记录的是用于复制和恢复的变更事件,存储引擎日志用于本地崩溃恢复。服务器需要协调两者,避免出现:
- 本地事务已经提交,但二进制日志没有完整记录;
- 二进制日志显示已提交,但本地数据没有相应提交。
这通常依靠存储引擎与二进制日志之间的提交协调机制完成。具体刷盘时机受配置影响,因此“写入 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不同导致同一条语句结果不同。
在跨产品复制迁移中,通常应:
- 选择明确支持的版本组合;
- 优先使用行格式或按官方限制配置;
- 先在测试环境完成全量导入和持续变更验证;
- 记录源端和目标端的复制位置;
- 用业务数据比对而非只看复制线程;
- 设计无法继续复制时的回滚路径。
3. Galera 与传统异步复制不是一回事
MariaDB 生态中还存在基于 Galera/wsrep 的同步或近同步集群方案。它与传统的 binlog 主从复制在机制上不同:
传统复制:
主库提交 → binlog → 副本接收 → 副本应用
Galera 类方案:
节点事务认证/写集合传播 → 多节点应用或提交
Galera 关注的是多主节点间的写集认证、冲突检测和状态转移;传统复制关注的是二进制日志事件传输和顺序应用。
两者不能互换地理解为:
- Galera 不是“自动拥有 MySQL GTID”;
- 传统异步复制不是“多节点同步提交”;
- 集群节点可见不等于每个读请求都读到同一时刻的数据;
- wsrep 配置、状态转移和应用约束会引入新的边界。
如果架构使用 Galera,应单独验证:
- 节点加入和状态转移;
- 网络分区;
- 冲突事务;
- 自增主键分配;
- DDL 行为;
- 与外部异步副本的衔接方式。
七、迁移方式:逻辑、物理和复制迁移
1. 逻辑迁移
逻辑迁移把数据库转换为 SQL 或其他逻辑记录:
表结构 DDL
↓
INSERT 或批量数据
↓
索引、约束、触发器、事件等对象
常见工具包括 mariadb-dump、mysqldump 和面向大数据集的并行导出工具。具体参数应以目标版本工具的帮助信息为准:
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
切换前需要同时满足:
- 全量数据已经导入;
- 增量事件已经应用到目标;
- 目标没有复制错误;
- 关键表和关键业务数据已比对;
- 应用连接配置可以切换;
- 源端写入可以停止或被可靠隔离;
- 已知无法兼容的对象已经改写。
如果使用 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 语义
存储引擎语义
复制进度语义
运维与故障切换语义
只有当这四层分别验证通过时,迁移才不仅是“服务启动了”,而是目标系统真正保持了所需的数据、事务和故障行为。
系列导航与关联阅读
- 系列入口:数据库完整学习路线:从关系模型、事务索引到分布式与向量检索
- 上一篇:OceanBase 分布式数据库:分区、副本、事务、高可用和运维
- 下一篇:向量数据库基础:Embedding、距离度量、召回、过滤与一致性
- 延伸:MySQL 架构全景:连接层、优化器、执行器、存储引擎与日志
- 延伸:MySQL 复制与高可用:Binlog、GTID、半同步、切换与一致性
官方资料
本文依据数据库官方文档重新梳理;正文、示例与生产检查清单由 WR BLOG 编写。

评论
0 条讨论