一、使用index Merge的企业级场景
1-1、先讲一个生活化的比喻
想象你在一家10万人的互联网大厂当 HR,老板让你找两类人:
"找出所有 2019年之前入职 且 职级是P8 的员工"
公司没有全能花名册,只有几本索引卡片:
-
卡片A:按"入职年份"排序,记录了每个人对应的工号
-
卡片B:按"职级"排序,记录了每个人对应的工号
没有 Index Merge 时(全表扫描)
你只能抱着全体员工档案柜(数据表),从第1个翻到第10万个,一个个看入职年份和职级。慢得要死,老板等得直跺脚。
有了 Index Merge 时(交集 Intersection)
MySQL 做了件聪明事:
-
查 卡片A,把"2019年前入职"的人的工号全部记下来 → 假设有 3 万人
-
查 卡片B,把"P8"的人的工号全部记下来 → 假设有 500 人
-
取交集:两份名单里都有的工号,才是最终目标 → 可能只有 80 人
-
最后只去档案柜里把这叫 80 个人的档案调出来(回表)
结果:翻了 3 万 + 500 个索引条目,但只回表 80 次。比翻 10 万个档案快多了!
1-2、企业级场景一:电商订单系统(AND + Intersection)
业务背景
你的电商平台有一张 orders 订单表,数据量 5000万条。
sql
CREATE TABLE orders (
order_id BIGINT PRIMARY KEY, -- 主键
user_id BIGINT NOT NULL, -- 用户ID
status TINYINT NOT NULL, -- 订单状态:0待支付 1已支付 2已发货 3已完成
create_time DATETIME NOT NULL,
INDEX idx_user_id (user_id), -- 单列索引1
INDEX idx_status (status) -- 单列索引2
);
业务需求
客服系统要查:"用户 10086 的所有已支付订单"
sql
SELECT * FROM orders
WHERE user_id = 10086 AND status = 1;
没有 Index Merge 会怎样?
MySQL 只能选一个索引:
-
选
idx_user_id:先找到用户 10086 的 200 个订单,再一个个回表看 status 是不是 1。要回表 200 次。 -
选
idx_status:先找到所有"已支付"订单(可能有 1000 万条),再一个个看 user_id 是不是 10086。疯了。
或者更惨------直接全表扫描,5000万条全翻一遍。
有了 Index Merge(Intersection)
MySQL 同时查两个索引:
-
idx_user_id查到:用户 10086 的订单主键集合 → {101, 205, 308, ..., 共 200 个} -
idx_status查到:状态=1 的订单主键集合 → {..., 205, 308, 412, ...} -
取交集:两份名单里都有的主键 → 可能只有 50 个
-
只回表 50 次
执行计划长这样:
sql
EXPLAIN SELECT * FROM orders WHERE user_id = 10086 AND status = 1;
-- type: index_merge
-- key: idx_user_id, idx_status
-- Extra: Using intersect(idx_user_id, idx_status); Using where
核心收益:回表次数从 200 次降到 50 次,查询时间可能从 2 秒降到 50 毫秒。
1-3、企业级场景二:物流追踪系统(OR + Union)
业务背景
物流公司有张 packages 包裹表,数据量 1亿条。
sql
CREATE TABLE packages (
package_id BIGINT PRIMARY KEY,
tracking_no VARCHAR(50), -- 运单号
receiver_phone VARCHAR(20), -- 收件人手机号
sender_name VARCHAR(50),
INDEX idx_tracking (tracking_no), -- 单列索引1
INDEX idx_phone (receiver_phone) -- 单列索引2
);
业务需求
客服接到投诉,客户只记得运单号 或收件人手机号其中一个,系统要同时查:
"运单号是 SF123456789 或者 收件人手机号是 13800138000 的包裹"
sql
SELECT * FROM packages
WHERE tracking_no = 'SF123456789' OR receiver_phone = '13800138000';
没有 Index Merge 会怎样?
OR 条件是个老大难问题。单个索引搞不定 OR:
-
idx_tracking只能查运单号那一半 -
idx_phone只能查手机号那一半
MySQL 一看两个单列索引都搞不定 OR,直接放弃,全表扫描 1 亿条记录。客服页面直接卡死。
有了 Index Merge(Union)
MySQL 做了件聪明事:
-
查
idx_tracking:运单号 = 'SF123456789' 的主键 → {8888} -
查
idx_phone:手机号 = '13800138000' 的主键 → {6666, 8888, 9999} -
取并集(去重):{6666, 8888, 9999} → 3 条
-
只回表 3 次
执行计划:
sql
EXPLAIN SELECT * FROM packages WHERE tracking_no = 'SF123456789' OR receiver_phone = '13800138000';
-- type: index_merge
-- key: idx_tracking, idx_phone
-- Extra: Using union(idx_tracking, idx_phone); Using where
核心收益:从全表扫描 1 亿条 → 只回表 3 条。客服页面秒开。
1-4、企业级场景三:金融风控系统(OR + Sort-Union)
业务背景
银行有张 transactions 交易流水表,数据量 5亿条。
sql
CREATE TABLE transactions (
trans_id BIGINT PRIMARY KEY,
amount DECIMAL(18,2), -- 交易金额
trans_time DATETIME, -- 交易时间
risk_level TINYINT,
INDEX idx_amount (amount), -- 单列索引1
INDEX idx_time (trans_time) -- 单列索引2
);
业务需求
风控部门要筛查异常交易:
"交易金额 大于 10万 或者 交易时间在 凌晨 2:00~5:00 的交易"
sql
SELECT * FROM transactions
WHERE amount > 100000 OR trans_time BETWEEN '2024-01-01 02:00:00' AND '2024-01-01 05:00:00';
这里为什么用 Sort-Union 而不是普通 Union?
因为这两个条件都是范围查询 (> 和 BETWEEN),不是等值查询。
普通 Union 要求索引扫描出来的主键是有序的,但范围查询扫出来的主键可能不是严格有序的。所以 MySQL 要先排序,再合并。
执行流程:
-
idx_amount扫出金额>10万的主键集合 → {100, 500, 200, 800, ...} (无序) -
idx_time扫出凌晨交易的主键集合 → {300, 100, 900, ...} (无序) -
分别排序 → {100, 200, 500, 800, ...} 和 {100, 300, 900, ...}
-
取并集去重 → {100, 200, 300, 500, 800, 900, ...}
-
按主键顺序回表(还能顺便利用 MRR 优化,把随机 I/O 变顺序 I/O)
执行计划:
sql
EXPLAIN SELECT * FROM transactions WHERE amount > 100000 OR trans_time BETWEEN '...' AND '...';
-- type: index_merge
-- key: idx_amount, idx_time
-- Extra: Using sort_union(idx_amount, idx_time); Using where
1-5、但 Index Merge 不是万能的!(踩坑预警)
上面三个故事听起来很美好,但现实中 Index Merge 经常翻车,特别是 Intersection。
翻车案例:Mattermost 生产事故
Mattermost(一个开源协作平台)曾遇到过一个经典案例:
他们的 Posts 表有几百万条数据,查询:
sql
SELECT * FROM Posts WHERE ChannelId = 'xxx' AND DeleteAt = 0 AND CreateAt < z;
MySQL 选择了 Index Merge Intersection ,把 ChannelId 和 DeleteAt 两个索引做交集。
但问题是:DeleteAt = 0 表示"未删除的帖子",几乎就是全表数据!MySQL 估算错了成本,实际上扫描了 1300 万行,查询卡死。
教训 :如果某个条件过滤性极差(比如
status = 1占了 90% 的数据),Index Merge 反而比单索引或全表扫描更慢。
翻车案例:Percona 的性能测试
Percona 的测试也证明:
当两个列的数据高度相关时(比如 i1=50 和 i2=50 总是同时出现),MySQL 假设它们是独立的,估算的交集行数远小于实际值,结果选了个最差的执行计划。
给你的实战建议
| 场景 | 建议 |
|---|---|
| AND 条件 + 多个单列索引 | 优先建联合索引 (a, b),比 Index Merge 快 10 倍。Index Merge 只是"没有办法的办法"。 |
| OR 条件 + 多个单列索引 | Index Merge 是救星!因为 OR 没法走单个索引,有 Index Merge 至少比全表扫描强。 |
发现执行计划出现 index_merge 但查询很慢 |
用 EXPLAIN ANALYZE 看实际扫描行数。如果某个索引过滤性极差,用 USE INDEX(...) 强制走单索引,或关闭 index_merge_intersection: SET SESSION optimizer_switch='index_merge_intersection=off'; |
| 数据量小(< 10万行) | 不用纠结,全表扫描可能更快,MySQL 自己会选择。 |
一句话总结
Index Merge 就像是"同时翻几本索引卡片,然后把名单合并"。
对于
AND条件它帮你减少回表次数,对于OR条件它帮你避免全表扫描。但它不是银弹------当某个条件过滤性太差时,翻多本卡片的成本可能比直接翻档案柜还高。企业里最好的做法还是根据业务查询模式设计联合索引,让 Index Merge 只是兜底方案。
2、单个索引 联合索引 VS OR
假设你有一张用户表,有两个单列索引:
sql
CREATE TABLE users (
id INT PRIMARY KEY,
age INT,
city VARCHAR(20),
INDEX idx_age (age), -- 单列索引1:只按 age 排序
INDEX idx_city (city) -- 单列索引2:只按 city 排序
);
现在执行:
sql
SELECT * FROM users WHERE age = 20 OR city = '北京';
我问你:单独用 idx_age 这个索引,能不能查出所有满足条件的行?
不能。 因为 idx_age 这棵 B+Tree 里只存了 age 和主键 id 的对应关系,它根本不知道每行人的 city 是什么。
-
idx_age能告诉你:age=20 的人是 id=1, 5, 9 -
但
city='北京'的人呢?idx_age完全无能为力,只能全表扫描去猜
同理,单独用 idx_city 呢?
也搞不定。它能找到 city='北京' 的人,但对 age=20 的人一无所知。
那联合索引能搞定吗?
你肯定会想:那我建个联合索引 (age, city) 总行了吧?
sql
INDEX idx_age_city (age, city)
答案还是:搞不定这个 OR。
因为联合索引是先按 age 排序,再在相同 age 内按 city 排序:
age=18, city=上海 → id=2
age=20, city=北京 → id=1 ← 这里能找到 age=20 的
age=20, city=广州 → id=5
age=25, city=北京 → id=8 ← city=北京 的分散在不同地方!
-
age = 20可以走联合索引(最左前缀) -
但
city = '北京'在整棵树上不是全局有序的(它只在 age 相同的小范围内有序) -
所以联合索引也无法一次性 覆盖
OR的两边
联合索引能搞定的是
AND:WHERE age = 20 AND city = '北京',因为条件可以顺着树一路定位到age=20, city=北京那个分支。但OR不行。
3、index merge怎么用
Index Merge 是自动的,但你可以控制它
MySQL 的 Index Merge 默认就是开启的,不需要你手动"使用"。
优化器会自动判断:当查询满足条件时,就用;不满足时,就不用。
但你可以做三件事:观察它、控制它、优化它。
一、先看:怎么知道有没有用到 Index Merge?
用 EXPLAIN 看执行计划:
sql
EXPLAIN SELECT * FROM orders
WHERE user_id = 10086 OR status = 1;
如果用了 Index Merge,你会看到:
| 字段 | 显示内容 | 含义 |
|---|---|---|
type |
index_merge |
✅ 明确告诉你用了 Index Merge |
key |
idx_user_id,idx_status |
同时用了这两个索引 |
Extra |
Using union(idx_user_id,idx_status); Using where |
Union 合并方式 |
三种合并类型的 Extra 字样:
-
Intersection(交集,AND) :
Using intersect(idx_a,idx_b) -
Union(并集,OR) :
Using union(idx_a,idx_b) -
Sort-Union(排序并集,范围 OR) :
Using sort_union(idx_a,idx_b)
二、控制:手动开启/关闭 Index Merge
1. 全局开关(影响整个实例)
sql
-- 查看当前设置
SHOW VARIABLES LIKE 'optimizer_switch';
-- 关闭 Index Merge(不建议生产环境用)
SET GLOBAL optimizer_switch='index_merge=off,index_merge_union=off,index_merge_sort_union=off,index_merge_intersection=off';
默认都是 on,一般不要关。
2. 会话级开关(只影响当前连接)
sql
-- 当前会话关闭 intersection(解决某个查询的误判)
SET SESSION optimizer_switch='index_merge_intersection=off';
-- 用完记得恢复,或退出会话自动失效
3. SQL Hint(只影响单条查询)⭐ 最常用
MySQL 8.0 提供了 Hint 语法,可以对单条 SQL 精准控制:
sql
-- 强制使用 Index Merge(当优化器没选时)
SELECT /*+ INDEX_MERGE(orders idx_age, idx_city) */ *
FROM orders
WHERE age = 20 OR city = '北京';
-- 强制禁用 Index Merge(当优化器误选时)
SELECT /*+ NO_INDEX_MERGE(orders idx_age, idx_city) */ *
FROM orders
WHERE age = 20 OR city = '北京';
💡 企业级场景 :当你发现某条 SQL 走了 Index Merge 但很慢时,不需要改全局配置,只需在这条 SQL 上加
NO_INDEX_MERGE即可。
三、实战:什么时候该"用",什么时候该"禁"?
该让它自动用的情况
| 场景 | 示例 SQL | 原因 |
|---|---|---|
| OR + 多个单列索引 | WHERE a = 1 OR b = 2 |
单个索引搞不定 OR,Index Merge 是唯一避免全表扫描的办法 |
| AND + 单列索引 + 数据分布均匀 | WHERE a = 1 AND b = 2 |
两个条件过滤性都好,交集后回表次数极少 |
❌ 该禁用的情况(加 NO_INDEX_MERGE)
| 场景 | 原因 | 解决方案 |
|---|---|---|
| 某个条件的过滤性极差 | 比如 status = 1 占了 90% 数据,Index Merge 要扫巨量行 |
NO_INDEX_MERGE 或改用联合索引 |
| 两个索引列高度相关 | 比如 province='广东' 和 city='深圳' 几乎同时出现,MySQL 假设独立,估算错误 |
NO_INDEX_MERGE,建联合索引 (province, city) |
| 索引返回行数估算偏差大 | EXPLAIN 显示 rows 很小,实际扫了几百万行 |
用 ANALYZE TABLE 更新统计信息,或加 Hint |
四、企业级最佳实践
1. 优先用联合索引,Index Merge 只是兜底
sql
-- 不好的设计:两个单列索引
INDEX idx_age (age),
INDEX idx_city (city)
-- 好的设计:一个联合索引(如果 AND 查询多)
INDEX idx_age_city (age, city)
联合索引 (age, city) 比 Index Merge 快 5~10 倍,因为:
-
只查一棵树
-
不用合并两个索引的结果集
-
天然有序,回表更可控
2. 如果必须用 Index Merge,确保回表次数可控
sql
-- 先看两个条件各自过滤后有多少行
EXPLAIN SELECT id FROM orders WHERE age = 20; -- 看 rows
EXPLAIN SELECT id FROM orders WHERE city = '北京'; -- 看 rows
如果两个条件的 rows 乘积(Intersection)或总和(Union)超过表总行数的 20%,Index Merge 大概率不如全表扫描。
3. 定期 ANALYZE TABLE 保持统计信息准确
sql
ANALYZE TABLE orders;
MySQL 靠统计信息决定是否用 Index Merge。如果统计信息过时,优化器可能做出错误选择。
五、一句话总结
Index Merge 不用你"用",MySQL 会自动选。你要做的是:会看执行计划(
type = index_merge)、会用 Hint 控制(INDEX_MERGE/NO_INDEX_MERGE)、会判断该不该让它用(看过滤性和数据分布)。
企业里的老手不会刻意追求 Index Merge,而是把联合索引设计好,让 Index Merge 只在"联合索引覆盖不了"的 OR 场景下默默兜底。