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

时间:2014-03-30 16:51:54

标签: mysql query-optimization

这是我在桌子上使用的查询: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  | ...

索引

产品:

PRIMARY KEY - id

评论

PRIMARY KEY - id

FOREIGN KEY - product_id(id IN products table)

FOREIGN KEY - 用户名(用户名IN用户表)

回复:

PRIMARY KEY - id

FOREIGN KEY - review_id(id IN review table)

FOREIGN KEY - 用户名(用户名IN用户表)

查看图片:

PRIMARY KEY - id

FOREIGN KEY - review_id(id IN review table)


解释查询:

id | select_type | 表格 | 输入 | possible_keys | |的额外

1 |简单|产品|指数| null | 1 |使用索引;使用临时;使用filesort

1 |简单|评论|所有| product_id | 4 |用在哪里;使用连接缓冲区(块嵌套循环)

1 |简单|回复| ref | 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 ms

排序结果27μs

执行11μs

发送数据11.6毫秒

创建排序索引1.4毫秒

结束89μs

删除Tmp表8.9 ms

结束34μs

查询结束25μs

关闭表66μs

释放物品41μs

删除Tmp表1.4毫秒

释放物品46μs

删除Tmp表1.2 ms

释放物品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成员的足够回应。但自从上周到上周以来,我一直在寻找解决方案和搜索概念。仍然没有运气。希望你们中的一些人可以帮助我。提前谢谢。

1 个答案:

答案 0 :(得分:3)

当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()组连接的字符串。