从 MySQL 8.0 迁移到 MariaDB 11.x
跨分支迁移的完整 checklist——风险点、工具、代码差异、切流方案
MySQL 8 ↔ MariaDB 11 不再是简单 drop-in 兼容。两个分支自 2020 年起明显分叉。这一篇假设你的生产负载非平凡,需要严肃测试。
谁应该迁?
- 想脱离 Oracle 控制(许可、商业模式风险)
- 想用 ColumnStore / Xpand / VECTOR 等 MariaDB 独有特性
- 想要更彻底的开源(HeatWave 闭源)
- 想要更活跃的社区路线
谁应该不迁?
- 用了 MySQL 8 的
JSON_TABLE重度查询 - 用了 X-Plugin / X Protocol
- 用了 Group Replication 集群(迁过去要换 Galera)
- 重度依赖 InnoDB Cluster 工具链
- 用了
caching_sha2_password且客户端不易改
风险点全集
1. 默认 utf8mb4 collation 不同
| 数据库 | 默认 |
|---|---|
| MySQL 8 | utf8mb4_0900_ai_ci |
| MariaDB 11.5+ | utf8mb4_uca1400_ai_ci |
| MariaDB 11.4 | utf8mb4_general_ci |
影响:
- 排序结果可能不同
ORDER BY后端分页顺序变- 唯一约束哈希不同 → 同样的字符串可能不再"相同"
对策:导出时把 collation 替换:
sed -i 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g' dump.sql
sed -i 's/utf8mb4_0900_as_cs/utf8mb4_unicode_ci/g' dump.sql2. 认证插件
MySQL 8 默认 caching_sha2_password,MariaDB 不存在。
-- 在 MariaDB 端重建账号
CREATE USER 'app'@'%' IDENTIFIED VIA mysql_native_password USING PASSWORD('xxx');
GRANT ... TO 'app'@'%';老客户端驱动也需要支持 mysql_native_password。
3. JSON 函数差异
| 功能 | MySQL 8 | MariaDB 11 |
|---|---|---|
JSON_TABLE | ✅ 完整 | ⚠️ 部分实现 |
JSON_VALUE | ✅ | ✅ |
JSON_OVERLAPS | ✅ | ⚠️ 8.0.17+ 才有,11.x 部分支持 |
->> 运算符 | ✅ | ✅ |
JSON_TABLE 嵌套 | ✅ | ❌ |
如果你的报表查询用了 JSON_TABLE 拆数组,需要改写成 CROSS APPLY 或物化成关系表。
4. CTE 性能差异
两边都支持 CTE,但 MariaDB 10.6+ 引入了 CTE inlining,有时比 MySQL 8 快。EXPLAIN 二者输出格式略不同。
5. 优化器 hint
MySQL 8 的 /*+ HASH_JOIN(...) */ 在 MariaDB 不识别(MariaDB 已自动选 hash join)。STRAIGHT_JOIN 两边都支持。
6. 存储引擎
| 引擎 | MySQL 8 | MariaDB 11 |
|---|---|---|
| InnoDB | ✅ | ✅ |
| MyRocks | ❌(除非自编) | ✅ |
| ColumnStore | ❌ | ✅ |
| Aria | ❌ | ✅ |
| 旧 MyISAM | ✅ | ✅(建议别用) |
7. Galera 替代 Group Replication
MySQL 8 的 Group Replication(InnoDB Cluster 一部分)不能搬到 MariaDB。MariaDB 用 Galera——拓扑相似但配置文件不同。
# MariaDB Galera
[mariadb]
wsrep_on=ON
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_cluster_address="gcomm://node1,node2,node3"
wsrep_cluster_name="my_cluster"
wsrep_node_name=node1
wsrep_sst_method=mariabackup8. 复制 GTID 格式不同
MariaDB GTID 格式:<domain>-<server_id>-<seq>
MySQL GTID 格式:<server_uuid>:<seq>
两边不能直接复制(无法把 MariaDB 当 MySQL 8 的从库或反之)。要迁移用 dump + reload。
9. 加密表空间格式不同
如果用了 InnoDB Transparent Data Encryption,两边密钥环 / 加密格式不兼容,必须解密再迁。
10. 系统表差异
MySQL 8 用了 InnoDB 系统表(mysql.user 是 InnoDB),MariaDB 用 Aria。重建即可:
mariadb-install-db --user=mysql --basedir=/usr --datadir=/var/lib/mysql迁移方案对照
| 方案 | 停机 | 难度 | 适合 |
|---|---|---|---|
| 离线 dump + reload | 几小时~几天 | ⭐ | 小库、可停机 |
| 物理拷贝(仅同版本) | — | — | ❌ 不可行(格式差异) |
| 逻辑复制工具 | 分钟级 | ⭐⭐ | 需要持续追平 |
| 双写切流 | 0 | ⭐⭐⭐ | 关键业务 |
推荐:双写切流
1. 在 MariaDB 端准备空库
docker run -d --name mariadb-target \
-e MARIADB_ROOT_PASSWORD=xxx \
-p 3307:3306 mariadb:11.42. 初始全量同步
mysqldump -h mysql-source --single-transaction --routines --triggers \
--set-gtid-purged=OFF --all-databases | \
sed 's/utf8mb4_0900_ai_ci/utf8mb4_unicode_ci/g' | \
mariadb -h mariadb-target -uroot -pxxx3. 用 Debezium / Maxwell 持续同步 binlog
# Debezium connector
name: mysql-to-mariadb
config:
connector.class: io.debezium.connector.mysql.MySqlConnector
database.hostname: mysql-source
database.server.id: 184054
database.include.list: app
table.include.list: app.*
topic.prefix: app下游写一个 sink 消费 binlog 应用到 MariaDB(或用 GoldenGate / Striim)。
4. 应用层双写
async function write(sql, params) {
await Promise.all([
sourcePool.query(sql, params),
targetPool.query(sql, params).catch((e) => log.warn('shadow write fail', e)),
]);
}5. 跑数据一致性对比
# 工具:pt-table-checksum / mysql-table-sync / 自写
pt-table-checksum --replicate=app.checksums h=source --recursion-method=hosts6. 切只读流量到 MariaDB
灰度 5% → 50% → 100%,观察 1 周。
7. 切写流量
应用层 feature flag 切,秒级回滚预案保留 2 周。
必须改的代码
- 驱动连接字符串里的 auth plugin
utf8mb4collation 在 ORM 配置里固定(避免运行时检测出错)JSON_TABLE重度使用的 SQL 重写- 依赖
INFORMATION_SCHEMA.INNODB_*表的监控 重命名为 MariaDB 等价 mysqlshell →mariadbshell(兼容但建议)
验证清单
- 所有应用层 e2e 测试通过
- 主键唯一性检查(重 dump 后做)
-
pt-upgrade跑过一遍,对比关键查询结果 - 性能基准:
sysbench oltp_read_write持平或更好 - 慢日志数量没暴涨
- 备份策略改用
mariabackup(而非XtraBackup) - 监控 dashboard 改用 MariaDB 兼容版本
- DBA 培训:会用
SHOW REPLICA STATUS(MariaDB 11.4+ 标准)
常见踩坑
mysql.user表结构差异:直接拷贝会失败,必须用mariadb-install-db初始化再 GRANTgeneral_log行为差异:MariaDB 写更详细tmp目录满:MariaDB 优化器对大查询更激进,临时文件可能更大- HikariCP 连接池:默认
validateConnection用/* mysql-connector */注释,要换成SELECT 1