【MySQL】 索引、联合索引以及大数据量分批查询问题总结

📅 2026/8/1 11:23:10 👁️ 阅读次数 📝 编程学习
【MySQL】 索引、联合索引以及大数据量分批查询问题总结

MySQL 索引、联合索引以及大数据量分批查询问题总结

1. 索引到底是干什么的?

很多人理解索引:

加索引以后,查询就一定快。

这个理解是不准确的。

索引真正的作用:

帮助数据库快速定位数据,减少需要扫描的数据量。


例如一张表:

用户表 1000万条数据

查询:

SELECT*FROMuserWHEREuser_id=10086;

如果没有索引,数据库只能:

第1条 第2条 第3条 ... 第1000万条

一条一条判断。

这种叫:

全表扫描(Full Table Scan)

如果建立索引:

CREATEINDEXidx_user_idONuser(user_id);

查询:

SELECT*FROMuserWHEREuser_id=10086;

数据库可以:

索引 ↓ 快速定位 user_id=10086 ↓ 找到对应数据

不用扫描全部数据。

所以:

索引的本质,是减少扫描的数据量。


2. 索引是不是用了就一定快?

不是。

很多人的误区:

使用索引 = 一定快

实际上:

使用索引 ↓ 扫描数据少 ↓ 才会快

如果使用索引以后,仍然需要扫描大量数据,也可能很慢。


例如:

表:

订单表 1亿条数据

建立:

CREATEINDEXidx_create_timeONorder_table(create_time);

查询:

SELECT*FROMorder_tableWHEREcreate_time>='2026-01-01';

执行计划:

type: range

表示:

走了索引范围查询

但是:

如果符合条件的数据:

5000万条

那么数据库仍然需要:

通过索引找到开始位置 ↓ 扫描5000万条数据

所以:

虽然:

走索引

但是:

扫描范围太大

仍然可能慢。


3. range 实际上是走了索引,对吧?

对。

例如:

SELECT*FROMorder_tableWHEREcreate_time>='2026-07-01';

如果 create_time 有索引:

执行计划:

type: range

表示:

索引范围扫描

例如:

索引:

2026-01 2026-02 2026-03 ... 2026-07

数据库:

找到2026-07的位置 ↓ 继续向后扫描

所以:

range = 走索引。

但是:

range 不代表一定快。

还要看扫描的数据量。


4. 普通索引和联合索引有什么区别?

4.1 普通索引

例如:

CREATEINDEXidx_nameONuser(name);

表示:

给一个字段建立索引。

查询:

SELECT*FROMuserWHEREname='张三';

可以使用这个索引。


4.2 联合索引

例如:

CREATEINDEXidx_name_ageONuser(name,age);

表示:

多个字段组成一个索引。

例如:

数据:

张三 18 张三 20 李四 25 王五 30

索引结构类似:

name | +-- age

联合索引遵循:

最左匹配原则

索引:

(name,age)

可以支持:

WHEREname='张三'

也可以支持:

WHEREname='张三'ANDage=18

但是:

WHEREage=18

通常不能有效使用。

原因:

联合索引首先按照:

name

排序。

没有 name,数据库不知道从哪里开始查 age。


5. 为什么增加 id > 0 后扫描量变多?

假设:

表:

业务数据表 100万条数据

其中:

id 是主键

数据:

id 1 2 3 4 ... 1000000

查询:

SELECT*FROMAWHEREstatus='完成'ORDERBYidLIMIT1000;

可能执行:

根据status索引找到符合数据 ↓ 排序id ↓ 返回1000条

现在增加:

ANDid>0

变成:

SELECT*FROMAWHEREstatus='完成'ANDid>0ORDERBYidLIMIT1000;

问题:

id > 0

对于主键来说:

基本等于:

所有数据

过滤效果很低。


但是同时:

存在:

ORDERBYidLIMIT1000

而:

主键天然按照id排序

所以 MySQL 可能认为:

直接扫描主键 不用额外排序 找到1000条停止

