多态关联字段必须联合建索引,单列索引无效;因B-tree索引需左前缀匹配,commentable_type等值条件选择性高,应置于复合索引最左侧,正确顺序为['commentable_type', 'commentable_id']。

多态关联字段 commentable_id 和 commentable_type 必须联合建索引,单列索引几乎无效。
为什么 commentable_id 单独加索引没用
数据库执行查询时,若 WHERE 条件同时含 commentable_type 和 commentable_id(例如 where commentable_type = 'App\Models\Post' and commentable_id = 123),仅对 commentable_id 建索引会导致全表扫描或索引失效——因为 B-tree 索引按左前缀匹配,commentable_type 是等值条件且选择性高,必须放在复合索引最左侧。
- 常见错误:只运行
$table->index('commentable_id'),EXPLAIN 显示type: ALL - 正确顺序:
['commentable_type', 'commentable_id'],类型字段在前,ID 字段在后 - 如果业务中还常按
created_at排序,可扩展为['commentable_type', 'commentable_id', 'created_at'],但注意索引宽度和写入开销
morphs() 不会自动加索引,得手动补
Laravel 的 $table->morphs('commentable') 只创建字段和基础外键约束,不建索引。很多人误以为“用了 morphs 就万事大吉”,结果上线后查评论变慢。
- 迁移中必须显式追加:
$table->index(['commentable_type', 'commentable_id']) - 建议命名清晰,便于后续维护:
$table->index(['commentable_type', 'commentable_id'], 'comments_commentable_index') - 若已上线且表很大,MySQL 5.6+ 可在线加索引,但仍需避开高峰;PostgreSQL 需用
CONCURRENTLY,不能包在事务里,得用DB::unprepared()绕过 Laravel 迁移事务
软删除 + 多态查询时的索引陷阱
当评论表启用软删除(SoftDeletes),又常查 withTrashed() 或 onlyTrashed() 下的多态数据,deleted_at 字段必须参与复合索引,否则 WHERE commentable_type = ? AND commentable_id = ? AND deleted_at IS NULL 仍可能走不到索引。
- 不要建两个单列索引(
deleted_at+['commentable_type', 'commentable_id']),MySQL 通常只用一个 - 优先建三字段复合索引:
['deleted_at', 'commentable_type', 'commentable_id'],deleted_at放最前(等值判断) - 如果
deleted_at实际从不为NULL(比如默认设为0),可改用UNSIGNED INTEGER类型,避免 NULL 值干扰优化器选索引
真正卡住性能的往往不是语法写错,而是索引没覆盖到实际执行的 WHERE 条件组合。每次加完索引,务必用 EXPLAIN 跑一遍真实 SQL,确认 key 列非空、rows 显著小于总行数——别信“应该能用上”。











