滚动新闻 →
Windows 11 家庭安全设置怎么用?儿童设备管理需要掌握哪些功能 欧洲青年示威为何走向暴力 极左翼势力渗透与共产主义政治影响值得警惕 波兰宪法法院危机背后的美欧政治角力 左右派争夺国家未来 波兰宪法法院风波敲响警钟 左翼政府与国家主权的正面冲突 中国商务部拟出台商业秘密保护新规 招商与管制政策面临挑战 韦伯望远镜揭示N159恒星诞生区的剧烈演化过程 MySQL 联合索引怎么设计?多条件查询中的索引顺序非常重要 万圣节期间多地执法引争议 上海市民围警车抗议 美日印澳构建非中国供应链体系 针对性应对关键矿产限制 傅成玉退休十一年后被查 石油系统旧账与中共高层权力博弈再受关注 从古诗词看古人的离别文化 波兰司法危机背后的政治路线之争 纳沃罗茨基与图斯克正面交锋 “徽剑案”疑点重重 多名资深律师质疑法院判决 法国岩洞壁画3万年保存完好 或因方解石层保护 2026年11月维权事件追踪:退休金被停发军嫂就医困境 美伊军事行动未定,卢比奥称谈判仍在进行中 传统中医与中国茶文化的关系 欧洲多国示威升级为暴力冲突,极端分子渗透引关注 欧洲多国博物馆频遭盗窃 文物安全引担忧 万圣节期间中国多地出现执法行动 海外华人抗议活动引关注 保加利亚总统选举第二轮:国家利益与欧洲立场之争 前美军顾问警告:中共黑客威胁升级至军事层面 古代文人为什么重视书房环境 胡歌自曝好友因心梗离世 现随身携带救心丸 FBI指控朝鲜参与63亿美元加密货币盗窃案 如何让孩子真正理解道歉的意义 土耳其研究揭示塑胶茶包热水冲泡释放大量微塑胶颗粒 新手剪视频应该从哪里开始 美驻俄大使馆预警莫斯科恐遭恐袭 俄景点紧急关闭 鸦片战争为什么会爆发 中共中央强化党内民主规范 引发对权力集中模式的讨论 网购侏儒猪失控长至200斤 女子带其散步仅限无人区 买家具之前为什么一定要测量空间 波兰或内战?前总统:89年后没发生过 火锅如何体现中国人的社交文化 印度希尔顿酒店泳池漏电致两名外国游客溺亡 如果喜欢安静的海边小镇,加拿大坎贝尔里弗值得慢慢住几晚 余茂春评姆巴佩事件:到底谁伤害中国人感情 节气门脏了会影响怠速吗 葡萄牙警方强拆里斯本抗议营地 要求撤回租屋法案

MySQL 联合索引怎么设计?多条件查询中的索引顺序非常重要

发布时间: 2026-11-01 13:00:02    最后更新: 2026-11-01 14:23:34    阅读:15  约18 分钟阅读     

MySQL 联合索引怎么设计?多条件查询中的索引顺序非常重要
图片说明:示意图   图片来源:Public Domain(公有领域)
在SaaS系统、订单系统和后台管理系统中,一个查询往往同时带有租户、状态、用户、时间和排序条件。这个时候,单列索引往往不够用,联合索引就成为数据库性能优化的重要工具。

但联合索引最容易被误解的地方,恰恰是“索引顺序”。很多教程喜欢总结成“高选择性字段放前面”或者“查询频率最高的字段放最前面”,这些说法可以作为思考方向,却不能当成固定规则。真正合理的索引设计,需要从实际SQL、过滤条件、范围查询、ORDER BY、数据分布以及写入成本一起判断。

一、联合索引到底是怎样工作的

假设建立一个联合索引:

CREATE INDEX idx_user_status_time
ON orders (user_id, status, create_time);

对于InnoDB常见的B+Tree索引结构,可以把它理解成按照 (user_id, status, create_time) 这个组合键进行有序排列。MySQL官方文档明确说明,联合索引可以用于索引中最左侧的一列、前两列或者全部列。例如 (a,b,c) 可以形成 (a)、(a,b) 和 (a,b,c) 这些左侧前缀的索引查找能力。

因此下面三个查询具有不同程度的索引利用能力:

WHERE user_id = 1001
WHERE user_id = 1001
AND status = 'paid'
WHERE user_id = 1001
AND status = 'paid'
AND create_time >= '2026-01-01'

