慢速查询Groupby,加入MYSQL laravel

慢速查询Groupby,加入MYSQL laravel,mysql,sql,laravel,group-by,sql-execution-plan,Mysql,Sql,Laravel,Group By,Sql Execution Plan,我有4张桌子:地点、品牌、类别、地点 这种关系是属于品牌的地方,许多地方都有类别和地点 我想得到搜索结果与某些类别和位置的地方,但只显示一个品牌的地方 表格信息 places表包含大约100k+行 place\u categorypivot表包含650k+行,其中包含place\u category。place\u id和places。brand\u id列被索引 place\u locationspivot表包含约550k+行,其中包含place\u location.place\u id和p

我有4张桌子:地点、品牌、类别、地点

这种关系是属于品牌的地方,许多地方都有类别和地点

我想得到搜索结果与某些类别和位置的地方,但只显示一个品牌的地方

表格信息
places
表包含大约100k+行

place\u category
pivot表包含650k+行,其中包含
place\u category。place\u id
places。brand\u id
列被索引

place\u locations
pivot表包含约550k+行,其中包含
place\u location.place\u id
place\u location.location\u id

到目前为止我得到的问题

Place::join('place_location', function ($join) use ($city) {
    $join->on('place_location.place_id', '=', 'places.id')
         ->where('place_location.location_id', '=',  $city->id);
})
->join('place_category', function ($join) {
    $join->on('place_category.place_id', '=', 'places.id')
         ->where('place_category.category_id', '=',  $category->id);
})
->groupBy('places.brand_id')
->take(5)
->get();
导致查询速度慢的
groupBy
大约需要2秒

解释结果如下所示

id | select_type | table          | possible_key            | key            | key_len | ref                | rows | Extra

1  | SIMPLE      | places         | PRIMARY                 | brand_id       | 4       | NULL               | 50   | Using where

1  | SIMPLE      | place_location | place_id,place_location | place_location | 4       | const,db.places.id | 1    | Using index

1  | SIMPLE      | place_category | place_category          | place_category | 4       | db.places.id,const | 1    | Using where; Using index
select 
    `places`.`id`, 
    `places`.`name`, 
    `places`.`display`, 
    `places`.`status_a`, 
    `places`.`status_b`, 
    `places`.`brand_id`, 
    `places`.`address` 
from `places` 
inner join `place_location` 
    on `place_location`.`place_id` = `places`.`id` 
    and `place_location`.`location_id` = 4047 
inner join `place_category` 
    on `place_category`.`place_id` = `places`.`id` 
    and `place_category`.`category_id` = 102 
where 
    `places`.`status_a` != 1 
    and `status_b` = 2 
    and `display` >= 5 
