MySQL distinct +排序查询性能问题

时间:2012-06-08 13:57:22

标签: mysql performance

问题

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

  • 我删除了排序(查询然后< 1秒)
  • 我删除了distinct关键字:(查询然后< 1秒)
  • 开始删除联接(查询然后< 5秒)

问题

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

相关数据既用于确定是否存在相关字段,也用于进行GROUP_CONCAT操作 - 因此要求对同一个registereditems表进行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 ;

EXPLAIN SELECT

的结果
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 config

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

3 个答案:

答案 0 :(得分:0)

让我们检查索引。

您是否在铺设项目中的meeting_id和item_type上有复合索引?

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

会议日期时间是否有聚集索引?

您可以将子查询放在每个会议ID的堆栈顶部,而不是区分吗?

类似的东西:

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

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

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

您可以首先尝试帮助优化器SELECT - 感兴趣的记录(使用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

显着差异:

  • 我们只加入预订的商品表一次。在select语句中,我们使用GROUP_CONCATCASE语句来选择会议组中符合特定条件的所有ID。此外,为此,我们需要添加GROUP BY语句来对表示同一会议的所有行进行分组。

    这意味着我们可能会为meetings_SERV_idsmeetings_TRANSP_idsmeetings_ACCO_idsmeetings_BOOKEDITEMS_ids获取一系列ID。因此,请记住处理结果行的客户端代码中的explode或等效内容。