Laravel查询性能异常:小表查询慢于大表问题排查求助
我开发了一款消息应用,为实现新消息检测功能,除了messages表之外额外创建了unread_message_flag_clients表,用一个布尔标识来标记用户是否有未读消息。原本觉得这个未读标识表字段更少、行数也远少于messages表,查询效率应该比直接查messages表找未读消息更高,但性能测试结果却相反——查messages表的速度更快,我搞不懂原因,希望有人帮忙排查。
测试脚本与查询代码
$start=hrtime(true); UnreadMessageFlagClient::query() ->where('client_id',50) ->get(); $end=hrtime(true); $eta=$end-$start; $time1 = $eta/1e+6; $start2=hrtime(true); Message::query() ->where('talk_id',10) ->where('sent_by','p') ->whereNull('read_by_receiver') ->get(); $end2=hrtime(true); $eta2=$end2-$start2; $time2 = $eta2/1e+6; echo 'Time1: '.$time1.' <br/><br/> Time2: '.$time2;
测试输出
测试结果始终显示第一个查询耗时更长,例如:
Time1: 3.794781
Time2: 1.475115
表结构
Schema::create('messages', function (Blueprint $table) { $table->id(); $table->foreignId('talk_id'); $table->text('message', 6000); $table->string('sent_by',1); // C代表客户,P代表专业人员 $table->softDeletes(); $table->timestamps(); }); Schema::create('unread_message_flag_clients', function (Blueprint $table) { $table->id(); $table->foreignId('client_id'); $table->boolean('unread_messages_flag')->nullable(); });
测试时messages表有486行数据,unread_message_flag_clients表有60行数据。
问题排查与解决方向
1. 索引缺失是核心原因
从表结构可以看到,unread_message_flag_clients表的client_id字段没有创建索引。当执行where('client_id',50)查询时,数据库会进行全表扫描(哪怕只有60行);而messages表的talk_id作为外键,Laravel的foreignId方法会隐式添加索引,加上sent_by的过滤条件,查询时会走索引,效率自然更高。
2. 查询逻辑可以优化
你当前用get()获取整个集合,但实际上只需要判断当前客户是否存在未读标识,用first()代替get()可以减少ORM对结果集的处理开销,进一步提升速度:
UnreadMessageFlagClient::query() ->where('client_id',50) ->first();
3. 用EXPLAIN验证执行计划
在数据库中直接执行两个查询的EXPLAIN语句,查看执行计划:
EXPLAIN SELECT * FROM unread_message_flag_clients WHERE client_id = 50; EXPLAIN SELECT * FROM messages WHERE talk_id = 10 AND sent_by = 'p' AND read_by_receiver IS NULL;
通过结果可以直观看到第一个查询是否走了全表扫描,第二个查询是否使用了索引。
4. 补充索引的方案
修改unread_message_flag_clients表的迁移文件,给client_id添加索引:
Schema::create('unread_message_flag_clients', function (Blueprint $table) { $table->id(); $table->foreignId('client_id')->index(); // 添加index() $table->boolean('unread_messages_flag')->nullable(); });
或者已创建表后,单独创建索引:
CREATE INDEX idx_unread_client_id ON unread_message_flag_clients(client_id);
内容的提问来源于stack exchange,提问作者kakaroto codes

