简体   繁体   English

优化mysql查询-避免创建临时表?

[英]Optimizing the mysql query - Avoid creation of temporary table?

This is the query that I am using on tables : products , reviews , replies , review_images . 这是我在表格上使用的查询: productsreviewsrepliesreview_images

Query : 查询:

SELECT products.id, reviews.*,
GROUP_CONCAT(DISTINCT CONCAT_WS('~',replies.reply, replies.time)) AS Replies,
GROUP_CONCAT(DISTINCT CONCAT_WS('~',review_images.image_title, review_images.image_location)) AS ReviewImages
FROM products
LEFT JOIN reviews on products.id = reviews.product_id
LEFT JOIN replies on reviews.id = replies.review_id
LEFT JOIN review_images on reviews.id = review_images.review_id
WHERE products.id = 1
GROUP BY products.id, reviews.id;

Schema : 架构

Products : 产品:

id  |  name  |  product_details....

Reviews : 评论:

id  |  product_id  |  username  |  review  |  time  | ...

Replies : 回覆:

id  |  review_id   |  username  |  reply  |  time  | ...

Review Images : 评论图片:

id  |  review_id  |  image_title  |  image_location  | ...

Indexes : 指标

Products : 产品:

PRIMARY KEY - id 主键-ID

Reviews : 评论:

PRIMARY KEY - id 主键-ID

FOREIGN KEY - product_id (id IN products table) 外键-product_id(产品表中的id)

FOREIGN KEY - username (username IN users table) 外键-用户名(“用户名”为“用户”表)

Replies : 回覆:

PRIMARY KEY - id 主键-ID

FOREIGN KEY - review_id (id IN reviews table) 外键-review_id(id为“评论”表)

FOREIGN KEY - username (username IN users table) 外键-用户名(“用户名”为“用户”表)

Review Images : 评论图片:

PRIMARY KEY - id 主键-ID

FOREIGN KEY - review_id (id IN reviews table) 外键-review_id(id为“评论”表)


Explain Query : 说明查询:

id | id | select_type | select_type | table | 桌子 | type | 类型 possible_keys | 可能的钥匙 | rows | | extra 额外

1 | 1 | SIMPLE | 简单 products | 产品| index | 索引| null | 空| 1 | 1 | Using index; 使用索引; Using temporary; 使用临时; Using filesort 使用文件排序

1 | 1 | SIMPLE | 简单 reviews | 评论| ALL | 全部| product_id | product_id | 4 | 4 | Using where; 在哪里使用 Using join buffer (Block Nested Loop) 使用联接缓冲区(块嵌套循环)

1 | 1 | SIMPLE | 简单 replies | 回复| ref | 参考| review_id | review_id | 1 | 1 | Null 空值

1 | 1 | SIMPLE | 简单 review_images | review_images | ALL | 全部| review_id | review_id | 5 | 5 | Using where; 在哪里使用 Using join buffer (Block Nested Loop) 使用联接缓冲区(块嵌套循环)

I don't know what is wrong here, that it needs to use filesort and create a temporary table? 我不知道这有什么问题,它需要使用filesort并创建一个临时表?

Here are few Profiling results : 以下是一些分析结果:

Opening Tables 140 µs 开孔台140 µs

Init 139 µs 初始化139 µs

System Lock 34 µs 系统锁定34 µs

Optimizing 21 µs 优化21 µs

Statistics 106 µs 统计量106 µs

Preparing 146 µs 准备146 µs

Creating Tmp Table 13.6 ms 创建Tmp表13.6毫秒

Sorting Result 27 µs 排序结果27 µs

Executing 11 µs 执行11 µs

Sending Data 11.6 ms 发送数据11.6毫秒

Creating Sort Index 1.4 ms 创建排序索引1.4毫秒

End 89 µs 结束89 µs

Removing Tmp Table 8.9 ms 删除Tmp表8.9毫秒

End 34 µs 结束34 µs

Query End 25 µs 查询结束25 µs

Closing Tables 66 µs 闭合台66 µs

Freeing Items 41 µs 释放项目41 µs

Removing Tmp Table 1.4 ms 删除Tmp表1.4毫秒

Freeing Items 46 µs 释放项目46 µs

Removing Tmp Table 1.2 ms 删除Tmp表1.2毫秒

Freeing Items 203 µs 释放项目203 µs

Cleaning Up 55 µs 清理55 µs


As from the Explain and Profiling results, it is clear that temporary table is created to produce the results. 从解释和概要分析结果来看,很明显,已创建临时表以产生结果。 How can I optimize this query to get similar results and better performance and avoid the creation of temporary table? 如何优化此查询以获得相似的结果和更好的性能,并避免创建临时表?

Help would be appreciated. 帮助将不胜感激。 Thanks in advance. 提前致谢。

EDIT 编辑

Create Tables 建立表格

CREATE TABLE `products` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `name` varchar(100) NOT NULL,
 `description` varchar(100) NOT NULL,
 `items` int(11) NOT NULL,
 `price` int(11) NOT NULL,
 PRIMARY KEY (`id`)
) ENGINE=InnoDB

CREATE TABLE `reviews` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `username` varchar(30) NOT NULL,
 `product_id` int(11) NOT NULL,
 `review` text NOT NULL,
 `time` datetime NOT NULL,
 `ratings` int(11) NOT NULL,
 PRIMARY KEY (`id`),
 KEY `product_id` (`product_id`),
 KEY `username` (`username`)
) ENGINE=InnoDB

CREATE TABLE `replies` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `review_id` int(11) NOT NULL,
 `username` varchar(30) NOT NULL,
 `reply` text NOT NULL,
 `time` datetime NOT NULL,
 PRIMARY KEY (`id`),
 KEY `review_id` (`review_id`)
) ENGINE=InnoDB