而:

WHERE status = 'paid'

不能把 (user_id,status,create_time) 当成一个以 status 开头的索引来进行普通的左侧前缀查找。原因并不是“status字段完全不能参与过滤”,而是索引树首先按照 user_id 排序,单独寻找所有 status='paid' 的记录无法形成一个简单的连续索引区间。

这里还有一个非常重要的细节:SQL中的条件书写顺序与索引定义顺序不是一回事。

例如:

WHERE user_id = 1001
AND status = 'paid'

和:

WHERE status = 'paid'
AND user_id = 1001

逻辑上相同,优化器不会因为你把 status 写在前面,就要求索引必须是 (status,user_id)。真正决定索引能否形成有效访问路径的是条件与索引列之间的关系,而不是WHERE子句文字排列顺序。

二、联合索引顺序不能简单套用“选择性越高越靠前”

“高选择性字段放最左边”是非常常见的一句话,但如果把它当成绝对规则,很容易设计出并不适合实际业务的索引。

假设:

SELECT *
FROM orders
WHERE tenant_id = 100
AND status = 'paid'
AND create_time >= '2026-01-01';

一种很常见的设计是:

CREATE INDEX idx_orders
ON orders (tenant_id, status, create_time);

如果SaaS系统的大多数查询都是围绕租户进行,而且查询经常同时按照状态和时间过滤,那么这个顺序非常自然。

如果把 create_time 放在最前面:

(create_time, tenant_id, status)

当然不是说索引“完全失效”。时间范围仍然可以形成索引范围扫描,但后面的tenant_id和status能否继续缩小索引扫描范围、能否帮助排序等,需要根据具体查询和优化器执行计划判断。多列索引的范围访问本身就是一个成本优化问题,不能简单理解成“后面的列全部不能用”。

所以更实用的思路是:先找出业务中最稳定、最常见的查询形态,再决定索引顺序。

三、等值条件和范围条件通常是设计联合索引的关键

在很多典型查询中,可以优先考虑把稳定的等值过滤条件放在前面,再考虑范围条件。

例如:

SELECT id, user_id, status, create_time
FROM orders
WHERE tenant_id = 100
AND status = 'paid'
AND create_time >= '2026-01-01'
AND create_time < '2026-02-01';

可以考虑:

CREATE INDEX idx_orders
ON orders (tenant_id, status, create_time);

这里tenant_id和status都是等值条件,create_time是范围条件。

这种结构通常能够先把索引搜索范围缩小到某个租户、某种状态,再在对应的索引范围内进行时间扫描。

但是,“等值字段一定要按照选择性从高到低排列”同样不是绝对规则。

例如:

(tenant_id, status, create_time)

和:

(status, tenant_id, create_time)

在某个特定SQL中可能产生相似的访问路径,而在其他SQL中却完全不同。

如果系统还有大量查询:

WHERE tenant_id = ?

那么以tenant_id开头的索引可能可以服务更多查询。

这就是为什么联合索引设计不能脱离SQL列表单独讨论。

四、一个联合索引可以服务多个查询,但不能什么都兼顾

例如:

CREATE INDEX idx_orders
ON orders (tenant_id, status, create_time);

它可能同时帮助:

WHERE tenant_id = ?
WHERE tenant_id = ?
AND status = ?
WHERE tenant_id = ?
AND status = ?
AND create_time >= ?

但如果业务中还有大量:

WHERE status = ?

那么这套索引并不能自动替代一个以status开头的索引。

因此设计索引时不能只看某一条SQL,而应该把同一张表的重要查询模式放在一起分析。MySQL官方也建议建立一组能够服务多个相关查询的索引,而不是为每一个查询都建立一个新索引。

五、范围条件之后的列要特别小心

这是联合索引设计中非常容易踩坑的地方。

例如:

SELECT *
FROM orders
WHERE user_id = 1001
AND create_time >= '2026-01-01'
ORDER BY update_time;

有人可能直接设计:

CREATE INDEX idx_orders
ON orders (user_id, create_time, update_time);

然后认为这个索引同时完成了user_id过滤、create_time范围过滤以及update_time排序。

这种理解并不准确。

因为索引的排序顺序是:

user_id → create_time → update_time

当create_time已经进入一个范围后,update_time并不是在整个扫描范围内保持一个可以直接满足ORDER BY update_time的全局连续顺序。因此不能简单认为“把ORDER BY字段放在范围字段后面,就自动消除排序”。

