加了索引查询仍慢,主因是复合索引字段顺序不匹配查询条件或where中使用函数/隐式转换导致索引失效;例如where year(created_at)=2025无法走created_at索引,或索引(status,user_id)却只查where user_id=123。

为什么加了索引查询还是慢?EXPLAIN 显示 type=ALL 或 rows 过大
常见原因是复合索引字段顺序不匹配查询条件,或 WHERE 中用了函数/隐式转换导致索引失效。比如 WHERE YEAR(created_at) = 2025 无法走 created_at 索引;又如索引是 (status, user_id),但查询只写了 WHERE user_id = 123,则该索引几乎不生效。
实操建议:
- 用
EXPLAIN FORMAT=TREE(MySQL 8.0+)看真实执行路径,比传统EXPLAIN更直观 - 把高频过滤字段放在复合索引最左侧,例如常查
WHERE tenant_id = ? AND status IN (?, ?),索引应建为(tenant_id, status) - 避免在索引列上做运算、类型转换或
LIKE '%xxx',改用覆盖索引 + 应用层后过滤,或引入GENERATED COLUMN + INDEX预计算 - 检查
SHOW INDEX FROM table_name中的Cardinality,若远低于实际行数,说明统计信息过期,运行ANALYZE TABLE table_name
什么时候该用分区表?PARTITION BY RANGE 和 LIST 怎么选
分区不是银弹——只有当单表超 2000 万行、且存在明显时间/租户/地域等天然切分维度时才值得引入。否则反而增加优化器决策负担,甚至让简单查询变慢。
实操建议:
- 优先选
PARTITION BY RANGE (TO_DAYS(created_at))(按天)或RANGE COLUMNS(created_at)(MySQL 5.5+,支持 datetime 直接分区),避免用YEAR()导致每月数据倾斜 - 不要用
HASH分区:它打散数据利于写入,但绝大多数查询无法裁剪分区,失去分区意义 - 每个分区数据量建议控制在 100–500 万行,太多起不到裁剪效果,太少则管理开销上升;可通过
SELECT PARTITION_NAME, TABLE_ROWS FROM INFORMATION_SCHEMA.PARTITIONS WHERE TABLE_NAME = 't_log'观察分布 - 必须配合分区键出现在
WHERE条件中才能生效,例如按created_at分区,但查询WHERE user_id = 123仍会扫描全部分区
ALTER TABLE ... ADD INDEX 在千万级表上会锁表吗?如何在线加索引
MySQL 5.6+ 默认启用 ALGORITHM=INPLACE,但并非完全无锁:ADD INDEX 仍需短暂的 MDL_WRITE 锁(阻塞 DML),且构建索引期间占用大量 I/O 和内存。线上直接执行大概率引发超时或主从延迟飙升。
实操建议:
- 确认版本是否支持在线 DDL:
SELECT VERSION()≥ 5.6 且innodb_file_per_table=ON(必须) - 强制指定低影响方式:
ALTER TABLE t_user ADD INDEX idx_status_uid (status, user_id), ALGORITHM=INPLACE, LOCK=NONE;若提示LOCK=NONE不支持,降级为LOCK=SHARED(允许读,阻塞写) - 更稳妥的做法是用
pt-online-schema-change(Percona Toolkit):它通过触发器双写同步,全程不锁原表,但要求有足够磁盘空间存影子表 - 加索引前先
SET innodb_sort_buffer_size = 268435456(256MB),加快内部排序,减少临时文件生成
查询只返回几条数据,为什么 ORDER BY created_at LIMIT 10 还很慢?
这是典型“深分页陷阱”:MySQL 必须先按 created_at 排序全部千万行,再取前 10 条。即使有索引,ORDER BY + LIMIT 在无过滤条件下仍要遍历索引树大量节点。
实操建议:
- 用游标分页替代
LIMIT offset, size:记录上一页最后一条的created_at和id,下一页查WHERE created_at - 如果业务允许,加强过滤条件,例如限定最近 30 天:
WHERE created_at > DATE_SUB(NOW(), INTERVAL 30 DAY),再加ORDER BY - 对高并发分页场景,可预生成“页码映射表”,用定时任务维护
page_no → min_id/max_id关系,避免实时计算 - 注意
ORDER BY字段必须包含在索引中,且顺序一致;若索引是(status, created_at),但查询ORDER BY created_at无status条件,则无法利用该索引做排序
真正卡住性能的往往不是某条 SQL 写得不够巧,而是索引没对上查询模式、分区没对上数据生命周期、或者在线变更时低估了元数据锁的影响。这些地方一错,加再多缓存也救不回响应时间。











