Karp 的技术博客

背景

我们的系统长期以来采用 MySQL(InnoDB) 作为 OLTP 主库,TiDB 作为分布式扩展层,整体承载了核心业务的读写和部分分析型查询。

然而,近期的外部变化让我们不得不重新审视这套技术选型:

  1. 云运营商开始停止维护 MySQL 8.4 以下版本,意味着旧版本将失去安全补丁和官方支持;
  2. MySQL 8.4 引入了大量不向前兼容的变更,升级成本极高,部分语法和行为与旧版本存在显著差异;
  3. 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. 存储引擎架构

MySQLPostgreSQL
引擎设计插件式多引擎(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 InnoDBPostgreSQL
行级锁
表级锁粒度读锁 / 写锁(2种)8 种表级锁模式,粒度更细
间隙锁(Gap Lock)✅(防止幻读)❌ 不需要,MVCC 直接解决幻读
Advisory Lock(咨询锁)✅ 独有,适合分布式任务调度
死锁自动检测
注意:PostgreSQL 没有间隙锁,因为其 MVCC 实现本身已经能在 Repeatable Read 级别防止幻读,无需依赖间隙锁。

4. 索引体系

索引类型MySQL InnoDBPostgreSQL
B-Tree✅ 默认✅ 默认
Hash⚠️ 支持但不推荐
GIN(倒排索引)✅ 适合 JSON、数组、全文检索
GiST(空间索引)✅ 适合地理/几何类型
BRIN(块范围索引)✅ 适合超大时序表
部分索引(Partial Index)✅ 只索引满足条件的行
表达式索引⚠️ 有限✅ 完整支持
INCLUDE 覆盖列✅ PG 11+
不锁表建索引✅ Online DDLCREATE 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 InnoDBPostgreSQL
默认事务隔离级别Repeatable ReadRead Committed
DDL 事务❌ DDL 自动提交,无法回滚✅ DDL 可包含在事务中并回滚
ACID
PostgreSQL 支持 DDL 事务 是一个非常重要的运维优势。执行 ALTER TABLE 失败时可以整体回滚,避免数据库处于中间状态。

迁移注意事项

迁移过程中需要重点关注以下差异:

语法层面

  • MySQL 的 `反引号` 标识符在 PG 中需改为 "双引号"
  • AUTO_INCREMENT 改为 SERIALGENERATED ALWAYS AS IDENTITY
  • LIMIT x, y 改为 LIMIT y OFFSET x
  • GROUP BY 在 PG 中更严格,SELECT 的非聚合列必须出现在 GROUP BY 中

行为层面

  • PG 字符串比较大小写敏感,MySQL 默认不敏感
  • PG 的 boolean 类型是真正的布尔,不是 0/1 整数
  • 时间类型处理更严格,需要明确时区

运维层面

  • 需要制定 VACUUMANALYZE 的定期维护策略
  • 监控表膨胀(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 核心差异

维度TiDBApache Doris
定位HTAP(兼顾 OLTP + OLAP)专注 OLAP(实时数仓)
兼容协议MySQL 协议MySQL 协议(高度兼容)
存储模型行存(TiKV)+ 列存(TiFlash)纯列存
查询引擎TiDB SQL Layer + MPPMPP 向量化执行引擎
实时写入✅ 强一致事务写入✅ 支持实时导入(微批)
复杂聚合性能一般(依赖 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 不兼容),但结果是我们主动拥抱了更好的技术选型:

旧方案新方案收益
OLTPMySQL InnoDBPostgreSQL更强的 SQL 标准支持、更丰富的索引类型、DDL 事务、更活跃的开源社区
OLAP / 分布式TiDBApache Doris更专业的列存引擎、更高的分析性能、更低的运维复杂度、不依赖 MySQL 版本

技术债务的清偿往往需要一个契机

版权属于:karp
作品采用:本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
更新于: 2026年04月07日 13:53
1

目录

来自 《从 MySQL + TiDB 迁移至 PostgreSQL + Apache Doris —— 我们为什么做这个决定》