如果排序非常重要,就必须把实际SQL、WHERE条件、ORDER BY、LIMIT以及数据分布一起放进EXPLAIN或EXPLAIN ANALYZE验证。

MySQL的优化器可以采用索引排序、范围扫描、额外排序等不同方案,最终执行计划才是判断依据。

六、等值条件下,索引顺序往往比想象中更灵活

例如:

WHERE user_id = 1001
AND status = 'paid'

下面两个索引在这个特定查询中都可能有效:

(user_id, status)

以及:

(status, user_id)

因为两个字段都是等值条件。

但如果还有:

WHERE user_id = 1001

那么第一种索引明显具有额外价值,因为user_id是它的最左前缀。

所以索引顺序真正重要的地方,不是“数据库要求WHERE里的字段必须按照索引顺序写”,而是要考虑一组查询能否共享同一个索引。

七、OR并不意味着索引一定失效

原稿中这一说法需要纠正。

例如:

SELECT *
FROM users
WHERE id = 1
OR name = 'Alice';

不能简单得出“OR导致索引失效”。

MySQL存在Index Merge等优化方式,在特定条件下可以分别利用不同索引,然后合并结果。官方文档也明确提到,对于多个单列索引,优化器可能选择Index Merge,或者选择能够排除更多行的索引。

当然,OR查询是否真的高效仍然要看数据量、选择性、索引结构以及实际执行计划。

因此看到OR以后,正确做法不是立即删掉索引,而是:

EXPLAIN
SELECT ...

必要时进一步使用:

EXPLAIN ANALYZE
SELECT ...

看真实执行过程。

八、函数作用在索引列上确实需要特别注意

例如:

WHERE YEAR(create_time) = 2023

传统的普通索引:

INDEX(create_time)

通常无法像下面这种范围条件一样直接利用create_time的连续范围:

WHERE create_time >= '2023-01-01'
AND create_time < '2024-01-01'

后者通常更适合普通B+Tree索引的范围访问。MySQL官方文档也将范围条件作为B-tree索引的重要访问方式。

如果业务确实需要对表达式进行索引,MySQL新版本也支持functional key parts,在适合的场景下可以针对表达式建立索引,而不是只能依靠普通列索引。

九、字符串和数字之间的类型转换不能简单说成“索引失效”

例如:

WHERE id = '123'

如果id是INT,不能直接得出“索引一定失效”。

MySQL会进行类型转换,最终是否采用索引、采用什么访问方式,要看具体数据类型、表达式以及执行计划。

更重要的工程原则是:应用层应该尽量保持参数类型与数据库字段类型一致。

例如数据库定义:

id BIGINT

应用程序就应该把它作为整数ID处理,而不是长期依赖隐式类型转换。

这样既减少歧义,也有利于保持稳定的查询行为。

十、覆盖索引不是“联合索引的升级版”

假设:

CREATE INDEX idx_user
ON users (user_id, name, email);

查询:

SELECT name, email
FROM users
WHERE user_id = 1001;

如果执行计划能够实现Index Only Scan,也就是所需字段已经可以从索引中取得,那么就可能减少访问聚簇记录的需要。

这就是覆盖索引的价值。

但是覆盖索引也有成本。

如果为了覆盖查询而把十几个字段全部塞进索引:

(user_id, name, email, phone, address, ...)

索引会明显变宽,磁盘空间、Buffer Pool占用以及INSERT、UPDATE、DELETE维护成本都会增加。

所以覆盖索引应该针对高频、关键查询设计,而不是“只要能覆盖就把所有字段都加进去”。

MySQL官方同样强调,索引并不是越多越好,因为每个索引都会产生存储和写入维护成本。

十一、Index Condition Pushdown不要和“索引能使用”混为一谈

原稿中把:

(user_id, status)

配合:

WHERE user_id = 1001
AND status = 'paid'

直接称为Index Condition Pushdown示例,并不准确。

这个查询本身已经可以直接利用联合索引进行有效查找。ICP是MySQL在特定访问路径下,将部分索引条件进一步下推到存储引擎层过滤,以减少不必要的记录读取,并不是“只要联合索引有两个字段就叫ICP”。

因此,在实际排查时不要看到一个多列索引就把所有优化都归类为“索引下推”。应该通过执行计划判断优化器到底采用了什么访问方式。

十二、ANALYZE TABLE是更新统计信息,不是“整理索引”

联合索引设计完成以后,可以根据实际情况执行:

