【问题标题】:Query Optimization (WHERE, GROUP BY, LEFT JOINs)查询优化(WHERE、GROUP BY、LEFT JOIN)
【发布时间】:2013-04-17 21:08:05
【问题描述】:

我正在使用 InnoDB。

查询、解释和索引

选择 故事。*, 计数(cmets.id)作为 cmets, GROUP_CONCAT( DISTINCT 分类 2.name SEPARATOR ';' ) 作为分类名称, GROUP_CONCAT( 不同的图像.id ORDER BY images.position, images.id 分隔符';' ) 作为 images_id, GROUP_CONCAT( 不同的图像.caption ORDER BY images.position, images.id 分隔符';' ) 作为 images_caption, GROUP_CONCAT( 不同的图像.thumbnail ORDER BY images.position, images.id 分隔符';' ) 作为 images_thumbnail, GROUP_CONCAT( 不同的图像.medium ORDER BY images.position, images.id 分隔符';' ) 作为图像_媒体, GROUP_CONCAT( 不同的图像.大 ORDER BY images.position, images.id 分隔符';' ) AS images_large, GROUP_CONCAT( DISTINCT users.id ORDER BY users.id SEPARATOR ';' ) 作为作者 ID, GROUP_CONCAT( DISTINCT users.display_name ORDER BY users.id SEPARATOR ';' ) 作为作者显示名称, GROUP_CONCAT( 不同的 users.url ORDER BY users.id SEPARATOR ';' ) 作为作者 URL 从 故事 LEFT JOIN 分类 ON stories.id = 分类.story_id LEFT JOIN 分类 AS 分类2 ON stories.id = classifications2.story_id 左连接 cmets ON stories.id = cmets.story_id 左连接 image_story ON stories.id = image_story.story_id 左连接图像 ON images.id = image_story.`image_id` 左连接 author_story ON stories.id = author_story.story_id 左加入用户 ON users.id = author_story.author_id WHERE 分类。`name` LIKE 'Home:Top%' AND stories.status = 1 GROUP BY stories.id 按分类排序。`名称`,分类。`位置` +----+-------------+------------------+--------+-- -------------+---------+---------+--------------- ---------+--------+------------------------------- ---------------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+------------------+--------+-- -------------+---------+---------+--------------- ---------+--------+------------------------------- ---------------+ | 1 |简单 |故事 |参考 |状态 |状态 | 1 |常量 | 434792 |使用哪里;使用临时的;使用文件排序 | | 1 |简单 |分类 |参考 |故事ID |故事ID | 4 |故事.id | 1 |使用位置 | | 1 |简单 |分类2 |参考 |故事ID |故事ID | 4 |故事.id | 1 |使用位置 | | 1 |简单 |厘米 |参考 |故事ID |故事ID | 8 |故事.id | 6 |使用哪里;使用索引 | | 1 |简单 |图片故事 |参考 |故事ID |故事ID | 4 |故事.id | 1 |空 | | 1 |简单 |图片 | eq_ref |初级 |初级 | 4 | image_story.image_id | 1 |空 | | 1 |简单 |作者故事 |参考 |故事ID |故事ID | 4 |故事.id | 1 |使用位置 | | 1 |简单 |用户 | eq_ref |初级 |初级 | 4 | author_story.author_id | 1 |使用位置 | +----+-------------+------------------+--------+-- -------------+---------+---------+--------------- ---------+--------+------------------------------- ---------------+ +-----------------+------------+-------------+---- ----------+-------------+------------+------------- +---------+--------+------+------------+ |表 |非唯一 |键名 | Seq_in_index |列名 |整理 |基数|子部分 |包装 |空 |索引类型 | +-----------------+------------+-------------+---- ----------+-------------+------------+------------- +---------+--------+------+------------+ |故事 | 0 |初级 | 1 |编号 |一个 | 869584 |空 |空 | | BTREE | |故事 | 1 | created_at | 1 | created_at |一个 | 434792 |空 |空 | | BTREE | |故事 | 1 |来源 | 1 |来源 |一个 | 2 |空 |空 |是 | BTREE | |故事 | 1 | source_id | 1 | source_id |一个 | 869584 |空 |空 |是 | BTREE | |故事 | 1 |类型 | 1 |类型 |一个 | 2 |空 |空 | | BTREE | |故事 | 1 |状态 | 1 |状态 |一个 | 2 |空 |空 | | BTREE | |故事 | 1 |类型状态 | 1 |类型 |一个 | 2 |空 |空 | | BTREE | |故事 | 1 |类型状态 | 2 |状态 |一个 | 2 |空 |空 | | BTREE | |分类 | 0 |初级 | 1 |编号 |一个 | 207 |空 |空 | | BTREE | |分类 | 1 |故事ID | 1 |故事ID |一个 | 207 |空 |空 | | BTREE | |分类 | 1 |姓名 | 1 |姓名 |一个 | 103 |空 |空 | | BTREE | |分类 | 1 |姓名 | 2 |职位 |一个 | 207 |空 |空 |是 | BTREE | |厘米 | 0 |初级 | 1 |编号 |一个 | 239336 |空 |空 | | BTREE | |厘米 | 1 |状态 | 1 |状态 |一个 | 2 |空 |空 | | BTREE | |厘米 | 1 |日期 | 1 |日期 |一个 | 239336 |空 |空 | | BTREE | |厘米 | 1 |故事ID | 1 |故事ID |一个 | 39889 |空 |空 | | BTREE | +-----------------+------------+-------------+---- ----------+-------------+------------+------------- +---------+--------+------+------------+

