我是小耶,干运营半路出家的野生DBA------写功课只是为了我踩过的坑,你们别再踩了!
今天不搞虚的,直接上实验:一张 5000 万行的订单表,对比无索引和有索引的查询性能。
实验环境 :MySQL 8.0,innodb_buffer_pool_size 默认配置,订单表结构含 user_id、order_date、amount 等字段。
1. 无索引查询
ini
SELECT * FROM orders WHERE user_id = 123456;
耗时 45 秒。CPU 飙升,应用超时。
2. 添加单列索引
sql
ALTER TABLE orders ADD INDEX idx_user_id (user_id);
再次执行相同查询:0.02 秒 。性能提升 2250 倍。
原因:InnoDB 默认使用 B+ 树索引。没有索引时,执行全表扫描,逐行读取 5000 万条数据;有索引后,通过 B+ 树进行二分查找,树高通常为 3~4 层,每次查询只需 3~4 次磁盘 I/O,所以速度极快。
什么情况下应该建索引?
WHERE条件中频繁出现的列JOIN的关联列ORDER BY或GROUP BY的列
什么情况下不应建索引?
- 列的区分度极低(例如
gender只有男/女)。索引无法有效过滤数据,反而增加维护成本。 - 频繁更新的列(
UPDATE会同时更新索引,写性能下降)。 - 很少作为查询条件的列(索引占空间但无收益)。
进阶1:联合索引与最左前缀原则
如果经常同时按 user_id 和 order_date 查询,可以建联合索引:
sql
ALTER TABLE orders ADD INDEX idx_user_date (user_id, order_date);
联合索引的 B+ 树先按第一列排序,再按第二列排序。查询条件必须从索引最左侧开始连续匹配,才能使用索引。
- ✅
WHERE user_id = 123------ 可用 - ✅
WHERE user_id = 123 AND order_date > '2026-01-01'------ 可用 - ❌
WHERE order_date > '2026-01-01'------ 无法使用,因为第一列缺失 - ❌
WHERE user_id = 123 AND amount > 100------ 只能用到user_id部分,amount在索引中不连续
如何确定联合索引的列顺序?
- 等值查询的列放前面 ,范围查询的列放后面。
- 例如
WHERE user_id = 123 AND order_date > '2026-01-01',索引(user_id, order_date)最优。 - 如果有多个等值条件,把区分度高的列放左边。
进阶2:覆盖索引
如果 SELECT 的列全部包含在索引中,InnoDB 可以直接从索引返回数据,无需回表,性能进一步提升。
ini
CREATE INDEX idx_user_date_amount ON orders (user_id, order_date, amount);
SELECT user_id, order_date, amount FROM orders WHERE user_id = 123;
执行 EXPLAIN 会在 Extra 列显示 Using index。覆盖索引能减少大量 I/O,尤其适合统计类查询。
常见错误:隐式类型转换导致索引失效
如果索引列是字符串类型,但查询时传入数字,MySQL 会对列做隐式转换,导致索引失效。
sql
-- phone 是 varchar,正确做法是加引号
SELECT * FROM users WHERE phone = '13800000000'; -- 走索引
SELECT * FROM users WHERE phone = 13800000000; -- 不走索引
小结
索引是数据库性能优化的核心手段,但不是越多越好。建议:
- 对高频查询的
WHERE、JOIN、ORDER BY列建索引。 - 联合索引遵循最左前缀原则,范围查询列放最后。
- 尽量使用覆盖索引避免回表。
- 注意隐式类型转换、函数操作对索引的影响。
你可以花 10 分钟检查下业务核心表的慢查询日志,看看哪些查询缺少合适的索引。往往一两处优化就能让整个系统性能翻倍。
小耶在手,SQL 不愁。
你遇到过"建了索引还是慢"的情况吗?发出来我帮你分析分析~