于是执行:

PRIMARY KEY扫描 id=1 判断条件 id=2 判断条件 id=3 判断条件 ... 找到1000条

6. 为什么主键扫描反而慢?

例如:

总数据:

100万条

符合条件:

5000条

如果走业务索引:

先找到符合条件的数据 扫描5000条

如果走主键:

id=1 不符合 id=2 不符合 id=3 不符合 ... 扫描20万条 才找到1000条

虽然:

使用了索引

但是:

扫描更多数据

所以仍然慢。


7. LIMIT 到底是什么执行逻辑?

例如:

表:

1000条数据

符合条件:

200条

SQL:

SELECT*FROMAWHERE条件LIMIT100;

是不是:

先查询100条 然后过滤?

不是。

逻辑顺序:

FROM ↓ WHERE过滤 ↓ ORDER BY排序 ↓ LIMIT限制

实际逻辑:

1000条数据 ↓ 过滤 ↓ 剩余200条 ↓ 取前100条

但是实际执行时:

数据库可能提前停止。

例如:

扫描数据 找到第1条符合 找到第2条符合 ... 找到第100条符合 停止

因为已经满足 LIMIT。


8. 大数据量为什么需要分批查询?

假设:

5000万条数据

一次查询:

SELECT*FROMA;

问题:

  • 内存压力大
  • 查询时间长
  • 数据处理慢

所以需要:

每次处理1000条

9. 为什么使用 id > 上一次ID?

不要使用:

LIMIT100000,1000

因为:

页数越大:

扫描越多。

例如:

第一页:

LIMIT0,1000

扫描:

1000条

第10000页:

LIMIT10000000,1000

数据库需要:

扫描10001000条 丢弃前10000000条 返回1000条

推荐:

游标分页:

第一次:

SELECT*FROMAWHERE条件ORDERBYidLIMIT1000;

得到:

最后id=5000

第二次:

SELECT*FROMAWHERE条件ANDid>5000ORDERBYidLIMIT1000;

继续。


10. 那 id > 0 这种方式有什么问题?

分页思想没有问题。

问题是:

第一次:

id>0

范围太大。

同时:

ORDERBYid

容易让 MySQL 选择:

主键扫描

而不是:

业务条件索引过滤

正确方式:

后续:

id>上一次最大id

是合理的。


11. 联合索引如何帮助分页查询?

假设:

SQL:

SELECT*FROMAWHEREstatus='完成'ANDtype='运输'ANDid>?ORDERBYidLIMIT1000;

如果只有:

status索引

数据库可能:

先过滤status ↓ 再过滤type ↓ 再排序id

效率一般。


可以建立:

CREATEINDEXidx_status_type_idONA(status,type,id);

这样:

索引顺序:

status ↓ type ↓ id

数据库可以:

先过滤status ↓ 过滤type ↓ 按照id范围扫描 ↓ 返回1000条

12. 如何判断是不是索引选择错误?

使用:

EXPLAINSELECT...

重点看:

key

实际使用的索引。

例如:

key: PRIMARY

表示走主键。

可能导致扫描大量数据。


rows

预计扫描行数。

例如:

rows: 500000

表示需要扫描很多数据。

如果:

rows: 5000

通常更合理。


Extra

例如:

Using filesort

表示额外排序。

但是:

不要简单认为:

没有filesort一定更快

因为:

为了避免排序走主键,有可能扫描更多数据。


13. 总结

核心理解:

1. 索引

作用:

减少扫描的数据量

不是:

用了索引一定快

2. range

表示:

索引范围查询

但是:

范围太大一样慢

3. 联合索引

作用:

多个字段一起建立索引

遵循:

最左匹配原则

4. 大数据分页

推荐:

id>lastIdORDERBYidLIMIT1000

5. SQL 优化重点

不要只看:

有没有走索引

重点看:

扫描多少数据 使用什么索引 执行计划是否合理

最终目标:

让数据库尽可能少扫描数据。