查询时间

平均需要0.035 seconds 才能运行。

如果我只删除GROUP BY,平均时间会下降到0.007

如果我只删除 stories.status=1 过滤器,平均时间会下降到 0.025。这个好像很容易优化。

如果我只删除LIKE 过滤器和ORDER BY 子句,平均时间会下降到0.006

更新 1:2013-04-13

通过答案,我的理解得到了多方面的提高。

我向author_storyimages_story 添加了索引,这似乎改进了对0.025 秒的查询,但出于某种奇怪的原因,EXPLAIN 计划看起来好多了。此时删除ORDER BY 将查询降低到0.015 秒并同时删除ORDER BYGROUP BY 将查询性能提高到0.006。我是现在要关注的两件事吗?如果需要,我可能会将ORDER BY 移到应用逻辑中。

这里是修改后的EXPLAININDEXES

+----+-------------+------------------+--------+-- -------------------------------+----------+-------- --+--------------+------+-------------- -------------------------------------------------------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+------------------+--------+-- -------------------------------+----------+-------- --+--------------+------+-------------- -------------------------------------------------------+ | 1 |简单 |分类 |范围 | story_id,名称 |姓名 | 102 |空 | 14 |使用索引条件;使用临时的;使用文件排序 | | 1 |简单 |故事 | eq_ref |初级,状态 |初级 | 4 |分类.story_id | 1 |使用位置 | | 1 |简单 |分类2 |参考 |故事ID |故事ID | 4 |故事.id | 1 |使用位置 | | 1 |简单 |作者故事 |参考 | author_id,story_id,author_story |故事ID | 4 |故事.id | 1 |使用索引条件 | | 1 |简单 |用户 | eq_ref |初级 |初级 | 4 | author_story.author_id | 1 |使用位置 | | 1 |简单 |厘米 |参考 |故事ID |故事ID | 8 |故事.id | 8 |使用哪里;使用索引 | | 1 |简单 |图片故事 |参考 | story_id,story_id_2 |故事ID | 4 |故事.id | 1 |空 | | 1 |简单 |图片 | eq_ref | PRIMARY,position_id |初级 | 4 | image_story.image_id | 1 |空 | +----+-------------+------------------+--------+-- -------------------------------+----------+-------- --+--------------+------+-------------- -------------------------------------------------------+ +-----------------+------------+------------------ --+----------------+--------------+------------+------ --------+----------+--------+------+------------+-- --------+---------------+ |表 |非唯一 |键名 | Seq_in_index |列名 |整理 |基数|子部分 |包装 |空 |索引类型 |评论 |索引评论 | +-----------------+------------+------------------ --+----------------+--------------+------------+------ --------+----------+--------+------+------------+-- --------+---------------+ |作者故事 | 0 |初级 | 1 |编号 |一个 | 220116 |空 |空 | | BTREE | | | |作者故事 | 0 |故事作者 | 1 |故事ID |一个 | 220116 |空 |空 | | BTREE | | | |作者故事 | 0 |故事作者 | 2 |作者_id |一个 | 220116 |空 |空 | | BTREE | | | |作者故事 | 1 |作者_id | 1 |作者_id |一个 |第2179章空 |空 | | BTREE | | | |作者故事 | 1 |故事ID | 1 |故事ID |一个 | 220116 |空 |空 | | BTREE | | | |图片故事 | 0 |初级 | 1 |编号 |一个 | 148902 |空 |空 | | BTREE | | | |图片故事 | 0 |故事图片 | 1 |故事ID |一个 | 148902 |空 |空 | | BTREE | | | |图片故事 | 0 |故事图片 | 2 | image_id |一个 | 148902 |空 |空 | | BTREE | | | |图片故事 | 1 |故事ID | 1 |故事ID |一个 | 148902 |空 |空 | | BTREE | | | |图片故事 | 1 | image_id | 1 | image_id |一个 | 148902 |空 |空 | | BTREE | | | |分类 | 0 |初级 | 1 |编号 |一个 | 257 |空 |空 | | BTREE | | | |分类 | 1 |故事ID | 1 |故事ID |一个 | 257 |空 |空 | | BTREE | | | |分类 | 1 |姓名 | 1 |姓名 |一个 | 128 |空 |空 | | BTREE | | | |分类 | 1 |姓名 | 2 |职位 |一个 | 257 |空 |空 |是 | BTREE | | | |故事 | 0 |初级 | 1 |编号 |一个 | 962570 |空 |空 | | BTREE | | | |故事 | 1 | created_at | 1 | created_at |一个 | 481285 |空 |空 | | BTREE | | | |故事 | 1 |来源 | 1 |来源 |一个 | 4 |空 |空 |是 | BTREE | | | |故事 | 1 | source_id | 1 | source_id |一个 | 962570 |空 |空 |是 | BTREE | | | |故事 | 1 |类型 | 1 |类型 |一个 | 2 |空 |空 | | BTREE | | | |故事 | 1 |状态 | 1 |状态 |一个 | 4 |空 |空 | | BTREE | | | |故事 | 1 |类型状态 | 1 |类型 |一个 | 2 |空 |空 | | BTREE | | | |故事 | 1 |类型状态 | 2 |状态 |一个 | 6 |空 |空 | | BTREE | | | |厘米 | 0 |初级 | 1 |编号 |一个 | 232559 |空 |空 | | BTREE | | | |厘米 | 1 |状态 | 1 |状态 |一个 | 6 |空 |空 | | BTREE | | | |厘米 | 1 |日期 | 1 |日期 |一个 | 232559 |空 |空 | | BTREE | | | |厘米 | 1 |故事ID | 1 |故事ID |一个 | 29069 |空 |空 | | BTREE | | | |图片 | 0 |初级 | 1 |编号 |一个 | 147206 |空 |空 | | BTREE | | | |图片 | 0 | source_id | 1 | source_id |一个 | 147206 |空 |空 |是 | BTREE | | | |图片 | 1 |职位 | 1 |职位 |一个 | 4 |空 |空 | | BTREE | | | |图片 | 1 | position_id | 1 |编号 |一个 | 147206 |空 |空 | | BTREE | | | |图片 | 1 | position_id | 2 |职位 |一个 | 147206 |空 |空 | | BTREE | | | |用户 | 0 |初级 | 1 |编号 |一个 | 981 |空 |空 | | BTREE | | | |用户 | 0 | users_email_unique | 1 |电子邮件 |一个 | 981 |空 |空 | | BTREE | | | +-----------------+------------+------------------ --+----------------+--------------+------------+------ --------+----------+--------+------+------------+-- --------+---------------+ 选择 故事。*, 计数(cmets.id)作为 cmets, GROUP_CONCAT(DISTINCT users.id ORDER BY users.id SEPARATOR ';') AS authors_id, GROUP_CONCAT(DISTINCT users.display_name ORDER BY users.id SEPARATOR ';') AS authors_display_name, GROUP_CONCAT(DISTINCT users.url ORDER BY users.id SEPARATOR ';') AS authors_url, GROUP_CONCAT(DISTINCT classifications2.name SEPARATOR ';') AS classifications_name, GROUP_CONCAT(DISTINCT images.id ORDER BY images.position,images.id SEPARATOR ';') AS images_id, GROUP_CONCAT(DISTINCT images.caption ORDER BY images.position,images.id SEPARATOR ';') AS images_caption, GROUP_CONCAT(DISTINCT images.thumbnail ORDER BY images.position,images.id SEPARATOR ';') AS images_thumbnail, GROUP_CONCAT(DISTINCT images.medium ORDER BY images.position,images.id SEPARATOR ';') AS images_medium, GROUP_CONCAT(DISTINCT images.large ORDER BY images.position,images.id SEPARATOR ';') AS images_large 从 分类 INNER JOIN 故事 ON stories.id = 分类.story_id LEFT JOIN 分类 AS 分类2 ON stories.id = classifications2.story_id 左连接 cmets ON stories.id = cmets.story_id 左连接 image_story ON stories.id = image_story.story_id 左连接图像 ON images.id = image_story.`image_id` INNER JOIN author_story ON stories.id = author_story.story_id INNER JOIN 用户 ON users.id = author_story.author_id WHERE 分类。`name` LIKE 'Home:Top%' AND stories.status = 1 GROUP BY stories.id 按空排序