ANALYZE TABLE orders;

它的主要作用是帮助优化器获得更准确的索引统计信息,从而改善执行计划选择。MySQL官方也建议在某些InnoDB索引创建后更新统计信息。

但不要把:

ANALYZE TABLE

理解成“定期整理索引”。

同样,也不要把:

OPTIMIZE TABLE

当成日常索引保养工具。两者解决的问题不同。

十三、真正判断联合索引是否设计正确,要看执行计划

一个索引名字看起来再漂亮,也不能证明设计正确。

实际应该观察:

EXPLAIN
SELECT ...

以及在适合的生产或测试条件下:

EXPLAIN ANALYZE
SELECT ...

重点关注的并不是某一个字段变成绿色就算成功,而是整个执行过程,包括访问类型、实际扫描行数、估算行数、使用的索引、过滤情况、排序、临时表以及JOIN过程。

例如:

key
possible_keys
key_len
rows
filtered
Extra

都需要结合SQL和数据规模解释。

Using filesort也不等于“数据库性能一定很差”,key=NULL也不等于“索引一定失效”。优化器有时判断全表扫描比使用索引更便宜,尤其是表很小或者查询需要读取大量记录时。

因此,索引优化的目标不是让EXPLAIN看起来“全部使用索引”,而是让实际查询成本下降。

十四、SaaS系统尤其要考虑tenant_id

对于多租户SaaS系统,很多业务查询天然带有:

WHERE tenant_id = ?

这种情况下,tenant_id经常成为联合索引设计的重要组成部分,例如:

CREATE INDEX idx_orders_tenant_status_time
ON orders (tenant_id, status, create_time);

但也不能机械地给所有索引都加tenant_id。

如果某些后台任务本身按照订单ID、全局唯一编号或者时间范围处理全部租户数据,那么强行把tenant_id放进所有索引反而可能增加索引数量和宽度。

更需要注意的是,tenant_id进入索引只解决性能和访问路径问题,并不能自动解决租户数据隔离。真正的SaaS隔离还必须覆盖ORM、Repository、后台任务、缓存、消息队列以及权限检查。

十五、联合索引设计最实用的方法不是背口诀,而是整理查询矩阵

如果一张orders表有下面几类核心SQL:

tenant_id
tenant_id + status
tenant_id + status + create_time范围
tenant_id + user_id
order_no
tenant_id + create_time + LIMIT

那么应该先把这些查询按照频率、延迟、数据量和业务重要程度整理出来,再寻找能够覆盖多个查询的索引组合。

例如:

INDEX (tenant_id, status, create_time)

可能服务前三种查询。

而:

UNIQUE INDEX (order_no)

则解决订单号精确查找。

如果还存在:

WHERE tenant_id = ?
AND user_id = ?

再判断现有索引是否已经能够有效支持,还是确实需要:

INDEX (tenant_id, user_id)

这比按照“每个WHERE字段建立一个索引”的方式更加可靠。

联合索引设计最终不是一道数学题,也没有一个适用于所有系统的“最高选择性字段优先”公式。真正决定索引顺序的是业务查询模式:哪些条件长期稳定存在,哪些是等值匹配,哪些是范围查询,是否需要排序或分页,哪些查询最重要,以及一个索引能否服务多条核心SQL。

对于SaaS数据库,最危险的不是少建一个索引,而是在没有分析真实SQL的情况下不断加索引。索引越多,写入和更新成本越高,优化器需要考虑的访问路径也越多。MySQL官方同样强调,应当建立一组能够有效服务相关查询的索引,而不是为每一种可能的查询组合无限增加索引。

真正成熟的联合索引优化流程应该是:先收集慢查询和核心SQL,再根据查询模式设计候选索引,然后用EXPLAIN和EXPLAIN ANALYZE验证,最后结合CPU、I/O、Buffer Pool、锁等待、扫描行数以及写入成本观察上线后的实际效果。索引不是装饰品,也不是越多越安全,它本质上是为真实访问路径服务的数据结构。

喜欢这篇报道?

使用下面的功能,方便以后继续阅读和分享 MNewsTV

设为 Google 新闻首选来源 让 Google 新闻优先显示 MNewsTV 的最新报道 ›
★ 我的收藏 查看已经收藏的文章
关于文章收藏 收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。 删除收藏请进入「我的收藏」进行管理。
分享这篇报道

捐助(Paypal): https://www.paypal.me/observeccp
订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP