Laravel多对多关联下对比Eloquent与Query Builder的执行效率及测试方法
两种实现方式的执行效率对比
*通常场景下,第二种基于DB Query Builder的实现执行时间更短、效率更优,二者的性能差距核心来源如下:
- ORM层开销差异:第一种实现基于Eloquent模型层,执行过程中会自动加载Green模型的关联定义、触发模型生命周期事件、为每条匹配结果实例化完整的Green模型对象,哪怕只需要提取ID,也会产生不可忽略的框架层开销;当返回的Green ID数量较多(比如超过1万条)时,这部分开销会被进一步放大。第二种实现完全跳过模型层,直接操作中间关联表,不存在上述额外开销。
- SQL执行效率差异:第一种实现的
whereHas会生成两个嵌套的EXISTS子查询,最终SQL大致为:
select id from greens where exists ( select 1 from red_green where red_green.green_id = greens.id and red_green.red_id = 123 ) or exists ( select 1 from blue_green where blue_green.green_id = greens.id and blue_green.blue_id in (2,3,5) )
数据库优化器对这类跨表嵌套子查询的执行计划选择,往往不如直接查两张中间关联表的单表查询高效;第二种实现的两次查询都是针对中间表的简单单表查询,只要red_green.red_id、green_blue.blue_id建立了普通索引,数据库可以直接命中索引返回结果,无需关联主表扫描。
- 潜在逻辑隐患:两种原实现都存在返回结果重复的问题——同一个Green可能同时被目标Red、目标Blue关联,合并后的ID数组会存在重复值,需要额外用
array_unique处理;第一种实现如果查询构造器的条件嵌套不当,OR逻辑可能会打破原有查询约束,最终返回全表Green的无效结果。
准确测量二者执行效率的方法
要避免单次测试的偶然误差,需要在可控的测试环境下按照以下方法测量:
- 统一测试环境基线
- 固定数据库的索引配置、测试数据量级(建议准备和线上环境同规模的测试数据集),关闭PHP、数据库、框架层的所有缓存,避免冷启动、缓存命中带来的测试偏差。
- 测量时关闭所有第三方调试工具(如Debugbar、Telescope实时采集),避免埋点工具本身的开销干扰测试结果。
- 多维度对照测量
- 用
microtime(true)分别记录两段代码从启动到拿到最终结果数组的全程耗时,覆盖ORM层、SQL层、PHP数组处理的全部开销,而不是只单独测量SQL的执行时间。 - 开启Laravel查询日志
DB::enableQueryLog(),抓取两段代码实际生成的全部SQL,通过EXPLAIN分析每条SQL的执行计划,对比索引命中率、扫描行数、临时表使用等数据库侧的性能差异。
- 用
- 消除偶然误差
- 单轮测量的结果受CPU调度、磁盘IO波动影响极大,需要将两段逻辑分别循环执行100-1000次,取平均耗时作为最终对比依据。
- 对比耗时前先校验结果一致性:确保两段代码最终返回的Green ID集合完全相同,避免因为逻辑漏洞(漏查、重复查、条件错误)导致的耗时对比无意义。
注:原代码中存在多处变量名不匹配的问题,比如Eloquent实现中引用的
$red、$blues与定义的$red_id、$blue_ids不一致,DB实现中引用的$user->red_id与给定的目标Red ID参数不一致,实际运行时需要先修正这类语法错误。
内容的提问来源于stack exchange,提问作者DevonDahon
相关产品推荐
相关产品推荐