更新 2:2013-04-14

我注意到另一件事。如果我不选择 stories.content (LONGTEXT) 和 stories.content_html (LONGTEXT),则查询将从 0.015 秒下降到 0.006 秒。现在我正在考虑是否可以不使用 contentcontent_html 或用其他东西替换它们。

我在上面的 2013-04-13 更新中更新了查询、索引和解释,而不是在这个更新中重新发布,因为它们是次要的和增量的。该查询仍在使用filesort。我无法摆脱GROUP BY,但已经摆脱了ORDER BY

更新 3:2013-04-16

根据要求,我从 image_storyauthor_story 中删除了 stories_id INDEXES,因为它们是多余的。结果是解释的输出仅更改为显示possible_keys 已更改。不幸的是,它仍然没有显示Using Index 优化。

还将LONGTEXT 更改为TEXT,现在获取LEFT(stories.content, 500) 而不是stories.content,这在查询执行时间上产生了非常显着的差异。

+----+-------------+------------------+--------+-- ---------------+--------------+-------- --+--------------+------+-------------- -------------------------------------------------- ------+ |编号 |选择类型 |表|类型 |可能的键 |关键 | key_len |参考 |行 |额外 | +----+-------------+------------------+--------+-- ---------------+--------------+-------- --+--------------+------+-------------- -------------------------------------------------- ------+ | 1 |简单 |分类 |参考 |故事ID,姓名,姓名位置|姓名 | 102 |常量 | 10 |使用索引条件;使用哪里;使用临时的;使用文件排序 | | 1 |简单 |故事 | eq_ref |初级,状态 |初级 | 4 |分类.story_id | 1 |使用位置 | | 1 |简单 |分类2 |参考 |故事ID |故事ID | 4 |故事.id | 1 |使用位置 | | 1 |简单 |作者故事 |参考 |故事作者 |故事作者 | 4 |故事.id | 1 |使用哪里;使用索引 | | 1 |简单 |用户 | eq_ref |初级 |初级 | 4 | author_story.author_id | 1 |使用位置 | | 1 |简单 |厘米 |参考 |故事ID |故事ID | 8 |故事.id | 8 |使用哪里;使用索引 | | 1 |简单 |图片故事 |参考 |故事图片 |故事图片 | 4 |故事.id | 1 |使用索引 | | 1 |简单 |图片 | eq_ref | PRIMARY,position_id |初级 | 4 | image_story.image_id | 1 |空 | +----+-------------+------------------+--------+-- ---------------+--------------+-------- --+--------------+------+-------------- -------------------------------------------------- ------+ innodb_buffer_pool_size 134217728 TABLE_NAME INDEX_LENGTH image_story 10010624 image_story 4556800 图片故事 0 TABLE_NAME INDEX_NAMES 大小 黎明/image_story story_image 13921