group by `places`.`brand_id` 
limit 4
CREATE TABLE `places` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `user_id` int(11) unsigned DEFAULT NULL,
 `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `desc` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
 `city_id` int(11) unsigned NOT NULL DEFAULT '102',
 `state_id` int(11) unsigned NOT NULL DEFAULT '34',
 `location_id` int(11) unsigned NOT NULL DEFAULT '15',
 `landmark_id` int(10) unsigned NOT NULL DEFAULT '1',
 `postcode` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `country_id` int(4) unsigned NOT NULL,
 `lat` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `long` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `phone` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
 `sec_phone` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
 `third_phone` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `fourth_phone` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `brand_id` int(10) NOT NULL DEFAULT '1',
 `display` int(11) NOT NULL DEFAULT '0',
 `view` int(10) unsigned NOT NULL DEFAULT '0',
 `status_b` tinyint(3) unsigned NOT NULL DEFAULT '2',
 `status_a` tinyint(4) NOT NULL DEFAULT '2',
 `company_name` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
 `slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `lock_status_id` tinyint(3) unsigned DEFAULT '1',
 `created_at` timestamp NULL DEFAULT NULL,
 `updated_at` timestamp NULL DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `slug` (`slug`),
 KEY `city_id` (`city_id`),
 KEY `location_id` (`location_id`),
 KEY `user_id` (`user_id`),
 KEY `landmark_id` (`landmark_id`),
 KEY `name` (`name`),
 KEY `brand_id` (`brand_id`),
 KEY `groupby_brandid` (`status_b`, `display`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=116070 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci


CREATE TABLE `place_location` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `location_id` int(10) NOT NULL,
 `place_id` int(10) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `place_location` (`place_id`,`location_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=564259 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `place_category` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `category_id` int(11) unsigned NOT NULL,
 `place_id` int(11) unsigned NOT NULL,
 `branch_id` int(11) unsigned NOT NULL,
 `created_at` timestamp NULL DEFAULT NULL,
 `updated_at` timestamp NULL DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `place_id` (`place_id`),
 KEY `place_category` (`category_id`,`place_id`)
) ENGINE=InnoDB AUTO_INCREMENT=905384 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
原始Mysql查询如下所示

id | select_type | table          | possible_key            | key            | key_len | ref                | rows | Extra

1  | SIMPLE      | places         | PRIMARY                 | brand_id       | 4       | NULL               | 50   | Using where

1  | SIMPLE      | place_location | place_id,place_location | place_location | 4       | const,db.places.id | 1    | Using index

1  | SIMPLE      | place_category | place_category          | place_category | 4       | db.places.id,const | 1    | Using where; Using index
select 
    `places`.`id`, 
    `places`.`name`, 
    `places`.`display`, 
    `places`.`status_a`, 
    `places`.`status_b`, 
    `places`.`brand_id`, 
    `places`.`address` 
from `places` 
inner join `place_location` 
    on `place_location`.`place_id` = `places`.`id` 
    and `place_location`.`location_id` = 4047 
inner join `place_category` 
    on `place_category`.`place_id` = `places`.`id` 
    and `place_category`.`category_id` = 102 
where 
    `places`.`status_a` != 1 
    and `status_b` = 2 
    and `display` >= 5 
group by `places`.`brand_id` 
limit 4
CREATE TABLE `places` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `user_id` int(11) unsigned DEFAULT NULL,
 `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `desc` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
 `city_id` int(11) unsigned NOT NULL DEFAULT '102',
 `state_id` int(11) unsigned NOT NULL DEFAULT '34',
 `location_id` int(11) unsigned NOT NULL DEFAULT '15',
 `landmark_id` int(10) unsigned NOT NULL DEFAULT '1',
 `postcode` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `country_id` int(4) unsigned NOT NULL,
 `lat` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `long` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `phone` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
 `sec_phone` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
 `third_phone` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `fourth_phone` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `brand_id` int(10) NOT NULL DEFAULT '1',
 `display` int(11) NOT NULL DEFAULT '0',
 `view` int(10) unsigned NOT NULL DEFAULT '0',
 `status_b` tinyint(3) unsigned NOT NULL DEFAULT '2',
 `status_a` tinyint(4) NOT NULL DEFAULT '2',
 `company_name` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
 `slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `lock_status_id` tinyint(3) unsigned DEFAULT '1',
 `created_at` timestamp NULL DEFAULT NULL,
 `updated_at` timestamp NULL DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `slug` (`slug`),
 KEY `city_id` (`city_id`),
 KEY `location_id` (`location_id`),
 KEY `user_id` (`user_id`),
 KEY `landmark_id` (`landmark_id`),
 KEY `name` (`name`),
 KEY `brand_id` (`brand_id`),
 KEY `groupby_brandid` (`status_b`, `display`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=116070 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci


CREATE TABLE `place_location` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `location_id` int(10) NOT NULL,
 `place_id` int(10) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `place_location` (`place_id`,`location_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=564259 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `place_category` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `category_id` int(11) unsigned NOT NULL,
 `place_id` int(11) unsigned NOT NULL,
 `branch_id` int(11) unsigned NOT NULL,
 `created_at` timestamp NULL DEFAULT NULL,
 `updated_at` timestamp NULL DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `place_id` (`place_id`),
 KEY `place_category` (`category_id`,`place_id`)
) ENGINE=InnoDB AUTO_INCREMENT=905384 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Show Create Table如下所示

id | select_type | table          | possible_key            | key            | key_len | ref                | rows | Extra

1  | SIMPLE      | places         | PRIMARY                 | brand_id       | 4       | NULL               | 50   | Using where

1  | SIMPLE      | place_location | place_id,place_location | place_location | 4       | const,db.places.id | 1    | Using index

1  | SIMPLE      | place_category | place_category          | place_category | 4       | db.places.id,const | 1    | Using where; Using index
select 
    `places`.`id`, 
    `places`.`name`, 
    `places`.`display`, 
    `places`.`status_a`, 
    `places`.`status_b`, 
    `places`.`brand_id`, 
    `places`.`address` 
from `places` 
inner join `place_location` 
    on `place_location`.`place_id` = `places`.`id` 
    and `place_location`.`location_id` = 4047 
inner join `place_category` 
    on `place_category`.`place_id` = `places`.`id` 
    and `place_category`.`category_id` = 102 
where 
    `places`.`status_a` != 1 
    and `status_b` = 2 
    and `display` >= 5 
group by `places`.`brand_id` 
limit 4
CREATE TABLE `places` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `user_id` int(11) unsigned DEFAULT NULL,
 `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `desc` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
 `city_id` int(11) unsigned NOT NULL DEFAULT '102',
 `state_id` int(11) unsigned NOT NULL DEFAULT '34',
 `location_id` int(11) unsigned NOT NULL DEFAULT '15',
 `landmark_id` int(10) unsigned NOT NULL DEFAULT '1',
 `postcode` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `country_id` int(4) unsigned NOT NULL,
 `lat` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `long` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `phone` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
 `sec_phone` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
 `third_phone` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `fourth_phone` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
 `brand_id` int(10) NOT NULL DEFAULT '1',
 `display` int(11) NOT NULL DEFAULT '0',
 `view` int(10) unsigned NOT NULL DEFAULT '0',
 `status_b` tinyint(3) unsigned NOT NULL DEFAULT '2',
 `status_a` tinyint(4) NOT NULL DEFAULT '2',
 `company_name` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
 `slug` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
 `lock_status_id` tinyint(3) unsigned DEFAULT '1',
 `created_at` timestamp NULL DEFAULT NULL,
 `updated_at` timestamp NULL DEFAULT NULL,
 PRIMARY KEY (`id`),
 UNIQUE KEY `slug` (`slug`),
 KEY `city_id` (`city_id`),
 KEY `location_id` (`location_id`),
 KEY `user_id` (`user_id`),
 KEY `landmark_id` (`landmark_id`),
 KEY `name` (`name`),
 KEY `brand_id` (`brand_id`),
 KEY `groupby_brandid` (`status_b`, `display`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=116070 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci


CREATE TABLE `place_location` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `location_id` int(10) NOT NULL,
 `place_id` int(10) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `place_location` (`place_id`,`location_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=564259 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

CREATE TABLE `place_category` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `category_id` int(11) unsigned NOT NULL,
 `place_id` int(11) unsigned NOT NULL,
 `branch_id` int(11) unsigned NOT NULL,
 `created_at` timestamp NULL DEFAULT NULL,
 `updated_at` timestamp NULL DEFAULT NULL,
 PRIMARY KEY (`id`),
 KEY `place_id` (`place_id`),
 KEY `place_category` (`category_id`,`place_id`)
) ENGINE=InnoDB AUTO_INCREMENT=905384 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

