背景
我们的系统长期以来采用 MySQL(InnoDB) 作为 OLTP 主库,TiDB 作为分布式扩展层,整体承载了核心业务的读写和部分分析型查询。
然而,近期的外部变化让我们不得不重新审视这套技术选型:
- 云运营商开始停止维护 MySQL 8.4 以下版本,意味着旧版本将失去安全补丁和官方支持;
- MySQL 8.4 引入了大量不向前兼容的变更,升级成本极高,部分语法和行为与旧版本存在显著差异;
- TiDB 宣布在 MySQL 8.4 协议适配上存在严重兼容性问题,无法跟进升级,继续使用意味着与主库协议脱节,维护风险急剧上升。
在综合评估成本、风险与长期可维护性之后,我们决定:
- OLTP 层:MySQL → PostgreSQL
- OLAP / 分布式分析层:TiDB → Apache Doris
本文记录这次迁移的技术背景、差异对比与决策过程,供团队参考与后续回溯。
第一部分:MySQL → PostgreSQL
为什么不选择继续升级 MySQL?
| 问题 | 描述 |
|---|---|
| 不向前兼容 | 8.4 废弃了大量旧语法(如 GROUP BY 隐式规则、部分函数行为),存量 SQL 改造量巨大 |
| 云厂商 EOL | 主流云厂商已陆续宣布 8.4 以下版本的维护终止时间表,继续留在旧版本有安全风险 |
| 生态成本 | 升级 MySQL 大版本需同步升级 ORM、驱动、中间件,牵一发动全身 |
| 历史债务 | 借此次重构窗口,团队希望彻底解决长期以来对 MySQL 特性依赖过深的问题 |
与其在 MySQL 版本升级上消耗大量人力,不如借重构窗口切换到一个更现代、更标准、生态更开放的数据库。
MySQL(InnoDB)vs PostgreSQL 核心差异
1. 存储引擎架构
| MySQL | PostgreSQL | |
|---|---|---|
| 引擎设计 | 插件式多引擎(InnoDB / MyISAM / Memory...) | 单一内置引擎(Heap Storage),不可替换 |
| 聚簇索引 | ✅ 主键即聚簇索引,数据与索引共存 | ❌ 所有索引均为二级索引,通过 ctid 回表 |
| 死行清理 | Undo Log 自动回收 | 需要 VACUUM 定期清理死行 |
2. MVCC 实现差异
两者都实现了 MVCC(多版本并发控制),但实现路径不同:
- MySQL InnoDB:旧版本数据存储在 Undo Log 中,主数据文件只保留最新版本,读取旧版本需回溯 Undo 链。
- PostgreSQL:旧版本数据直接存储在数据页中,每行记录
xmin / xmax事务号标记版本,无需 Undo Log,但需要VACUUM定期清除死行,防止表膨胀。
3. 锁机制
| 特性 | MySQL InnoDB | PostgreSQL |
|---|---|---|
| 行级锁 | ✅ | ✅ |
| 表级锁粒度 | 读锁 / 写锁(2种) | 8 种表级锁模式,粒度更细 |
| 间隙锁(Gap Lock) | ✅(防止幻读) | ❌ 不需要,MVCC 直接解决幻读 |
| Advisory Lock(咨询锁) | ❌ | ✅ 独有,适合分布式任务调度 |
| 死锁自动检测 | ✅ | ✅ |
注意:PostgreSQL 没有间隙锁,因为其 MVCC 实现本身已经能在 Repeatable Read 级别防止幻读,无需依赖间隙锁。
4. 索引体系
| 索引类型 | MySQL InnoDB | PostgreSQL |
|---|---|---|
| B-Tree | ✅ 默认 | ✅ 默认 |
| Hash | ⚠️ 支持但不推荐 | ✅ |
| GIN(倒排索引) | ❌ | ✅ 适合 JSON、数组、全文检索 |
| GiST(空间索引) | ❌ | ✅ 适合地理/几何类型 |
| BRIN(块范围索引) | ❌ | ✅ 适合超大时序表 |
| 部分索引(Partial Index) | ❌ | ✅ 只索引满足条件的行 |
| 表达式索引 | ⚠️ 有限 | ✅ 完整支持 |
| INCLUDE 覆盖列 | ❌ | ✅ PG 11+ |
| 不锁表建索引 | ✅ Online DDL | ✅ CREATE INDEX CONCURRENTLY |
5. SQL 标准兼容性
PostgreSQL 对 SQL 标准的兼容程度显著高于 MySQL,迁移后我们获得了:
-- 窗口函数(MySQL 8.0 才支持,PG 很早就有)
SELECT user_id, amount,
SUM(amount) OVER (PARTITION BY user_id ORDER BY created_at) AS running_total
FROM orders;
-- CTE(公共表表达式)
WITH ranked AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY score DESC) AS rn
FROM products
)
SELECT * FROM ranked WHERE rn <= 3;
-- JSONB 原生支持(比 MySQL JSON 类型更强)
SELECT * FROM events WHERE payload @> '{"type": "login"}';
CREATE INDEX idx_payload ON events USING GIN(payload);
-- 范围类型
SELECT * FROM reservations
WHERE daterange(start_date, end_date) @> '2026-04-07'::date;6. 事务与一致性
| 特性 | MySQL InnoDB | PostgreSQL |
|---|---|---|
| 默认事务隔离级别 | Repeatable Read | Read Committed |
| DDL 事务 | ❌ DDL 自动提交,无法回滚 | ✅ DDL 可包含在事务中并回滚 |
| ACID | ✅ | ✅ |
PostgreSQL 支持 DDL 事务 是一个非常重要的运维优势。执行 ALTER TABLE 失败时可以整体回滚,避免数据库处于中间状态。迁移注意事项
迁移过程中需要重点关注以下差异:
语法层面
- MySQL 的
`反引号`标识符在 PG 中需改为"双引号" AUTO_INCREMENT改为SERIAL或GENERATED ALWAYS AS IDENTITYLIMIT x, y改为LIMIT y OFFSET xGROUP BY在 PG 中更严格,SELECT 的非聚合列必须出现在 GROUP BY 中
行为层面
- PG 字符串比较大小写敏感,MySQL 默认不敏感
- PG 的
boolean类型是真正的布尔,不是0/1整数 - 时间类型处理更严格,需要明确时区
运维层面
- 需要制定
VACUUM和ANALYZE的定期维护策略 - 监控表膨胀(Table Bloat),必要时执行
VACUUM FULL
第二部分:TiDB → Apache Doris
为什么放弃 TiDB?
TiDB 是一款兼容 MySQL 协议的 HTAP 分布式数据库,在 MySQL 5.7 / 8.0 时代为我们提供了良好的水平扩展能力。但随着 MySQL 8.4 的演进,问题开始浮现:
| 问题 | 描述 |
|---|---|
| 协议兼容性断层 | TiDB 对 MySQL 8.4 协议的适配存在严重滞后,驱动层和语法层均有不兼容问题 |
| OLAP 性能天花板 | TiDB 的 HTAP 属性偏向 OLTP,复杂分析查询性能不及专业 OLAP 引擎 |
| 运维复杂度高 | TiDB 集群组件多(TiDB / TiKV / PD / TiFlash),运维链路长,出现问题排查成本高 |
| 成本偏高 | TiFlash(列存)需要独立节点,资源开销大 |
面对这些问题,我们选择将 OLAP 职责交给更专业的引擎——Apache Doris。
TiDB vs Apache Doris 核心差异
| 维度 | TiDB | Apache Doris |
|---|---|---|
| 定位 | HTAP(兼顾 OLTP + OLAP) | 专注 OLAP(实时数仓) |
| 兼容协议 | MySQL 协议 | MySQL 协议(高度兼容) |
| 存储模型 | 行存(TiKV)+ 列存(TiFlash) | 纯列存 |
| 查询引擎 | TiDB SQL Layer + MPP | MPP 向量化执行引擎 |
| 实时写入 | ✅ 强一致事务写入 | ✅ 支持实时导入(微批) |
| 复杂聚合性能 | 一般(依赖 TiFlash) | ✅ 极强(原生列存 + 向量化) |
| 集群架构 | 多组件(复杂) | FE + BE(相对简洁) |
| 运维难度 | 高 | 中 |
| MySQL 8.4 兼容 | ❌ 存在严重问题 | ✅ 独立演进,不依赖 MySQL 版本 |
Apache Doris 的核心优势
1. 向量化执行引擎
Doris 采用全面向量化的执行引擎,利用 SIMD 指令集对列存数据进行批量计算,在 GROUP BY、JOIN、聚合类查询上性能远超行存数据库。
2. 丰富的数据模型
Duplicate Key Model → 明细表,保留所有原始数据
Aggregate Key Model → 预聚合,适合指标汇总
Unique Key Model → 主键唯一,适合 CDC 场景(Update/Delete)3. 实时数据接入
Flink → Doris(通过 Doris Flink Connector)
Kafka → Doris(Routine Load)
MySQL Binlog → Doris(通过 Flink CDC)4. 与 PostgreSQL 协同
在新架构中,PostgreSQL 与 Doris 各司其职:
业务写入 → PostgreSQL(OLTP)
↓
Binlog / CDC 同步
↓
Apache Doris(OLAP)
↓
报表 / 数据分析 / BI 工具新架构总览
┌─────────────────────────────────────────┐
│ 业务应用层 │
└────────────┬──────────────┬─────────────┘
│ │
OLTP写入/读取 分析型查询/报表
│ │
┌────────▼──────┐ ┌────▼──────────┐
│ PostgreSQL │ │ Apache Doris │
│ (主库 OLTP) │ │ (OLAP 数仓) │
└───────────────┘ └───────────────┘
│ ▲
└──── CDC ─────┘
(Flink / Debezium)旧架构:MySQL (OLTP) + TiDB (分布式扩展 / HTAP)
新架构:PostgreSQL (OLTP) + Apache Doris (OLAP)
职责更清晰,每层使用最适合的工具。
总结
这次迁移的核心驱动力是外部环境的变化(云厂商 EOL、MySQL 8.4 不兼容),但结果是我们主动拥抱了更好的技术选型:
| 旧方案 | 新方案 | 收益 | |
|---|---|---|---|
| OLTP | MySQL InnoDB | PostgreSQL | 更强的 SQL 标准支持、更丰富的索引类型、DDL 事务、更活跃的开源社区 |
| OLAP / 分布式 | TiDB | Apache Doris | 更专业的列存引擎、更高的分析性能、更低的运维复杂度、不依赖 MySQL 版本 |
技术债务的清偿往往需要一个契机