【问题讨论】:

  • 你能发布解释计划吗?
  • 同时显示您的索引。
  • 您预计什么时间可以满足查询要求?
  • @newtover,就个人而言,如果它可以达到 0.01 秒以下,那真的会让我放心,因为这个查询每秒可以运行几十次,但这可能是我的一厢情愿。我会尽我所能。
  • 是否可以获得带有测试数据的转储?对结果使用分页怎么样?

标签: mysql optimization group-by left-join


【解决方案1】:

我可以立即看到两个优化机会:

将 OUTER JOIN 更改为 INNER JOIN

您的查询目前正在扫描 434792 个故事,假设并非每个故事都有与“Home:Top%”匹配的分类,您应该能够更好地缩小范围。最好使用索引来查找您要查找的分类,然后然后查找匹配的故事。

但是您使用 LEFT OUTER JOIN 进行分类,这意味着所有故事都将被扫描,无论它们是否具有匹配的分类。然后,您通过在WHERE 子句中对分类设置条件来击败它,有效地强制要求有一个与您的模式匹配的分类LIKE。所以它不再是外连接,而是内连接。

如果您将分类表放在首位,并使其成为内连接,优化器将使用它来将搜索范围缩小到具有匹配分类的故事。

. . . 
FROM
  classifications
  INNER JOIN stories
    ON stories.id = classifications.story_id 
