数据库索引有什么作用?什么场景必须加索引?百万行数据查询差别有多大?
数据库索引是后端开发、数据库优化中最基础也最重要的知识点之一。
很多人知道"索引能加速查询",但到底为什么快、哪些场景必须建索引、百万行数据时差距有多大,并不一定真正搞清楚。
这篇文章就从原理、场景、实战角度,把数据库索引讲清楚。
索引是什么?
一句话理解:
索引就是帮助数据库快速定位数据的数据结构。
你可以把它类比成一本书的目录:
- 没有索引:只能一页一页翻,直到找到内容
- 有索引:先查目录,再直接定位到目标页
数据库也是一样。
比如下面这条 SQL:
sql
select * from user where phone = '13800138000';
如果 phone 字段没有索引,数据库可能需要把整张表一行一行扫过去,找到符合条件的数据;
如果 phone 字段有索引,就可以更快地定位到目标记录。
二、索引到底有什么作用?
很多人只知道"索引能提高查询速度",其实它的作用不止这一点。
1. 提高查询速度
这是索引最核心的作用。
例如:
select * from user where email = 'test@example.com';
如果 email 上有索引,数据库能快速定位;没有索引,就可能要扫描全表。
2. 加速排序
例如:
select * from orders order by create_time desc limit 10;
如果 create_time 上有索引,数据库在很多情况下可以直接利用索引顺序返回数据,而不用额外对大量数据排序。
3. 加速分组、去重、关联查询
例如:
group by
distinct
join
这些操作本质上都涉及大量数据的比较和筛选,索引可以有效降低处理成本。
比如这条 SQL:
select * from orders o
join users u on o.user_id = u.id;
如果 o.user_id 和 u.id 上都有索引,关联性能通常会好很多。
4. 保证唯一性
比如用户手机号、邮箱、订单号这类字段,很多时候既要求查询快,又要求不能重复。
这时通常会用 唯一索引:
create unique index idx_user_phone on user(phone);
这样数据库既能快速查找,又能保证数据唯一。
三、索引为什么会快?
要理解索引为什么快,关键是要知道:数据库并不是把数据简单地顺序扫描,而是会借助特定的数据结构来提升查找效率。
常见的索引结构有:
B+ 树索引
Hash 索引
倒排索引
在关系型数据库里,最常见的是 B+ 树索引,尤其是 MySQL 的 InnoDB。
B+ 树索引的特点是:
查询效率高
适合范围查询
适合排序
层级少,磁盘 I/O 少
所以数据库可以通过索引快速缩小查找范围,而不是把所有数据都扫一遍。
四、什么场景下必须加索引?
不是所有字段都要加索引,但下面这些场景,基本上必须认真考虑。
1. 高频出现在 where 条件中的字段
例如:
select * from orders where user_id = 1001;
select * from users where email = 'a@test.com';
select * from orders where status = 'paid';
如果某个字段经常出现在查询条件里,并且表数据量不小,就应该优先考虑建索引。
2. 用于 join 的字段
例如:
select * from orders o
join users u on o.user_id = u.id;
这里:
users.id 一般本来就是主键索引
orders.user_id 通常也应该加索引
否则在数据量大时,关联查询会明显变慢。
3. 经常用于排序或分组的字段
例如:
select * from logs order by create_time desc limit 20;
select status, count(*) from orders group by status;
这类字段如果在业务中频繁使用,也通常适合建索引。
4. 需要唯一约束的字段
比如:
手机号
邮箱
身份证号
订单号
这类字段一般建议建立 唯一索引,既保证数据规范,也提升查询效率。
5. 大表中的高频查询字段
如果一张表已经有几十万、几百万甚至更多数据,而某个字段又是高频查询条件,那么几乎一定要考虑索引。
6. 常用于分页过滤的字段
例如:
select * from orders
where user_id = 1001
order by id desc
limit 20;
这类场景如果没有索引,到了后期性能问题会非常明显。
五、哪些场景不适合乱建索引?
很多新人有个误区:既然索引能加速查询,那是不是每个字段都建一个索引?
答案当然不是。
索引有明显代价,乱建索引反而会拖垮系统。
1. 小表没必要建太多索引
如果一张表只有几十行、几百行数据,全表扫描本身就很快,加索引意义不大。
2. 区分度很低的字段不适合单独建索引
例如:
性别:男 / 女
是否删除:0 / 1
状态字段只有几个值
这些字段的可选值太少,命中数据太多,即使建索引,效果也未必好。
例如:
select * from user where gender = '男';
如果全表 50% 都是男性,这种索引帮助很有限。
3. 更新非常频繁的字段不适合建太多索引
索引不是只影响查询的。
每次 insert、update、delete,数据库都要维护索引。
所以如果某张表写多查少,索引太多会明显影响写入性能。
4. 很少使用的字段没必要建索引
如果一年只查几次,就没必要为了这几次查询增加长期维护成本。
六、百万行数据时,加索引和不加索引差别有多大?
很多人平时开发在测试环境里感受不到索引价值,因为数据量太小。
但一旦到了百万行、千万行,差距就非常明显了。
不加索引:全表扫描
假设表里有 100 万行数据:
select * from user where phone = '13800138000';
如果 phone 没有索引,数据库通常只能:
从第一行开始扫描
一行一行判断是否满足条件
直到找到结果或者扫完整张表
这就是 全表扫描。
在 MySQL 的执行计划里,通常可以看到:
type = ALL
这表示数据库扫描了整张表。
加索引:快速定位
如果给 phone 建立索引:
create index idx_phone on user(phone);
数据库就可以通过索引快速找到目标数据,而不是扫描全部 100 万行。
执行计划里通常会看到:
type = ref
type = const
type = range
这说明索引被用上了。
性能差距通常是数量级的差距
在百万级数据量下:
不加索引:可能几十毫秒、几百毫秒,甚至秒级
加索引:可能几毫秒内完成
虽然具体数值和硬件、SQL 写法、返回行数有关,但总体上看:
百万行数据时,索引往往不是"优化",而是"能不能接受"的分水岭。
七、一个更直观的例子
假设有一张订单表:
create table orders (
id bigint primary key,
user_id bigint,
status varchar(20),
amount decimal(10,2),
create_time datetime
);
表中有 100 万行数据。
场景 1:按用户查询订单
select * from orders where user_id = 123456;
没有索引
数据库需要扫描整张表,逐行判断 user_id 是否等于 123456。
有索引
create index idx_user_id on orders(user_id);
数据库可以直接通过索引定位这个用户的订单。
如果这个用户只有 10 条订单,那么数据库实际需要处理的数据量就少得多。
场景 2:查询最近 20 条订单
select * from orders order by create_time desc limit 20;
没有索引
数据库可能需要:
扫描整张表
排序 100 万行数据
再取前 20 条
有索引
create index idx_create_time on orders(create_time);
数据库可以按索引顺序直接拿到最近 20 条数据,速度会快很多。
场景 3:查询某个用户最近 20 条订单
select * from orders
where user_id = 123456
order by create_time desc
limit 20;
这种场景下,单列索引不一定最优,更适合建 联合索引:
create index idx_user_ctime on orders(user_id, create_time);
这样数据库既能按 user_id 过滤,又能按 create_time 排序,性能通常更好。
八、索引的代价是什么?
索引很好用,但不是免费的。
1. 占用额外存储空间
索引本身也是要存储的,索引越多,占用空间越大。
2. 会降低写入性能
每次新增、修改、删除数据时,数据库不只要修改表本身,还要维护相关索引。
也就是说:
索引越多
写入越慢
3. 会增加系统维护复杂度
索引太多还会带来这些问题:
容易出现冗余索引
执行计划更复杂
DDL 变更更重
有些索引建了却根本没用上
所以索引一定要按业务场景合理设计,而不是越多越好。
九、为什么有时候建了索引也没用?
这是实际开发中特别常见的坑。
你明明建了索引,但 SQL 还是慢,为什么?
因为:
索引建了,不代表数据库一定会使用。
常见导致索引失效的情况
1. 对索引列做函数操作
例如:
select * from user where date(create_time) = '2026-04-14';
这种写法可能导致索引失效。
更推荐写成:
select * from user
where create_time >= '2026-04-14 00:00:00'
and create_time < '2026-04-15 00:00:00';
2. 模糊查询以 % 开头
例如:
where name like '%张三%'
这种写法通常无法使用普通 B+ 树索引。
而下面这种写法通常可以:
where name like '张%'
3. 隐式类型转换
比如字段是数字类型,但你用字符串去查,在某些情况下会导致索引无法正常使用。
4. 联合索引不符合最左前缀原则
例如:
create index idx_a_b_c on t(a, b, c);
它通常支持以下查询:
where a = ?
where a = ? and b = ?
where a = ? and b = ? and c = ?
但以下查询往往用不上这个联合索引:
where b = ?
where c = ?
十、怎么判断 SQL 是否真正用到了索引?
在 MySQL 中,可以使用 EXPLAIN 查看执行计划:
explain select * from orders where user_id = 123456;
重点看几个字段:
type:访问类型,越接近 const、ref 越好,ALL 往往表示全表扫描
key:实际使用了哪个索引
rows:预计扫描的行数
extra:补充信息,比如是否回表、是否排序等
如果索引建了但 key 为空,或者 type = ALL,那通常说明这条 SQL 没真正用上索引。
十一、实战中的几个建议
1. 不要拍脑袋建索引
索引应该基于真实的查询场景设计,而不是凭感觉加。
2. 优先看慢 SQL
先找出真正慢、真正频繁的 SQL,再决定索引策略。
3. 优先考虑联合索引
如果业务查询经常是这样的:
where user_id = ? and status = ? order by create_time desc
那通常更适合建联合索引:
(user_id, status, create_time)
而不是分别建立 3 个单列索引。
4. 优先选择区分度高的字段建索引
例如:
手机号
邮箱
订单号
通常比以下字段更适合建索引:
性别
是否删除
5. 不要给每个字段都加索引
这会导致:
写入变慢
空间浪费
优化器更难做选择
更容易出现冗余索引
十二、总结
最后把核心结论总结一下。
索引的作用
提高查询速度
加速排序、分组、关联
保证唯一性
必须优先考虑加索引的场景
高频出现在 where 中的字段
join 字段
order by / group by 字段
需要唯一约束的字段
大表中的高频查询字段
百万行数据时区别有多大?
不加索引:常常是全表扫描,查询可能很慢
加索引:能快速定位,性能往往提升非常明显
但索引也不是越多越好
因为它会:
占空间
拖慢写入
增加维护成本
所以最好的做法不是"多建索引",而是:
根据真实业务查询场景,设计合适的索引。
结语
索引本质上不是一个"会不会"的问题,而是一个"怎么合理设计"的问题。
很多系统在数据量小的时候问题不明显,但一旦数据上来,索引设计不合理带来的性能问题会迅速放大。
因此,真正重要的不是背几个概念,而是学会结合 SQL、数据量、查询模式去判断应该怎么建索引。