你知道如何改进这个查询吗?错误的索引?还是错误的查询?

首先尝试执行下面的查询

explain 
select  `products`.`id`, `products`.`name`, `products`.`display`,
        `products`.`status_a`, `products`.`status_b`, `products`.`brand_id`,
        `products`.`address`
    from  `products`
    inner join  `product_location`  ON `product_location`.`product_id` = `products`.`id`
      and  `product_location`.`location_id` = 4047
    inner join  `product_category`  ON `product_category`.`product_id` = `products`.`id`
      and  `product_category`.`category_id` = 102
    where  `products`.`status_a` != 1
      and  `status_b` = 2
      and  `display` >= 5
    group by  `products`.`brand_id`
    limit  4 
EXPLAIN SELECT语句显示MySQL查询优化器将如何执行查询

索引可以提高性能,索引也可以产生负面影响 如果数量过多,则执行。这是因为 一个表有索引,MySQL必须做更多的工作来保持更新。 诀窍是在足够多的索引之间找到适当的平衡,以便 提高性能,但不要太多,以免产生负面影响 表演

再次尝试添加索引并执行相同的explain语句

希望这会有帮助,谢谢

`products`.`status_a` != 1 
and `status_b` = 2 
and `display` >= 5 
邀请这个

INDEX(status_b, display)
这是0/1标志吗