. . .

优化器应该能够确定何时对表重新排序是有利的,因此您可能不必更改查询中的顺序。但在这种情况下,您确实需要使用INNER JOIN

添加复合索引

您的交集表 image_story 和 author_story 没有复合索引。将复合索引添加到多对多关系的交集表中通常是一个很大的优势,这样它们就可以执行连接并获得“使用索引”优化。

ALTER TABLE image_story ADD UNIQUE KEY imst_st_im (story_id, image_id);
ALTER TABLE author_story ADD UNIQUE KEY aust_st_au (story_id, author_id);

你的 cmets 和更新:

我不确定您是否正确创建了新索引。您的索引转储没有显示列,并且根据更新的 EXPLAIN,没有使用新索引,我希望会发生这种情况。使用新索引会导致 EXPLAIN 的额外字段中出现“Using index”,这应该有助于提高性能。

如您所示,每个表的 SHOW CREATE TABLE 输出将比索引转储(没有列名)更完整。

创建索引后,您可能必须对这些表中的每一个运行一次 ANALYZE TABLE。此外,多次运行查询,以确保索引在缓冲池中。这个表是 InnoDB 还是 MyISAM?

我还在您的 EXPLAIN 输出中注意到 rows 列显示被触摸的行数要少得多。这是一种进步。

