繁体   English   中英

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

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

这是我在表格上使用的查询: productsreviewsrepliesreview_images

查询:

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;

架构

产品:

id  |  name  |  product_details....

评论:

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

回覆:

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

评论图片:

id  |  review_id  |  image_title  |  image_location  | ...

指标

产品:

主键-ID

评论:

主键-ID

外键-product_id(产品表中的id)

外键-用户名(“用户名”为“用户”表)

回覆:

主键-ID

外键-review_id(id为“评论”表)

外键-用户名(“用户名”为“用户”表)

评论图片:

主键-ID

外键-review_id(id为“评论”表)


说明查询:

id | select_type | 桌子 | 类型 可能的钥匙 | | 额外

1 | 简单 产品| 索引| 空| 1 | 使用索引; 使用临时; 使用文件排序

1 | 简单 评论| 全部| product_id | 4 | 在哪里使用 使用联接缓冲区(块嵌套循环)

1 | 简单 回复| 参考| review_id | 1 | 空值

1 | 简单 review_images | 全部| review_id | 5 | 在哪里使用 使用联接缓冲区(块嵌套循环)

我不知道这有什么问题,它需要使用filesort并创建一个临时表?

以下是一些分析结果:

开孔台140 µs

初始化139 µs

系统锁定34 µs

优化21 µs

统计量106 µs

准备146 µs

创建Tmp表13.6毫秒

排序结果27 µs

执行11 µs

发送数据11.6毫秒

创建排序索引1.4毫秒

结束89 µs

删除Tmp表8.9毫秒

结束34 µs

查询结束25 µs

闭合台66 µs

释放项目41 µs

删除Tmp表1.4毫秒

释放项目46 µs

删除Tmp表1.2毫秒

释放项目203 µs

清理55 µs


从解释和概要分析结果来看,很明显,已创建临时表以产生结果。 如何优化此查询以获得相似的结果和更好的性能,并避免创建临时表?

帮助将不胜感激。 提前致谢。

编辑

建立表格

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

编辑

我简化了上面的查询,现在它不会创建临时表。 @Bill Karwin提到的唯一原因是我在联接的第二个表上使用了GROUP BY

简化查询:

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

现在我面临的问题是:

因为我使用的是GROUP_CONCAT,所以它可以保存的数据存在一个限制,该限制在变量GROUP_CONCAT_MAX_LEN ,因此当我串联用户给出的答复时,它可能会非常长,并且可能超出定义的内存。 我知道我可以更改当前会话的GROUP_CONCAT_MAX_LEN的值,但是它仍然有一个局限性,在某个时间点,查询可能会失败或无法获取完整的结果。

如何修改查询,以免使用GROUP_CONCAT并仍然得到预期的结果。

可能的解决方案:

简单地使用LEFT JOINS,它为最后一列中的每个新结果创建重复的行,这使得在php难以遍历? 有什么建议么?

我看到这个问题没有从SO成员那里得到足够的答复。 但是从上周到上周,我一直在寻找解决方案并搜索概念。 仍然没有运气。 希望你们中的一些专业人士能帮助我。 提前致谢。

当您的GROUP BY子句引用来自两个不同表的列时,您将避免创建临时表。

避免此查询中的临时表的唯一方法是在一个表中存储数据的非规范化版本,并为要分组的两列建立索引。


您可以简化并以更易于在PHP中使用的格式获取结果的另一种方法是,无需使用GROUP BY即可进行多个查询。

首先获得评论。 示例在PHP和PDO中,但是原理适用于任何语言。

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

将它们排列在一个由review_id键控的关联数组中。

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

然后获取答复,并使用键“答复”将其附加到数组中。 使用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; 
}

并对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; 
}

最终结果是一个评论数组,其中包含元素,它们是分别用于相关回复和图像的嵌套数组。

运行简单查询的效率可以弥补运行三个查询的额外工作。 另外,您不必编写代码即可explode()包含组的字符串。

暂无
暂无

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

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