Laravel orWhere() / MySQL 或查询耗时较长

Laravel orWhere() / MySQL or query taking a long time

我正在使用 Laravel 4.2 并且我的应用程序用于跨多个位置跟踪库存。

数据库设置有 inventory_items table、inventory_locations table 和它们之间的枢轴 table inventory_items_inventory_location,其中包含数量值,同时引用记录所属的库存项目和位置。

我的查询是查找任何位置数量值大于或等于 0 的库存项目。在 Laravel 中,我使用子查询和 orWhere,如下所示:

InventoryItem::whereHas('inventoryLocations', function($q) {
  $q->where('reserved', '>=', 0)
     ->orWhere('available', '>=', 0) # slow
     ->orWhere('inbound', '>=', 0) # slow
     ->orWhere('total', '>=', 0); # slow
})->toSql();

给出以下 SQL:

select * from `inventory_items`
where `inventory_items`.`deleted_at` is null
and (
  select count(*) from `inventory_locations`
  inner join `inventory_item_inventory_location`
  on `inventory_locations`.`id` = `inventory_item_inventory_location`.`inventory_location_id` 
  where `inventory_item_inventory_location`.`inventory_item_id` = `inventory_items`.`id`
  and `reserved` >= ?
  or `available` >= ? # slow
  or `inbound` >= ? # slow
  or `total` >= ? # slow
) >= 1

问题是or语句(在代码中标记为#slow)查询时间直接用Sequel Pro最多1s,通过我的超过5s Laravel 应用程序(或通过 artisan tinker)。如果没有这些 'or' 检查(即只检查一种数量类型,例如 'reserved'),查询在 Sequel Pro 上小于 100 毫秒,在 app/tinker.

上类似

我不确定为什么添加这些额外的 'or' 检查会增加查询的时间。有什么想法可以提高查询的性能吗?

查看生成的查询及其 WHERE 条件。你肯定错过了一些括号,因为我猜你需要的是

where `inventory_item_inventory_location`.`inventory_item_id` = `inventory_items`.`id`
and (
   `reserved` >= ?
   or `available` >= ? #
   or `inbound` >= ?
   or `total` >= ?
)

而不是

where `inventory_item_inventory_location`.`inventory_item_id` = `inventory_items`.`id`
and `reserved` >= ?
or `available` >= ? # slow
or `inbound` >= ? # slow
or `total` >= ?

它会导致完整的 table 扫描,这对于 table 秒的大量行来说非常慢。

为了解决这个问题,更换

InventoryItem::whereHas('inventoryLocations', function($q) {
  $q->where('reserved', '>=', 0)
   ->orWhere('available', '>=', 0) # slow
   ->orWhere('inbound', '>=', 0) # slow
   ->orWhere('total', '>=', 0); # slow
})->toSql();

InventoryItem::whereHas('inventoryLocations', function($q) {
  $q->where(function($subquery) {
    $subquery->where('reserved', '>=', 0)
     ->orWhere('available', '>=', 0)
     ->orWhere('inbound', '>=', 0)
     ->orWhere('total', '>=', 0);
  });
})->toSql();

查看 MySQL 的 EXPLAIN 命令,让您分析查询将如何执行以及将查询多少行 - http://dev.mysql.com/doc/refman/5.7/en/explain.html