您真的需要ORDER BY 吗?如果您使用ORDER BY NULL,您应该能够摆脱“使用文件排序”,这可能会提高性能。


您的更新:

您仍然没有从 image_storyauthor_story 表中获得“使用索引”优化。我的一个建议是消除冗余索引:

ALTER TABLE image_story DROP KEY story_id;
ALTER TABLE author_story DROP KEY story_id;

原因是任何可以从 story_id 上的单列索引中受益的查询也可以从 (story_id,image_id) 上的两列索引中受益。消除冗余索引有助于优化器做出更好的决策(以及节省一些空间)。这就是 pt-duplicate-key-checker 这样的工具背后的理论。

我还会检查以确保您的缓冲池足够大以容纳您的索引。您不希望索引在查询期间分页进出缓冲池。

SHOW VARIABLES LIKE 'innodb_buffer_pool_size'

检查 image_story 表的索引大小:

SELECT TABLE_NAME, INDEX_LENGTH FROM INFORMATION_SCHEMA.TABLES 
WHERE TABLE_NAME = 'image_story';

并将其与这些索引中当前驻留在缓冲池中的数量进行比较:

SELECT TABLE_NAME, GROUP_CONCAT(DISTINCT INDEX_NAME) AS INDEX_NAMES, SUM(DATA_SIZE) AS SIZE 
FROM INFORMATION_SCHEMA.INNODB_BUFFER_PAGE_LRU 
WHERE TABLE_NAME = '`test`.`image_story`' AND INDEX_NAME <> 'PRIMARY'

当然,把上面的`test`改成你的表所属的数据库名。

该 information_schema 表是 MySQL 5.6 中的新表。我假设您使用的是 MySQL 5.6,因为您的 EXPLAIN 显示“使用索引条件”,这在 MySQL 5.6 中也是新的。

除非我真的需要很长的字符串,否则我根本不使用 LONGTEXT。请记住:

  • TEXT 最多可容纳 64KB
  • MEDIUMTEXT 最多可容纳 16MB
  • LONGTEXT 最多可容纳 4GB

【讨论】:

  • 完全同意这一点。我还将调查是否所有故事在 author_story 上都有作者条目,以及 author_story 上的所有作者条目是否都有用户匹配记录。他们应该这样做似乎是合乎逻辑的,在这种情况下,这些连接也可以更改为 INNER JOIN。
  • 是的,如果author_story 中存在一行,那么它应该总是引用storiesusers 中的现有行。如果这不是真的,那么您在author_story 中有一个孤立的行,这是一个异常情况。但是你可以很容易地在stories 中有一行而在author_story 中没有行,因此你需要外连接。将外连接转换为classifications 表更为重要,因为它可以为优化器提供重新排序表的线索。
  • 索引极大地改变了 EXPLAIN 计划,但奇怪的是优化器顿悟了,因为它开始将分类表放在首位。鉴于此,将分类放在首位对性能没有影响,但我会保持这种方式,因为它更有意义。查询比较和解释:hastebin.com/hifokahuyo.m
  • @Kickstart,不,不是“所有故事在 author_story 上都有作者条目”。但根据比尔的解释,我认为如果所有 author_story 条目都有故事/作者记录,我可以进行内部连接。作者 INNER JOIN 的性能没有改变,但从逻辑上讲这是有道理的,所以我会在进一步测试后更新我的查询。
  • 对于 author_story 和用户的内部连接,查询和解释:hastebin.com/delokuhoka.m
【解决方案2】:

当您使用 MYSQL 时,您可以利用 Straight_join

STRAIGHT_JOIN forces the optimizer to join the tables in the order in which they are listed in the FROM clause. You can use this to speed up a query if the optimizer joins the tables in nonoptimal order

还有一个改进范围是过滤表 stories 的数据,因为您只需要状态为 1 的数据

因此,在表单子句中,不要添加整个 stories 表,而是添加唯一需要的记录,因为您的查询计划显示 434792 行和 classification 表相同

FROM
   (SELECT 
       * 
   FROM
       STORIES
   WHERE 
       STORIES.status = 1) stories
LEFT JOIN
   (SELECT 
       * 
   FROM
       classifications
   WHERE
       classifications.`name` LIKE 'Home:Top%') classifications
ON stories.id = classifications.story_id 

还有一个建议,您可以增加sort_buffer_size,因为您显示为order bygroup by,但请注意随着每个会话的缓冲区大小增加而增加缓冲区大小。

此外,如果可能的话,您可以在您的应用程序中订购您的记录,因为您已经提到删除 order by 子句可以提高原始时间的 1/6 部分...

编辑

image_story 表和author_story.story_idauthor_story 表添加索引,因为这些列用于连接

还必须在使用时创建images.position, images.id 上的索引。

编辑 16/4

我认为你几乎优化了你的查询,看到你更新......

仍然有一个地方可以改进,就像 BillKarwin 提到的那样使用适当的数据类型...... 您可以使用 ENUMTINYINT 类型来输入状态等没有任何增长范围的列,这将帮助您优化查询性能还有你的表的存储性能......

希望这会有所帮助....

【讨论】:

  • 这很有意义,但我肯定在这里遗漏了一些东西。如您在此处显示的那样添加嵌套故事(甚至分类)选择会使查询减慢到我不得不中止它的地步 - hastebin.com/dilisusotu.sql
  • 看到解释计划似乎没有正确使用索引尝试此查询hastebin.com/yecohafeqi.sql....希望它有所帮助....感谢男人向我介绍了这样一个很棒的网站,hastebin 喜欢它.... :)
  • 查询还是不行,可以看到EXPLAIN@hastebin.com/nunuleviyo.rb——返回的行数非常多。我尝试改变周围的顺序,但事情仍然很慢。不知道为什么。 (也喜欢 hastebin)
【解决方案3】:

计算

GROUP_CONCAT(DISTINCT classifications2.name SEPARATOR ';')

可能是最耗时的操作,因为classifications 是一个大表,并且由于所有连接,要处理的行数成倍增加。

因此,我建议使用 临时表 来获取该信息。 另外,为了避免两次计算 LIKE 条件(一次用于临时表,一次用于“真实”查询),我还将为此创建一个临时表。

您的原始查询,在一个非常简化的版本中(没有图像和用户表,以便更容易阅读)是:

SELECT
    stories.*,
    count(DISTINCT comments.id) AS comments,
    GROUP_CONCAT(DISTINCT classifications2.name ORDER BY 1 SEPARATOR ';' )
        AS classifications_name
FROM
    stories
    LEFT JOIN classifications
        ON stories.id = classifications.story_id
    LEFT JOIN classifications AS classifications2
        ON stories.id = classifications2.story_id
    LEFT JOIN comments
        ON stories.id = comments.story_id
WHERE
    classifications.`name` LIKE 'Home:Top%'
        AND stories.status = 1
GROUP BY stories.id
    ORDER BY stories.id, classifications.`name`, classifications.`positions`;

我将用以下查询替换它,使用临时表 _tmp_filtered_classifications(名称为 LIKE Home:Top%' 的分类 ID)和 _tmp_classifications_of_story(对于 _tmp_filtered_classifications 中“包含”的每个故事 ID,所有分类名称):

DROP TABLE IF EXISTS `_tmp_filtered_classifications`;

CREATE TEMPORARY TABLE _tmp_filtered_classifications
    SELECT id FROM classifications WHERE name LIKE 'Home:Top%';

DROP TABLE IF EXISTS `_tmp_classifications_of_story`;

CREATE TEMPORARY TABLE _tmp_classifications_of_story ENGINE=MEMORY
    SELECT stories.id AS story_id, classifications2.name
        FROM 
        _tmp_filtered_classifications
        INNER JOIN classifications        
            ON _tmp_filtered_classifications.id=classifications.id
        INNER JOIN stories
            ON stories.id = classifications.story_id
        LEFT JOIN classifications AS classifications2
            ON stories.id = classifications2.story_id
        GROUP BY 1,2;

SELECT
    stories.*,
    count(DISTINCT comments.id) AS comments,
    GROUP_CONCAT(DISTINCT classifications2.name ORDER BY 1 SEPARATOR ';')
        AS classifications_name
FROM
    _tmp_filtered_classifications
    INNER JOIN classifications        
        ON _tmp_filtered_classifications.id=classifications.id
    INNER JOIN stories
        ON stories.id = classifications.story_id
    LEFT JOIN _tmp_classifications_of_story AS classifications2
        ON stories.id = classifications2.story_id
    LEFT JOIN comments
        ON stories.id = comments.story_id
WHERE
    stories.status = 1
GROUP BY stories.id
    ORDER BY stories.id, classifications.`name`, classifications.`positions`;

请注意,我在您的查询中添加了更多“order by”子句,以检查两个查询是否提供相同的结果(使用差异)。我还将 count(comments.id) 更改为 count(DISTINCT comments.id) 否则查询计算的 cmets 数是错误的(同样,因为连接乘以行数)。

【讨论】:

  • 这确实确实提高了性能,但并不一致。有时查询花费了两倍的时间(可能是由于内存管理或分页或其他原因)。我已经发布了基于其他一些改进的更新的解释计划,所以现在唯一的罪魁祸首是 GROUP BY 和 ORDER BY。不过感谢您的解释,我会牢记这一点,以备将来优化。
【解决方案4】:

我不知道你要实验的数据的所有细节,但我知道你应该首先执行匹配最少数据量的操作,从而为后续操作消除最多数据量。

根据您的整体查询的复杂程度,您可能无法以这种方式重新排序操作。但是,您可以执行两个单独的查询,其中第一个只是消除绝对不需要的数据,然后将其结果提供给第二个查询。其他人建议使用临时表,这是处理这种情况的好方法。

如果您需要对此策略的任何说明,请告诉我。

** 更新:当每个操作与其他操作匹配大约相同百分比的数据时使用的类似策略是分别为每个操作计时,然后首先运行使用最少时间的操作。一些搜索操作比其他操作更快,如果所有其他因素都相同,最快的应该是第一个。这样,较慢的搜索操作将使用较少的数据,并且总体结果将更高。

【讨论】:

    【解决方案5】:

    我敢打赌,LIKE 条件是您要求的最糟糕的事情。
    你确定你必须这样做吗?

    4 个步骤:

    1. classifications 表上创建IsHomeTop bool 索引列
    2. 运行UPDATE classifications SET IsTopHome = 1 WHERE NAME LIKE 'Home:Top%'
    3. 使用WHERE classifications.IsTopHome == 1 运行您的初始查询
    4. 享受

    您的查询对于让LIKE 运算符降低您的性能来说太重要了。
    如果stories 更新很多,我认为您的classifications 表不会是这种情况。所以给你一个机会,铲除LIKE运营商。

    【讨论】:

      【解决方案6】:

      您可以在这里尝试一些方法:

      1) create covering index on classifications.`name`
      

      您可以通过创建覆盖索引来加快查询速度。

      覆盖索引是指查询中选择的所有字段都被索引覆盖的情况,在这种情况下InnoDB(不是MyISAM)永远不会读取表中的数据,而只会使用索引中的数据,显着加快选择速度。

      创建表classifications ( KEY class_namename,...所有列) ) ENGINE=InnoDB 默认字符集=utf8;

      2) 而不是classifications.name LIKE 'Home:Top%' 使用locate('Home:Top',classifications.name)

      【讨论】:

        猜你喜欢
        • 2015-09-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-05-14
        • 1970-01-01
        • 1970-01-01
        • 2016-05-01
        相关资源
        最近更新 更多