简体   繁体   English

优化MySQL查询以避免“使用临时”和“使用文件排序”

[英]Optimizing a MySQL query to avoid “Using temporary” and “Using filesort”

I know there's a thousand similar questions out there, but none deal with as convoluted a query as mine (and my MySQL skills aren't to the point to really understand how to adapt them.) 我知道那里有一千个类似的问题,但是没有一个像我的那样处理复杂的查询(我的MySQL技能还不足以真正理解如何适应它们。)

Here it is: 这里是:

explain select
  `ev`.`EventID` AS `EventID`
  ,`ev`.`EventName` AS `EventName`
  ,concat(`ev`.`EventDate`,' ',`ev`.`StartTime`) AS `EventDT`
  ,`ev`.`NumberTicketsAvailable` AS `TotalTickets`
  ,`ev`.`Soldout` AS `Soldout`
  ,count((case when (`ec`.`CartStatus` = 'InCart') then 1 else NULL end)) AS `InCartCount`
  ,count((case when (`ec`.`CartStatus` = 'InPayment') then 1 else NULL end)) AS `InPaymentCount`
  ,count((case when (`ec`.`CartStatus` = 'Paid') then 1 else NULL end)) AS `PaidCount`
  ,count((case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 604800 second) > now())) then 1 else NULL end)) AS `PaidOverWeek`
  ,count((case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 432000 second) > now())) then 1 else NULL end)) AS `PaidOverFiveDays`
  ,count((case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 86400 second) > now())) then 1 else NULL end)) AS `PaidOverDay`
  ,count((case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 43200 second) > now())) then 1 else NULL end)) AS `PaidOverHalfDay`
  ,count((case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 21600 second) > now())) then 1 else NULL end)) AS `PaidOverQuarterDay`
  ,count((case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 10800 second) > now())) then 1 else NULL end)) AS `PaidOverThreeHours`
  ,count((case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 3600 second) > now())) then 1 else NULL end)) AS `PaidOverHour`
from (`Events` `ev`
  left join (`Events_EventCart_Rel` `eecr`
    left join `EventCart` `ec`
      on((`eecr`.`EventCartID` = `ec`.`EventCartID`)))
    on((`ev`.`EventID` = `eecr`.`EventID`)))
where (`eecr`.`Active` = 1 AND `eecr`.`Deleted` = 'No')
group by
  `ev`.`EventID`
  ,`ev`.`EventName`
  ,`ev`.`EventDate`
  ,`ev`.`StartTime`
  ,`ev`.`NumberTicketsAvailable`
  ,`ev`.`Soldout`;

The results of this look like this: 结果如下:

+-id-+-select_type-+-table-+--type--+--------possible_keys--------+----key----+-key_len-+----------ref----------+--rows--+---------------------------Extra---------------------------+
|   1| SIMPLE      | eecr  | index  | EventID,EventID_2,EventID_3 | EventID_3 | 10      | {null}                | 17609  | Using where; Using index; Using temporary; Using filesort |
|   1| SIMPLE      | ev    | eq_ref | PRIMARY                     | PRIMARY   | 4       | eecr.EventID          | 1      | Using where                                               |
|   1| SIMPLE      | ec    | eq_ref | PRIMARY                     | PRIMARY   | 4       | eecr.EventCartID      | 1      |                                                           |
+----+-------------+-------+--------+-----------------------------+-----------+---------+-----------------------+--------+-----------------------------------------------------------+

And the table definitions: 和表定义:

