我使用的是MySQL数据库,其中一张表中的数据量有1000万,后台可能会进行列表查询,我应该怎样给这张表建立索引(面试有问到)
给 1000 万数据的表建索引,不能只看“列表查询”四个字,必须看具体 SQL:WHERE 条件、ORDER BY、分页方式、返回字段、并发量。核心原则是:让列表查询尽量走联合索引,并且尽量避免回表、filesort、深分页。
下面给你一套可落地的思路。
1. 先明确后台列表查询的典型 SQL
后台列表一般长这样:
SELECT id, order_no, user_id, status, amount, create_time
FROM orders
WHERE tenant_id = ?
AND status = ?
AND create_time >= ?
AND create_time < ?
ORDER BY create_time DESC, id DESC
LIMIT 20 OFFSET 0;
你要先收集最常用的几条 SQL,尤其是:
- 默认列表查询
- 带筛选条件的查询
- 带时间范围的查询
- 排序字段是什么
- 是否要
COUNT(*)总数 - 是否支持模糊搜索
- 是否分页很深
2. 联合索引设计原则
2.1 基本顺序
一般建议:
等值条件列 -> 排序字段 -> 范围条件字段 -> 覆盖字段
但实际要结合 SQL。更准确地说:
-
等值过滤字段放最左
例如tenant_id = ? AND status = ?,它们通常放联合索引最左边。 -
排序字段尽量紧跟等值字段
因为ORDER BY如果不能用索引,就会产生filesort。 -
范围条件字段位置要小心
联合索引中,一旦某个列用了范围条件,它后面的列通常不能继续用于精确过滤和排序。 -
高频查询字段可做覆盖索引
如果列表只返回少量字段,可以把这些字段加到索引里,避免回表。 -
最左前缀原则
索引(a, b, c),查询WHERE b = ?用不上这个索引。
3. 常见列表场景与索引示例
场景一:租户 + 状态 + 创建时间倒序
WHERE tenant_id = ?
AND status = ?
AND create_time >= ?
ORDER BY create_time DESC
LIMIT 20;
推荐索引:
ALTER TABLE orders
ADD INDEX idx_tenant_status_time (tenant_id, status, create_time DESC);
如果排序还要加 id:
ORDER BY create_time DESC, id DESC
推荐:
ALTER TABLE orders
ADD INDEX idx_tenant_status_time_id (tenant_id, status, create_time DESC, id DESC);
场景二:租户 + 状态 + 按主键 id 倒序
WHERE tenant_id = ?
AND status = ?
ORDER BY id DESC
LIMIT 20;
推荐:
ALTER TABLE orders
ADD INDEX idx_tenant_status_id (tenant_id, status, id DESC);
场景三:按用户查询,按时间倒序
WHERE user_id = ?
ORDER BY create_time DESC
LIMIT 20;
推荐:
ALTER TABLE orders
ADD INDEX idx_user_time (user_id, create_time DESC);
场景四:覆盖索引,避免回表
如果列表只查这几个字段:
SELECT id, order_no, status, create_time
FROM orders
WHERE tenant_id = ? AND status = ?
ORDER BY create_time DESC
LIMIT 20;
可以考虑覆盖索引:
ALTER TABLE orders
ADD INDEX idx_cover_list (
tenant_id,
status,
create_time DESC,
id,
order_no
);
注意:覆盖索引越宽,占空间越大,写入越慢。不要盲目把所有字段都塞进去。
4. 深分页是后台列表最大的坑
不要这样写:
LIMIT 1000000, 20
MySQL 会扫描前 100 万行再丢掉,非常慢。
方案一:游标分页 / 连续分页
WHERE tenant_id = ?
AND status = ?
AND (create_time, id) < (?, ?)
ORDER BY create_time DESC, id DESC
LIMIT 20;
索引:
(tenant_id, status, create_time DESC, id DESC)
这种分页性能最好,适合“下一页”模式。
方案二:延迟关联
如果必须用 OFFSET:
SELECT t.*
FROM orders t
JOIN (
SELECT id
FROM orders
WHERE tenant_id = ?
AND status = ?
ORDER BY create_time DESC, id DESC
LIMIT 1000000, 20
) x ON t.id = x.id;
子查询走覆盖索引,只取主键,再回表拿完整数据。
方案三:总数优化
如果列表要显示总数:
SELECT COUNT(*) FROM orders WHERE ...;
大范围 COUNT 也可能很慢。可以考虑:
- 缓存总数
- 单独维护计数表
- 前端不显示精确总数
- 限制最大页数
- 异步统计
5. 哪些情况索引会失效
注意这些写法:
WHERE DATE(create_time) = '2024-01-01' -- 函数导致失效
WHERE create_time + 1 > ? -- 运算导致失效
WHERE name LIKE '%abc%' -- 前置模糊失效
WHERE status != 1 -- 通常不走索引
WHERE a = 1 OR b = 2 -- 可能失效,可改 UNION ALL
应改成:
WHERE create_time >= '2024-01-01'
AND create_time < '2024-01-02'
模糊搜索如果很复杂,建议上 Elasticsearch,而不是硬扛 MySQL。
6. 不要建太多索引
1000 万数据不算特别大,但索引会带来写放大:
- 每个索引都会占磁盘
- INSERT / UPDATE / DELETE 都要维护索引
- 索引太多,优化器可能选错
一般单表索引控制在 5~6 个以内。写多读少的表还要更少。
7. 用 EXPLAIN 验证,而不是猜
建完索引后一定执行:
EXPLAIN SELECT ...;
重点看:
type:最好ref、range,避免ALLkey:实际用了哪个索引rows:预估扫描行数Extra:Using index:覆盖索引,很好Using where:正常过滤Using filesort:需要额外排序,尽量消除Using temporary:临时表,尽量消除
MySQL 8.0 可以用:
EXPLAIN ANALYZE SELECT ...;
看真实执行时间。
8. 一个推荐落地流程
- 找出后台最常用的 3~5 条列表 SQL。
- 提取每条的 WHERE 等值字段、范围字段、ORDER BY 字段、返回字段。
- 按“等值字段 + 排序字段 + 范围字段”设计联合索引。
- 如果返回字段少,考虑覆盖索引。
- 分页改成游标分页或延迟关联。
- 用
EXPLAIN验证,观察filesort和扫描行数。 - 上线后开慢查询日志,用
pt-query-digest分析。 - 定期
ANALYZE TABLE更新统计信息。
9. 总结一句话
对于 1000 万的后台列表表,不要单独给每个字段建索引,而要根据最常用 SQL 建联合索引,顺序通常是:
等值条件列 -> 排序字段 -> 范围字段 -> 覆盖字段
例如:
ALTER TABLE orders
ADD INDEX idx_list (
tenant_id,
status,
create_time DESC,
id DESC
);
同时重点解决:
- 深分页
- 回表
- filesort
- COUNT 总数
- 模糊搜索
如果你能把具体的表结构和列表查询 SQL 发出来,我可以直接帮你设计索引和分页方案。

浙公网安备 33010602011771号