MySQL 数据为什么要同步到 Elasticsearch?全量、增量同步与常见方案
在实际项目中,我们经常会看到这样的架构:
MySQL → Elasticsearch
甚至:
MySQL → Canal → MQ → Elasticsearch
第一次看到可能会产生几个疑问:
- 数据已经存在 MySQL 了,为什么还要存一份到 ES?
- 什么是全量同步?
- 什么是增量同步?
- Canal 是干什么的?
- RabbitMQ / Kafka 又起什么作用?
- MySQL 和 ES 数据不一致怎么办?
本文用一个电商商品搜索的例子,把这些问题串起来。
一、为什么要把 MySQL 数据同步到 ES?
先记住一句话:
MySQL 负责可靠地存储业务数据,Elasticsearch 负责快速、灵活地搜索数据。
例如 MySQL 中有一张商品表:
CREATE TABLE product (
id BIGINT PRIMARY KEY,
name VARCHAR(255),
description TEXT,
brand VARCHAR(100),
price DECIMAL(10, 2),
stock INT
);
用户需要搜索:
苹果手机
简单情况下,可以直接使用 MySQL:
SELECT *
FROM product
WHERE name LIKE '%苹果手机%';
但是随着业务越来越复杂,可能需要:
关键词搜索
+ 分词
+ 多字段搜索
+ 相关度排序
+ 品牌筛选
+ 价格筛选
+ 搜索高亮
+ 搜索建议
这类搜索场景正是 Elasticsearch 擅长的。
因此系统通常会变成:
写数据
↓
MySQL
业务主数据源
│
│ 同步
▼
Elasticsearch
搜索索引
↑
│
用户搜索
这里要特别注意:
ES 通常不是 MySQL 的替代品。
一般认为:
MySQL = Source of Truth
ES = 为搜索建立的数据副本
例如:
MySQL
id = 1001
name = iPhone 17 Pro
price = 7999
ES 中可能也保存:
{
"id": 1001,
"name": "iPhone 17 Pro",
"price": 7999
}
用户下单、修改商品等核心业务操作 MySQL。
用户搜索商品时查询 ES。
二、MySQL → ES 的核心问题:数据同步
既然一份数据同时存在:
MySQL
+
Elasticsearch
就必须解决一个问题:
MySQL 数据变化以后,ES 怎么跟着变化?
例如:
UPDATE product
SET price = 6999
WHERE id = 1001;
此时 MySQL:
price = 6999
但是 ES 如果还是:
price = 7999
用户搜索出来的价格就是错误的。
所以需要:
MySQL
│
│ 数据同步
▼
Elasticsearch
数据同步通常又分为:
全量同步
增量同步
三、什么是全量同步?
全量同步非常好理解:
把 MySQL 中符合条件的所有数据重新同步到 ES。
例如 MySQL 中存在 100 万条商品:
MySQL
商品 1
商品 2
商品 3
...
商品 1000000
全量同步就是:
MySQL
│
│ 查询所有商品
▼
同步程序
│
▼
ES
最终重新构建完整的商品索引。
简单来说就是:
100 万条数据
全部重新同步一次
全量同步适合什么时候?
最典型的场景是:
1. 第一次初始化 ES
项目之前只有 MySQL,现在第一次引入 Elasticsearch。
ES 是空的:
MySQL:100 万商品
ES:0 条
这时候必须执行一次全量同步。
2. ES 索引需要重建
例如修改了 ES Mapping:
旧索引
↓
删除 / 新建索引
↓
重新从 MySQL 导入
这时也需要全量同步。
3. 数据严重不一致
如果因为程序 Bug 等原因导致:
MySQL
和
ES
大量数据对不上,可以重新全量构建索引。
四、全量同步为什么不能每次都做?
假设数据库只有:
1000 条
全部同步可能没什么问题。
但如果:
1000 万条商品
今天只有一件商品价格发生变化:
商品 1001
7999 → 6999
为了这一条数据重新同步:
1000 万条
显然非常浪费。
所以实际系统不能只依赖全量同步。
于是就有了:
增量同步
五、什么是增量同步?
增量同步就是:
只同步发生变化的数据。
假设:
MySQL:1000 万商品
今天只有:
商品 1001 修改价格
商品 2002 修改名称
商品 3003 被删除
那么只需要同步:
1001
2002
3003
而不是重新同步 1000 万条。
所以:
全量同步
=
把所有数据重新同步
增量同步
=
只同步新增 / 修改 / 删除的数据
这两个概念一定要区分开。
六、增量同步需要监听哪些操作?
主要就是数据库的:
INSERT
UPDATE
DELETE
例如:
INSERT INTO product ...;
ES:
新增 Document
MySQL:
UPDATE product ...;
ES:
更新 Document
MySQL:
DELETE FROM product WHERE id = 1001;
ES:
删除 Document
因此增量同步的核心问题其实变成了:
怎么知道 MySQL 哪些数据发生了变化?
这就产生了不同的同步方案。
七、方案一:业务代码直接同步 ES
最简单的方法:
业务服务
│
├── 更新 MySQL
│
└── 更新 ES
例如:
public void updateProduct(Product product) {
productMapper.updateById(product);
elasticsearchRepository.save(product);
}
流程:
修改商品
↓
更新 MySQL
↓
更新 ES
优点
非常简单。
小项目特别容易实现。
缺点
业务代码和 ES 强耦合。
而且存在一个很麻烦的问题:
MySQL 更新成功
↓
ES 更新失败
最终:
MySQL = 新数据
ES = 旧数据
所以稍微复杂一些的项目通常不会直接这么干。
八、方案二:MySQL + MQ + ES
更常见的一种方式:
业务服务
↓
MySQL
↓
发送 MQ
↓
RabbitMQ / Kafka
↓
ES 同步消费者
↓
Elasticsearch
例如商品修改完成:
productMapper.updateById(product);
rabbitTemplate.convertAndSend(
"product.exchange",
"product.updated",
product.getId()
);
发送:
{
"productId": 1001,
"event": "PRODUCT_UPDATED"
}
消费者收到:
productId = 1001
然后:
查询 MySQL 最新数据
↓
更新 ES
结构变成:
Product Service
↓
MySQL
↓
RabbitMQ
↓
ES Consumer
↓
Elasticsearch
为什么使用 MQ?
主要是为了:
异步
解耦
削峰
失败重试
业务服务不需要等待 ES 更新完成以后才能返回。
但是这种方案依然有一个经典问题:
更新 MySQL 成功
↓
准备发送 MQ
↓
服务突然宕机
结果:
MySQL 更新成功
MQ 消息没发送
ES 没更新
还是可能不一致。
因此又出现了一种非常常见的方案:
Canal / CDC
九、方案三:Canal + MQ + ES
Canal 的核心思想非常简单:
不让业务代码主动告诉我们 MySQL 发生了变化,而是直接监听 MySQL 的 binlog。
整体结构:
业务服务
↓
MySQL
↓
binlog
↓
Canal
↓
RabbitMQ / Kafka
↓
同步消费者
↓
Elasticsearch
十、什么是 binlog?
binlog 可以简单理解成:
MySQL 记录数据变化的日志。
例如执行:
UPDATE product
SET price = 6999
WHERE id = 1001;
MySQL 完成修改以后,会产生对应的 binlog 变化记录。
Canal 就可以监听这些变化。
因此:
MySQL
│
│ binlog
▼
Canal
Canal 能发现:
product 表发生 UPDATE
id = 1001
price:
7999 → 6999
然后将这个变化继续发送出去。
十一、Canal 和 MQ 是不是重复了?
不是。
这是非常容易混淆的地方。
可以这样理解:
Canal
↓
负责发现 MySQL 发生了什么变化
MQ
↓
负责把变化事件传递出去
Consumer
↓
负责处理变化
ES
↓
保存用于搜索的数据
完整流程:
MySQL
│
binlog
│
▼
Canal
│
捕获数据变化
│
▼
RabbitMQ / Kafka
│
传递变化事件
│
▼
ES Consumer
│
▼
Elasticsearch
所以 Canal 和 RabbitMQ 是两个不同角色。
十二、Canal 属于什么技术?
Canal 本质上属于:
CDC
全称:
Change Data Capture
中文:
变更数据捕获
CDC 的核心思想就是:
捕获数据库发生的 INSERT、UPDATE、DELETE 等变化,然后把这些变化同步到其他系统。
因此除了:
MySQL → ES
还可以:
MySQL → 数据仓库
MySQL → Redis
MySQL → Kafka
MySQL → 其他数据库
Canal 只是 CDC 的一种实现方案。
常见的 CDC / 数据同步技术还包括:
Canal
Debezium
Flink CDC
十三、另一种简单增量方案:根据更新时间扫描
还有一种很好理解的方法。
商品表增加:
update_time DATETIME
然后同步程序记录:
上次同步时间:
2026-09-01 10:00:00
下一次查询:
SELECT *
FROM product
WHERE update_time > '2026-09-01 10:00:00';
假设查到:
商品 1001
商品 2002
商品 3003
只同步这些数据。
然后更新同步时间。
结构:
定时任务
↓
查询 update_time
↓
找出变化数据
↓
同步 ES
优点是:
简单
容易实现
但是也有不少问题,例如:
删除数据不好处理
时间边界容易出问题
频繁扫描数据库
实时性比较差
因此比较适合数据量不大、实时性要求不高的系统。
十四、全量同步和增量同步通常要一起使用
这点非常重要。
实际项目通常不是:
全量 or 增量
而是:
全量 + 增量
例如系统第一次上线:
第一步:
MySQL
1000 万数据
↓
全量同步
↓
ES
1000 万数据
之后系统正常运行:
MySQL
↓
INSERT / UPDATE / DELETE
↓
增量同步
↓
ES
所以整个生命周期可以理解成:
系统第一次上线
│
▼
全量同步
│
▼
MySQL ============================= ES
│
初始化完成
│
▼
增量同步
│
┌───────────┼───────────┐
▼ ▼ ▼
INSERT UPDATE DELETE
│ │ │
└───────────┼───────────┘
▼
ES
一句话:
全量同步解决“历史数据怎么过去”,增量同步解决“以后变化的数据怎么过去”。
十五、实际项目常见同步方案对比
| 方案 | 实现难度 | 实时性 | 适合场景 |
|---|---|---|---|
| 手动/定时全量同步 | 低 | 低 | 初始化、小数据量 |
| update_time 定时扫描 | 低 | 一般 | 简单项目 |
| 业务代码直接更新 ES | 低 | 高 | 小项目 |
| 业务代码 + MQ | 中 | 高 | 常规业务 |
| Canal + MQ | 中高 | 高 | 数据同步、业务解耦 |
| Debezium + Kafka | 高 | 高 | CDC、数据平台 |
| Flink CDC | 高 | 高 | 实时数据处理、复杂数据链路 |
并不是技术越复杂越好。
如果项目只有几万条数据:
定时任务
+
update_time
可能已经足够。
如果业务规模比较大,希望业务代码和数据同步解耦,可以考虑:
MySQL
↓
Canal
↓
MQ
↓
ES
如果公司本身已经大量使用 Kafka、Flink 等数据基础设施,则可能选择:
MySQL
↓
Flink CDC / Debezium
↓
Kafka
↓
ES
十六、全量同步还有一个现实问题:不能一次查几百万条
例如:
SELECT *
FROM product;
如果商品表有:
1000 万条
直接全部加载到 JVM 内存显然不合适。
所以全量同步一般会:
分批查询
+
批量写入 ES
例如:
MySQL
1 ~ 1000
↓
ES Bulk
1001 ~ 2000
↓
ES Bulk
2001 ~ 3000
↓
ES Bulk
...
通常会利用:
分页 / 游标
+
ES Bulk API
降低数据库、JVM 和 ES 的压力。
十七、MySQL 和 ES 必须强一致吗?
大多数搜索业务并不要求。
例如管理员把商品价格:
7999
↓
6999
修改以后:
MySQL:立即变成 6999
几百毫秒后
ES:变成 6999
这段很短的时间内:
MySQL != ES
很多搜索场景是可以接受的。
这种思想叫:
最终一致性
即:
不要求两个系统每一毫秒的数据都完全一致,但最终必须达到一致状态。
因此:
MySQL
↓
MQ
↓
ES
这种异步架构在搜索系统中非常常见。
十八、为什么一般让 MySQL 做主数据?
因为 ES 的主要职责是搜索,而不是承担所有核心业务数据。
例如:
创建订单
扣库存
支付
账户余额
这些业务通常更依赖:
事务
一致性
约束
可靠更新
所以:
MySQL
│
Source of Truth
│
│ 同步
▼
ES
Search Index
如果 ES 数据出现问题:
ES 索引损坏
ES Mapping 修改
同步程序出现 Bug
还可以:
MySQL
↓
重新全量同步
↓
重建 ES
因此可以把 ES 看成:
根据 MySQL 主数据构建出来的一份“搜索视图”。
十九、最后用一张图把所有概念串起来
一个比较典型的架构:
用户修改商品
│
▼
Product Service
│
▼
MySQL
│
binlog
│
▼
Canal
│
▼
RabbitMQ / Kafka
│
▼
Sync Consumer
│
▼
Elasticsearch
▲
│
用户搜索
第一次建立 ES:
MySQL
│
│ 全量读取
▼
同步程序
│
│ Bulk
▼
Elasticsearch
系统正常运行以后:
MySQL
│
│ binlog
▼
Canal
│
▼
MQ
│
▼
Consumer
│
▼
Elasticsearch
因此整个同步体系可以压缩成:
历史数据
↓
全量同步
↓
ES
新增 / 修改 / 删除
↓
增量同步
↓
ES
二十、总结
学习 MySQL → Elasticsearch 数据同步,只需要先抓住下面几个核心概念:
MySQL
=
业务主数据源
Elasticsearch
=
为了搜索建立的数据副本
全量同步
=
把已有数据全部同步过去
增量同步
=
只同步新增、修改、删除的数据
binlog
=
MySQL 的数据变更日志
Canal
=
监听并解析 MySQL binlog
MQ
=
传递数据变更事件
CDC
=
捕获数据库的数据变化
最终一致性
=
允许短暂不一致,但最终恢复一致
最典型的一套思路就是:
第一次:
MySQL
↓
全量同步
↓
ES
正常运行:
MySQL
↓
binlog
↓
Canal
↓
RabbitMQ / Kafka
↓
Consumer
↓
ES
所以,当以后再看到:
MySQL + Canal + MQ + Elasticsearch
不要把它理解成一堆中间件堆在一起。
它们实际上是在分工:
MySQL 负责存数据,Canal 负责发现数据变化,MQ 负责传递变化,Consumer 负责处理变化,ES 负责搜索;全量同步负责初始化历史数据,增量同步负责持续追踪后续变化。