CREATE TABLE IF NOT EXISTS `Events` (
  `EventID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `EventName` varchar(150) NOT NULL,
  `StartTime` char(8) NOT NULL DEFAULT '00:00:00',
  `EndTime` char(8) NOT NULL DEFAULT '00:00:00',
  `EventDate` varchar(20) NOT NULL,
  `NumberTicketsAvailable` smallint(6) DEFAULT NULL,
  `Soldout` enum('yes','no') DEFAULT 'no',
  #...
  PRIMARY KEY (`EventID`),
  KEY `EndTime` (`EndTime`,`EventDate`),
  KEY `StartTime` (`StartTime`,`EventDate`),
  KEY `EventDate` (`EventDate`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1

CREATE TABLE IF NOT EXISTS `Events_EventCart_Rel` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `EventCartID` int(11) NOT NULL,
  `EventID` int(11) NOT NULL,
  `DateAdded` datetime NOT NULL,
  `PersonID` int(11) NOT NULL,
  `SeatTypeID` int(11) NOT NULL,
  `MealChoiceID` int(11) NOT NULL,
  `Active` tinyint(1) NOT NULL DEFAULT '1',
  `Deleted` enum('Yes','No') NOT NULL DEFAULT 'No',
  `ModifiedByAdmin` enum('Yes','No') NOT NULL DEFAULT 'No',
  PRIMARY KEY (`ID`),
  KEY `EventID` (`EventID`,`PersonID`),
  KEY `EventCartID` (`EventCartID`),
  KEY `EventID_2` (`EventID`),
  KEY `EventID_3` (`EventID`,`EventCartID`,`Active`,`Deleted`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1

CREATE TABLE IF NOT EXISTS `EventCart` (
  `EventCartID` int(11) NOT NULL AUTO_INCREMENT,
  `RegistrantsID` int(11) NOT NULL DEFAULT '0',
  `DateRecordCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `DateRecordModified` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  `CartStatus` enum('InCart','InPayment','Paid') NOT NULL DEFAULT 'InCart',
  `ModifiedByAdmin` enum('yes','no') NOT NULL DEFAULT 'no',
  PRIMARY KEY (`EventCartID`),
  KEY `rid` (`RegistrantsID`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1

To preempt any puzzled looks at a few of those columns - yes, there are quite a few legacy-type things in there that I didn't have time to fix in code. 为了避免任何困惑,请看其中的几列-是的,其中有很多遗留类型的内容,我没有时间来修复。

I've found that in MySQL at least, almost any query using GROUP BY invokes a temporary table. 我发现至少在MySQL中,几乎所有使用GROUP BY查询都调用一个临时表。 This is where your big performance cost goes. 这就是您巨大的性能成本所在。 Try examining where it's spending its time using the profiler: 尝试使用探查器检查它在哪里花费时间:

edit: I'm correcting the following to SET PROFILING (not SET PROFILES ): 编辑:我正在将以下内容SET PROFILINGSET PROFILING (不是SET PROFILES ):

SET PROFILING = On;
SELECT ...the whole query you want to profile...
SHOW PROFILES;
SHOW PROFILE FOR QUERY 1;

See http://dev.mysql.com/doc/refman/5.1/en/show-profiles.html for more details. 有关更多详细信息,请参见http://dev.mysql.com/doc/refman/5.1/en/show-profiles.html

There's not much you can do to fix this. 您没有太多可以解决的方法。 It's sometimes preferable for performance reasons to eliminate the GROUP BY and aggregate functions: 出于性能原因,有时最好消除GROUP BY和聚合函数:

select
  `ev`.`EventID` AS `EventID`
  ,`ev`.`EventName` AS `EventName`
  ,concat(`ev`.`EventDate`,' ',`ev`.`StartTime`) AS `EventDT`
  ,`ev`.`NumberTicketsAvailable` AS `TotalTickets`
  ,`ev`.`Soldout` AS `Soldout`
  ,case when (`ec`.`CartStatus` = 'InCart') then 1 else 0 end AS `InCartCounter`
  ,case when (`ec`.`CartStatus` = 'InPayment') then 1 else 0 end AS `InPaymentCounter`
  ,case when (`ec`.`CartStatus` = 'Paid') then 1 else 0 end AS `PaidCounter`
  ,case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 604800 second) > now())) then 1 else 0 end AS `PaidOverWeekCounter`
  ,case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 432000 second) > now())) then 1 else 0 end AS `PaidOverFiveDaysCounter`
  ,case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 86400 second) > now())) then 1 else 0 end AS `PaidOverDayCounter`
  ,case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 43200 second) > now())) then 1 else 0 end AS `PaidOverHalfDayCounter`
  ,case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 21600 second) > now())) then 1 else 0 end AS `PaidOverQuarterDayCounter`
  ,case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 10800 second) > now())) then 1 else 0 end AS `PaidOverThreeHoursCounter`
  ,case when ((`ec`.`CartStatus` = 'Paid') and ((`ec`.`DateRecordModified` + interval 3600 second) > now())) then 1 else 0 end AS `PaidOverHourCounter`
from `Events` `ev`
inner join `Events_EventCart_Rel` `eecr`
  on `ev`.`EventID` = `eecr`.`EventID` 
inner join `EventCart` `ec`
   on `eecr`.`EventCartID` = `ec`.`EventCartID`
where `eecr`.`Active` = 1 and `eecr`.`Deleted` = 'No'

Then in your application code, fetch all the rows, and loop over them, calculating the aggregate counts as you go. 然后,在您的应用程序代码中,获取所有行,并在它们上循环,从而在您进行操作时计算合计计数。 For instance in PHP: 例如在PHP中:

$stmt = $pdo->query($sql);
$events = array();
$counters = array("InCartCounter", "InPaymentCounter", "PaidCounter",
  "PaidOverWeekCounter", "PaidOverFiveDaysCounter", "PaidOverDayCounter",
  "PaidOverHalfDayCounter", "PaidOverQuarterDayCounter", 
  "PaidOverThreeHoursCounter", "PaidOverHourCounter");

while ($row = $stmt->fetch())
{
  if (!isset($events[$row["EventID"]])) {
    $events[$row["EventID"]] = $row;
  } else {
    foreach ($counters as $key) {
      $events[$row["EventID"]][$key] += $row[$key];
    }
  }
}

It looks like a lot of code and trouble to do something that SQL should be able to do more efficiently, but in the case of MySQL and GROUP BY writing more application code is often better. 它看起来像一个大量的代码,并不厌其烦地做一些SQL 应该能够更有效地完成,但在MySQL的情况下与GROUP BY编写更多的应用程序代码往往是更好的。

PS: In the example SQL query, I changed your joins to inner joins. PS:在示例SQL查询中,我将您的联接更改为内部联接。 I don't think you need outer joins. 我认为您不需要外部联接。

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

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