CREATE TABLE `review_images` (
 `id` int(11) NOT NULL AUTO_INCREMENT,
 `review_id` int(11) NOT NULL,
 `image_title` text NOT NULL,
 `image_location` text NOT NULL,
 PRIMARY KEY (`id`),
 KEY `review_id` (`review_id`)
) ENGINE=InnoDB

EDIT : 编辑

I simplified the query above and now it does not create temporary tables. 我简化了上面的查询,现在它不会创建临时表。 The only reason as mentioned by @Bill Karwin was that I was using GROUP BY on second table in the joins. @Bill Karwin提到的唯一原因是我在联接的第二个表上使用了GROUP BY

Simplified query : 简化查询:

SELECT reviews. * ,
GROUP_CONCAT( DISTINCT CONCAT_WS( '~', replies.reply, replies.time ) ) AS Replies,
GROUP_CONCAT( DISTINCT CONCAT_WS( '~', review_images.image_title, review_images.image_location ) ) AS ReviewImages
FROM reviews
LEFT JOIN replies ON reviews.id = replies.review_id
LEFT JOIN review_images ON reviews.id = review_images.review_id
WHERE reviews.product_id = 1
GROUP BY reviews.id

Now the PROBLEM that I'm facing is : 现在我面临的问题是:

Because I'm using GROUP_CONCAT, there is a limit to the data it can hold which is in the variable GROUP_CONCAT_MAX_LEN , so as I'm concatenating the replies given by the users, it could go very very long and can possibly exceed the memory defined. 因为我使用的是GROUP_CONCAT,所以它可以保存的数据存在一个限制,该限制在变量GROUP_CONCAT_MAX_LEN ,因此当我串联用户给出的答复时,它可能会非常长,并且可能超出定义的内存。 I know I can change the value of GROUP_CONCAT_MAX_LEN for current session, but still there is a limitation to it that at some point in time, the query may fail or unable to fetch complete results. 我知道我可以更改当前会话的GROUP_CONCAT_MAX_LEN的值,但是它仍然有一个局限性,在某个时间点,查询可能会失败或无法获取完整的结果。

How can I modify my query so as not to use GROUP_CONCAT and still get results expected. 如何修改查询,以免使用GROUP_CONCAT并仍然得到预期的结果。

POSSIBLE SOLUTION : 可能的解决方案:

Simply using LEFT JOINS, which creates duplicate rows for every new result in the last column and which makes it hard to traverse in php ? 简单地使用LEFT JOINS,它为最后一列中的每个新结果创建重复的行,这使得在php难以遍历? Any suggestions? 有什么建议么?

I see this question is not getting enough response from SO members. 我看到这个问题没有从SO成员那里得到足够的答复。 But I've been looking for the solution and searching about concepts since last to last week. 但是从上周到上周,我一直在寻找解决方案并搜索概念。 Still no luck. 仍然没有运气。 Hope some of you PROs can help me out. 希望你们中的一些专业人士能帮助我。 Thanks in advance. 提前致谢。

You can't avoid creating a temporary table when your GROUP BY clause references columns from two different tables. 当您的GROUP BY子句引用来自两个不同表的列时,您将避免创建临时表。

The only way to avoid the temporary table in this query is to store a denormalized version of the data in one table, and index the two columns you're grouping by. 避免此查询中的临时表的唯一方法是在一个表中存储数据的非规范化版本,并为要分组的两列建立索引。


Another way you can simplify and get results in a format that's easier to work with in PHP is to do multiple queries, without GROUP BY. 您可以简化并以更易于在PHP中使用的格式获取结果的另一种方法是,无需使用GROUP BY即可进行多个查询。

First get the reviews. 首先获得评论。 Example is in PHP & PDO, but the principle applies to any language. 示例在PHP和PDO中,但是原理适用于任何语言。

$review_stmt = $pdo->query("
    SELECT reviews.*,
    FROM reviews
    WHERE reviews.product_id = 1");

Arrange them in an associative array keyed by the review_id. 将它们排列在一个由review_id键控的关联数组中。

$reviews = array();
while ($row => $review_stmt->fetch(PDO::FETCH_ASSOC)) {
    $reviews[$row['d']] = $row;
}

Then get the replies and append them to an array using the key 'replies'. 然后获取答复,并使用键“答复”将其附加到数组中。 Use INNER JOIN instead of LEFT JOIN, because it's okay if there are no replies. 使用INNER JOIN而不是LEFT JOIN,因为没有回复就可以。

$reply_stmt = $pdo->query("
    SELECT replies.*
    FROM reviews
    INNER JOIN replies ON reviews.id = replies.review_id
    WHERE reviews.product_id = 1");
while ($row = $reply_stmt->fetch(PDO::FETCH_ASSOC)) {
    $reviews[$row['review_id']]['replies'][] = $row; 
}

And do the same for review_images. 并对review_images执行相同的操作。

$reply_stmt = $pdo->query("
    SELECT review_images.*
    FROM reviews
    INNER JOIN review_images ON reviews.id = review_images.review_id
    WHERE reviews.product_id = 1");
while ($row = $reply_stmt->fetch(PDO::FETCH_ASSOC)) {
    $reviews[$row['review_id']]['review_images'][] = $row; 
}

The end result is an array of reviews, which contains elements which are nested arrays for related replies and images respectively. 最终结果是一个评论数组,其中包含元素,它们是分别用于相关回复和图像的嵌套数组。

The efficiency of running simpler queries can make up for the extra work of running three queries. 运行简单查询的效率可以弥补运行三个查询的额外工作。 Plus you don't have to write code to explode() the group-concatted strings. 另外,您不必编写代码即可explode()包含组的字符串。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM