【问题标题】:MySQL distinct+sort query performance issueMySQL distinct+sort 查询性能问题
【发布时间】:2012-06-08 13:57:22
【问题描述】:

问题

以下查询需要超过 30 秒才能运行,除非:

  • 我删除了排序(查询然后
  • 我删除了 distinct 关键字:(查询然后
  • 开始删除连接(查询然后

问题

如何让这个查询在 1 秒内运行。必需:如何获得具有相关数据的唯一会议列表,如下面的联接所述,包括某种类型。

相关数据既用于确定是否存在相关字段,也用于执行 GROUP_CONCAT 操作 - 因此需要对同一个预定项目表进行 3 个不同的连接。

提前感谢您的任何帮助和建议!几个小时以来,我一直在努力解决这个问题!

查询

SELECT

DISTINCT( `meetings`.`id` ) AS `meeting_id`,
`meetings`.`uid` AS meeting_uid,
`meetings_SERV`.`id` AS meetings_SERV_id, 
`meetings_TRANSP`.`id` AS meetings_TRANSP_id, 
`meetings_ACCO`.`id` AS meetings_ACCO_id, 
`meetings_BOOKEDITEMS`.`id` AS meetings_BOOKEDITEMS_id

FROM `meetings` AS meetings 

LEFT OUTER JOIN `bookeditems` AS `meetings_SERV` 
ON `meetings`.`uid` = `meetings_SERV`.`meeting_uid`
AND 'SER' = `meetings_SERV`.`item_type` 

LEFT OUTER JOIN `bookeditems` AS `meetings_TRANSP` 
ON `meetings`.`uid` = `meetings_TRANSP`.`meeting_uid`
AND 'TRA' = `meetings_TRANSP`.`item_type` 

LEFT OUTER JOIN `bookeditems` AS `meetings_ACCO` 
ON `meetings`.`uid` = `meetings_ACCO`.`meeting_uid`
AND 'ACC' = `meetings_ACCO`.`item_type` 

LEFT OUTER JOIN `bookeditems` AS `meetings_BOOKEDITEMS` 
ON `meetings`.`uid` = `meetings_BOOKEDITEMS`.`meeting_uid` 

ORDER BY `meetings`.`datetime`

LIMIT 0, 50

表定义

CREATE TABLE IF NOT EXISTS `bookeditems` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `meeting_uid` varchar(256) NOT NULL,
  `item_type` varchar(256) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `meeting_uid` (`meeting_uid`(255)),
  KEY `index1` (`meeting_uid`(255),`item_type`(255))
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=5889 ;

CREATE TABLE IF NOT EXISTS `meetings` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` varchar(256) NOT NULL,
  `datetime` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `uid` (`uid`(255)),
  KEY `datetime` (`datetime`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=7487 ;

解释选择的结果

id | select_type | table                | type | possible_keys      | key         | key_len | ref                      | rows | Extra
-------------------------------------------------------------------------------------------------------------------------------------
1  | SIMPLE      | meetings             | ALL  | NULL               | NULL        | NULL    | NULL                     | 7483 | Using temporary; Using filesort
1  | SIMPLE      | meetings_SERV        | ref  | meeting_uid,index1 | meeting_uid | 767     | test.meetings.uid        | 1    | 
1  | SIMPLE      | meetings_TRANSP      | ref  | meeting_uid,index1 | meeting_uid | 767     | test.meetings.uid        | 1    | 
1  | SIMPLE      | meetings_ACCO        | ref  | meeting_uid,index1 | meeting_uid | 767     | test.meetings.uid        | 1    | 
1  | SIMPLE      | meetings_BOOKEDITEMS | ref  | meeting_uid,index1 | meeting_uid | 767     | test.meetings.uid        | 1    | 

分析结果

starting                      0.000092
checking permissions          0.000003
checking permissions          0.000002
checking permissions          0.000001
checking permissions          0.000001
checking permissions          0.000003
Opening tables                0.000036
System lock                   0.000008
init                          0.000033
optimizing                    0.000005
statistics                    0.000035
preparing                     0.000019
Creating tmp table            0.000165
executing                     0.000004
Copying to tmp table          1.790968
converting HEAP to MyISAM     1.669041
Copying to tmp table on disk  28.32606
Sorting result                0.141737
Sending data                  0.000099
end                           0.000005
removing tmp table            0.022097
end                           0.000014
query end                     0.000008
closing tables                0.000017
freeing items                 0.000779
logging slow query            0.000004
cleaning up                   0.000005

部分解决方案

根据 Eric R. Rath 在下面的帮助,我已经分析了查询,并通过将 max_heap_table_size=256M 和 tmp_table_size=256M 添加到 MySQL 配置中,我已经能够消除执行步骤“将 HEAP 转换为 MyISAM”和“复制到 tmp磁盘上的表”。

虽然这将总执行时间缩短到 2 秒以下,但我仍然不相信这是我能做的一切,如果在查询优化方面有任何其他建议,请告诉我。

按照 max_heap_table_size 和 tmp_table_size 配置进行分析

...
executing                     0.000004
Copying to tmp table          1.790968
Sorting result                0.141737
...

【问题讨论】:

  • Order by 语句总是会大大降低查询速度。您也可以删除 meeting 表上的别名,因为使用别名会强制 MySQL 查看表上的所有字段
  • 能否也粘贴查询的explain 的输出?即:EXPLAIN SELECT ...。这将帮助我们了解查询执行计划的外观以及使用了哪些索引。
  • 尝试在(item_type, meeting_id)上添加索引
  • 你的表是 InnoDB 还是 MyISAM?
  • @ypercube 它已经将其作为索引(我更新了查询,以便 meeting_id 现在是 meeting_uid); InnoDB

标签: mysql performance


【解决方案1】:

我认为 index2 是不必要的,可以删除;它是 index1 的前缀。不过,这不会减少查询时间。

EXPLAIN 输出显示了真正的罪魁祸首:“使用临时文件,使用文件排序”。有时您可以通过让 MySQL 使用用于连接的相同键执行排序来避免这种情况。如果将 index1 更改为 (meeting_id, datetime),它也许可以这样做。如果您需要将 item_type 保留在 index1 中,您可以将其添加为索引中的第三列,或者在查询中包含一个包含所有值的 IN() 子句。

【讨论】:

  • 感谢您的建议 - 将尝试他们。问题实际上是 datetime 不是唯一被排序的列,而是最常被排序的列。此外,为了清楚起见,我更新了问题以包含所有表中的索引。
  • 您可以使用 MySQL 的 profiling 来验证罪魁祸首。运行SET PROFILING=1,然后是您的查询,然后是SHOW PROFILES,找到您的查询,然后是SHOW PROFILE FOR QUERY n。最后一个命令将生成执行查询所需的细粒度步骤表,以及每个步骤所需的时间。
  • 结果是复制到磁盘上的 tmp 表 = 28.3 秒...我已将完整结果包含在问题中。
  • 谢谢!我将 max_heap_table_size=256M & tmp_table_size=256M 添加到配置中,导致“复制到磁盘上的 tmp 表 = 28.3 秒”被替换为“复制到 tmp 表,2.27”;但是我仍然不高兴这是我能做的一切(当你有 10 个人同时击中它时会发生什么 - 你吸收了 2.5G 的 RAM?)。请让我知道你在想什么。还有什么我想念的/可以做的吗?理想情况下,我想摆脱对临时表的需要?我很难相信查询需要
  • "...ORDER BY ... LIMIT N" 查询经常会遇到这个问题。您当前的查询要求 MySQL 1) 将结果限制为满足 WHERE 限制的那些行,2) 对所有行进行排序,以及 3) 返回前 N 个结果。诀窍是修改查询或模式以允许使用相同的索引进行限制和排序。这样,MySQL 可以避免在对 all 个结果进行排序之前找到 all 个满足限制的结果。相反,它保留的结果集要小得多,并且能够在处理行时更新这些结果。这并不总是那么容易。
【解决方案2】:

让我们检查索引。

bookeditems 中有关于 meeting_id 和 item_type 的复合索引吗?

您是否有关于会议上的会议 ID 的索引,如果它在复合键中,它是第一个索引吗?

你有关于会议日期时间的聚集索引吗?

您可以放置​​一个子查询来获取每个会议 ID 的堆栈顶部,而不是做不同的操作吗?

类似:

select * from meetings a
where datetime = (select max(datetime) from meetings 
where meetingid = a.meetingid)

您可以使用 case 语句而不是会议类型来获取相同的数据,而不是左加入吗?

【讨论】:

  • 复合我假设您的意思是包含两个字段的索引?回答:是的,我在会议表中有关于会议 ID 的索引。我不知道什么是聚集索引或如何在 MySQL 中创建一个聚集索引 - 在回答之前我必须先阅读我不明白你想用你建议的子查询来实现什么?这将如何加快速度?是的,如果这就是你的意思,我可以做一个 case 语句来获取会议表中的预定项目类型......这实际上是原始查询所做的 - 我将其剥离以避免复杂性
【解决方案3】:

您可以尝试通过首先SELECT-ing 感兴趣的记录(使用ORDER BYLIMIT)来帮助优化器,然后使用该结果与其他表连接。使用这种方法,datetime 上的索引可以完全用于ORDER BYLIMIT 子句。下面是这种方法的样子:

SELECT
  `meetings`.`id` AS `meeting_id`,
  `meetings`.`uid` AS meeting_uid,
  GROUP_CONCAT(
   DISTINCT CASE bookeditems.item_type
     WHEN 'SER' THEN bookeditems.id
     ELSE NULL
   END
  ) AS meetings_SERV_ids,
  GROUP_CONCAT(
   DISTINCT CASE bookeditems.item_type
     WHEN 'TRA' THEN bookeditems.id
     ELSE NULL
   END
  ) AS meetings_TRANSP_ids,
  GROUP_CONCAT(
   DISTINCT CASE bookeditems.item_type
     WHEN 'ACC' THEN bookeditems.id
     ELSE NULL
   END
  ) AS meetings_ACCO_ids,
  GROUP_CONCAT(bookeditems.id) AS meetings_BOOKEDITEMS_ids
FROM (
  SELECT id
  FROM meetings
  ORDER BY `datetime`
  LIMIT 0, 50
) filtered_meetings
INNER JOIN meetings
  ON meetings.id = filtered_meetings.id
LEFT OUTER JOIN bookeditems
  ON meeting_uid = bookeditems.meeting_uid
GROUP BY meeting_uid

显着差异:

  • 我们只加入booked items 表一次。在 select 语句中,我们使用 GROUP_CONCATCASE 语句来选择会议组中符合特定条件的所有 id。此外,要使其正常工作,我们需要添加 GROUP BY 语句来对代表同一会议的所有行进行分组。

    这意味着我们可能会得到meetings_SERV_idsmeetings_TRANSP_idsmeetings_ACCO_idsmeetings_BOOKEDITEMS_ids 的ID 字符串。因此,请记住在处理结果行的客户端代码中使用 explode 或等效项。

【讨论】:

  • 这实际上比原始查询需要更长的时间(现在排序 16 秒)。我已经用创建表定义更新了问题并添加了为什么我想要像我一样的相关表而不是你建议在单个字段中连接 ID 列表。
  • 我意识到我比我想象的更菜鸟!
  • 抱歉,没有任何测试表和数据,这只是我的想法。
  • 另外,您能否发布一些示例(所需)输出。我认为我不太了解您想要的输出格式
  • 我不太确定我是否理解您的问题...为了让您了解我想要的输出格式是什么,我需要包含我的解决方案中的 SQL 语句(20-30 个字段)。是否可以说我想要一个唯一的会议列表(即每次会议只有 1 行),其中包含来自预定项目的相关数据作为聚合数据 - SUM(meetings_ACCO.cost) AS total_cost - 或作为“存在”逻辑- IF(meetings_ACCO.id > 0, 1, 0) AS acco_exists
猜你喜欢
  • 1970-01-01
  • 2021-07-10
  • 1970-01-01
  • 2014-02-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-01
  • 2020-05-15
相关资源
最近更新 更多