`products`.`status_a` != 1 
如果是,则改为

`products`.`status_a` = 0
那你就可以做得更好了

INDEX(status_b, status_a, display)

产品位置
产品类别
听起来像是多对多映射表。正如这里所讨论的,它们需要复合索引:确保数据类型匹配。

为什么要使用连接查询,可以使用laravel雄辩的关系

Place::whereHas('placeLocation', function ($query) use ($city) {
    $query->where('location_id', '=',  $city->id);
})
->whereHas('placeCategory', function ($query) {
    $query->where('category_id', '=',  $category->id);
})
->groupBy('brand_id')
->take(5)
->get();


placeLocation(HasMany)和placeCategory(BelongToMany)这两种关系都是必须编写就地模型的关系。

通常,在查询执行期间,数据库将无法合并多个索引。这意味着,为表中的所有内容创建一个单列索引是没有帮助的

您似乎经常使用单列索引。尝试以满足您的查询的方式组合它们

多索引如何工作?

在创建数据库索引时,我总是试图根据烹饪书中的索引来解释它们:这些索引通常是嵌套索引。首先,他们被分类在膳食类型,如汤,菜,沙拉等。在这些类别中,它们按字母顺序排列

此索引与SQL中的索引类似:

KEY `recipe` (`meal_type_id`, `name`)
作为一个人类,你现在可以通过先去盘子(第一个索引)然后去字母“C”(第二个索引)来找到奶酪蛋糕

因此,在这种情况下,多列索引非常有用

优化表格位置:

您在
WHERE
子句中使用了
status\u a
status\u b
display
,在
groupby
中也使用了
brand\u id
。对于多列索引,请尝试找到这些列的有意义的组合

这里的秩序很重要

例如,如果只有10%的数据与
status_b=2
匹配,则应将该字段用作索引中的第一列,因为它将删除90%的行。第二个索引列要做的事情要少得多。想想上面的芝士蛋糕例子:我们已经知道芝士蛋糕是一道菜,所以我们直接看了这些菜,就能够排除其他90%的食谱

这被称为“”

优化表格位置和位置类别:

别忘了还要查看连接的表,并确保它们也被正确地索引

就像前面提到的一样,试着在这些表上找到一个有用的索引。看看你的查询要求什么,并尝试用一个有用的索引来满足它

一个表应该有多少索引?

要回答这个问题,您只需要了解索引必须在每次插入或更新时更新。因此,如果它是一个写密集型表,那么应该使用尽可能少的索引


如果该表是读密集型的,但不会频繁写入,则可以再增加一点索引。只要记住,他们需要记忆。如果您的表有数百万行,那么如果您使用了许多索引,您的索引可能会变得相当大。

作为练习,您是否还可以包含当前正在使用Laravel代码运行的原始MySQL查询?仅仅通过查看您的PHP代码,就有点难以看到该查询是什么。刚刚添加了原始MySQLis place\u location上的索引是包含2个键的索引还是包含1个键的2个索引?它是包含2列的索引,索引的列顺序是place\u id和location\u id列。我认为groupBYNote(与性能无关)按
brand\u id进行分组以及所有其他列未使用聚合函数包装所导致的问题很可能会返回不确定的结果(我怀疑产品表中的所有coulmn在功能上是否依赖于brand\u id)更多:有什么建议要索引哪些列吗?我现在正在为透视表product_location和product_category使用复合索引(我刚刚更新了关于这个问题的解释),但速度仍然很慢,groupby大约需要10-12秒,如果我删除groupby,则
产品
状态a
!=1,有3个值,所以不是0/1我尝试了索引索引的答案(状态b,状态a,显示),解释显示mysql不使用索引,它一直使用单列品牌id索引。@TomKur-
=通常可以