必须为commentable_type和commentable_id建复合索引,因Eloquent多态查询始终同时使用两字段,单列索引无效;按最左前缀原则,type在前更合理,可高效过滤离散值,确保with()和whereHas走索引。

多态关联字段必须加复合索引,否则查询性能会断崖式下降——尤其是 with() 预加载或 whereHas 场景下,没索引时 MySQL 可能全表扫描 comments 表。
为什么 commentable_id + commentable_type 必须建复合索引
这两个字段从不单独使用:Eloquent 查询时永远同时用到 commentable_id 和 commentable_type,比如:
WHERE commentable_type = 'App\Models\Post' AND commentable_id = 123WHERE commentable_type IN ('App\Models\Post', 'App\Models\Video') AND commentable_id IN (123, 456)
单给 commentable_id 加索引没用,因为 type 值分布极广(每个模型类名都是不同字符串),MySQL 无法高效定位。只有把两者作为一组联合条件,才需要 INDEX(commentable_id, commentable_type) 或更合理的 INDEX(commentable_type, commentable_id)。
迁移中正确声明 morphs() 索引的方式
Laravel 的 morphs() 方法默认只建 commentable_id 和 commentable_type 字段,但不会自动建复合索引——这是最容易被忽略的坑。
必须手动补上:
Schema::create('comments', function (Blueprint $table) {
$table->id();
$table->text('body');
$table->morphs('commentable'); // 生成 commentable_id + commentable_type
$table->timestamps();
<pre class="brush:php;toolbar:false;">// 关键:显式添加复合索引,顺序按查询模式定
$table->index(['commentable_type', 'commentable_id']);});
注意索引列顺序:commentable_type 在前更合理,因为:
- 查询常以固定 type 开头(如只查文章评论)
- type 值离散度高,放前面能更快过滤出候选行
- MySQL 的最左前缀原则要求 type 必须在前才能命中索引
已有表如何补索引
如果 comments 表已存在且数据量大,别直接在生产环境跑 php artisan migrate ——加索引会锁表。
安全做法是新建迁移,用 Schema::table() 添加,并配合数据库原生命令(如 MySQL 的 ALGORITHM=INPLACE):
Schema::table('comments', function (Blueprint $table) {
$table->index(['commentable_type', 'commentable_id'], 'idx_commentable_type_id');
});
执行前先在测试库验证执行时间;若超 30 秒,改用 DB::statement() 手动加索引并指定算法:
DB::statement('CREATE INDEX idx_commentable_type_id ON comments (commentable_type, commentable_id) ALGORITHM=INPLACE');
预加载 with() 时索引是否生效
会生效,但有个隐藏陷阱:Eloquent 对 morphMany 的预加载确实只发一条 SQL,形如:
SELECT * FROM comments WHERE commentable_type IN (?, ?) AND commentable_id IN (?, ?)
只要你的复合索引是 (commentable_type, commentable_id),这条语句就能走索引。但如果索引是 (commentable_id, commentable_type),而查询条件里 commentable_type 是 IN 多个值、commentable_id 也是 IN 多个值,MySQL 很可能放弃使用该索引——因为无法满足最左前缀匹配。
所以最终结论很实在:索引本身不难加,难的是加对顺序、加对时机、加完还得验证执行计划。别信“加了 morphs 就万事大吉”,EXPLAIN SELECT 一看便知有没有真用上。











