2010-09-07 3 views
1

Я знаю, что существует тысяча подобных вопросов, но никто не имеет дело с запутанным запросом, как мой (и мои навыки MySQL не соответствуют . на самом деле понять, как адаптировать их)Оптимизация запроса MySQL, чтобы избежать «Использование временных» и «Использование filesort»

Здесь:

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`; 

результаты этого выглядеть следующим образом:

+-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  |               | 
+----+-------------+-------+--------+-----------------------------+-----------+---------+-----------------------+--------+-----------------------------------------------------------+ 

И таблица определения:

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 

Чтобы упредить любые недоумение, взгляните на некоторые из этих столбцов - да, в нем есть немало вещей наследия, которые у меня не было времени для исправления кода.

+0

План объяснения выглядит для меня звуком. В чем проблема? –

+0

Это занимает около 2,5 секунд. Не ужасно, но мне нравится быть жадным. Это предназначено для «живого» отчета, и я хотел бы максимально сократить время выполнения. –

ответ

4

Я обнаружил, что в MySQL, по крайней мере, почти любой запрос с использованием GROUP BY вызывает временную таблицу. Вот где ваши большие затраты на производительность. Попробуйте изучения, где он тратит свое время с помощью профайлера:

редактировать: Я исправляя следующее SET PROFILING (не SET PROFILES):

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

См http://dev.mysql.com/doc/refman/5.1/en/show-profiles.html для более подробной информации.

Существует не так много, чтобы исправить это. Это иногда предпочтительнее по соображениям производительности для устранения 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' 

Затем в коде приложения, принесите всех строки, и петлю над ними, вычислением агрегатных счетчиков, как вы идете. Например, в 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]; 
    } 
    } 
} 

Это выглядит как много кода и проблемы, чтобы сделать что-то, что SQL должны иметь возможность более эффективно делать, но в случае MySQL и GROUP BY писать больше кода приложения часто лучше.

PS: В примере SQL-запроса я изменил ваши соединения на внутренние соединения. Я не думаю, что вам нужны внешние соединения.

+0

К сожалению, похоже, что наш сервер MySQL не имеет установленного/включенного профилирования (это правильная версия, но установите PROFILER, выдает ошибку.) Мне придется пересмотреть это, когда наш sysadmin успевает включить его. –

+0

Мои извинения, я получил синтаксис неправильно. Это 'SET PROFILING = On'. Я отредактирую свой ответ выше, чтобы быть прав. –