EXPLAIN诊断SQL性能需重点关注type和key列:type为ALL或index表示未有效用索引,range/ref/eq_ref才属合理;key为空即无索引可用;联合索引须遵循最左前缀原则,字段顺序应等值查询在前、范围查询在后。

EXPLAIN 看不懂?先盯住 type 和 key 这两列
Yii 应用慢,八成是 SQL 没走索引。直接在数据库里跑 EXPLAIN 最靠谱,别信 Yii 日志里那句“执行了 12ms”——它不告诉你到底扫了几百万行。
type 是关键:如果看到 ALL,说明全表扫描;index 是遍历索引树但没用上筛选条件;只有 range、ref、eq_ref 才算真正用了索引。而 key 列为空,基本等于没索引可用。
- 在 Yii 开发中,用
yii\db\Command::explain()获取执行计划(注意:MySQL 支持,PostgreSQL 需改用EXPLAIN ANALYZE) - 对 ActiveRecord 查询,先调
->createCommand()->getRawSql()拿出原生 SQL,再手动套EXPLAIN - 别忽略
Extra列里的Using filesort或Using temporary——这说明 ORDER BY 或 GROUP BY 触发了额外排序/临时表,索引可能没覆盖到排序字段
Yii 的 index 配置写对了,但 MySQL 没用上?检查字段顺序和隐式转换
在 schema 文件或迁移里加了 CREATE INDEX idx_user_status_created ON user (status, created_at),但 WHERE created_at > '2024-01-01' 还是全表扫?问题大概率出在联合索引的最左前缀没被命中。
MySQL 只能按定义顺序使用索引字段。上面那个索引对 WHERE created_at > ? 无效,因为跳过了 status;但如果查 WHERE status = 1 AND created_at > ?,就能用上。
- 联合索引字段顺序要按查询频率 + 选择性排:等值查询字段放前面,范围查询(
>,BETWEEN)放后面 - 字符串字段用
INT类型参数去查?比如$query->andWhere(['user_id' => '123']),而user_id是整型——MySQL 会隐式转类型,导致索引失效 - 用
LIKE '%abc'时,哪怕有索引也白搭;LIKE 'abc%'才可能用上
慢查询日志开了,但 Yii 的 Query 日志没暴露真实瓶颈
Yii 默认记录的是构建后的 SQL,但没带绑定参数值。你看到 WHERE id = ?,实际执行可能是 WHERE id = 9999999——而这个值恰好落在数据末尾,触发了深分页问题。
更麻烦的是,Yii 的 Command::execute() 日志只记耗时,不记扫描行数(rows_examined),而这才是判断是否索引失效的核心指标。
- 开启 MySQL 慢查询日志时,务必加上
log_queries_not_using_indexes = ON,否则漏掉“快但没走索引”的语句 - 在 Yii 配置里启用
'enableParamLogging' => true,才能看到绑定的具体值 - 对分页场景,避免
OFFSET 10000:用游标分页(WHERE id > last_seen_id LIMIT 20)更稳,索引能持续生效
复合索引建多了反而拖慢写入?看 INSERT/UPDATE 频次和字段更新频率
给每个 WHERE 条件都建个索引?小心写操作变卡。每新增一个索引,INSERT 就得多维护一棵 B+ 树;如果字段更新频繁(比如 updated_at),每次 UPDATE 都要同步多个索引页。
一个 user 表上有 idx_name、idx_email、idx_status_updated、idx_created_status……看着全面,实际可能只有 1–2 个真正在查。其余全是负担。
- 用
SHOW INDEX FROM user查Cardinality值,接近 0 或远低于总行数的索引,大概率低效 - 用
performance_schema.table_io_waits_summary_by_index_usage(MySQL 8.0+)看哪些索引根本没人用 - 对高频更新字段(如计数器、状态时间戳),尽量避免把它放在联合索引中间位置——会导致大量索引分裂
索引不是越多越好,而是让每个索引都承担明确的查询路径。删掉半年没被 EXPLAIN 踩过的索引,比加新索引更能提升整体性能。










