加速复杂的多连接MySQL查询(如果重要的话,通过CakePHP创建)

时间:2011-07-11 17:19:45

标签: mysql cakephp query-optimization

我有一个MySQL查询,我正在使用它来获取我的活动列表页面的事件。问题是,使用限制10运行大约需要35秒,而使用COUNT进行分页需要大约35秒。 超过70秒的页面加载时间不会削减它,正如您可以想象的那样。这只是740事件的结果!当我们得到2000+时,我很害怕想到这会怎样。

我们尝试过编制索引(尽管我们缺乏指数知识),而且效果确实为零。

表关联的说明: 活动可在餐厅或场地举行。该活动的城市由其举办的餐厅或地点的city_id决定。它也获得了上传(在这种情况下的照片)。

有些令人困惑的部分是时间表/日期 - 时间表包含事件的开始/结束/重复信息。日期记录是根据计划的信息创建的,并且保存事件每天的单独记录(start = datetime,end = datetime)

我正在使用CakePHP创建此查询,并在底部列出了我的关联:

SELECT
`Event`.*, `Venue`.`id`, `Venue`.`slug`, `Venue`.`name`, `Venue`.`GPS_Lon`,
`Venue`.`GPS_Lat`, `Venue`.`city_id`, `VenueCity`.`name`, `VenueCity`.`slug`,
`Restaurant`.`id`, `Restaurant`.`slug`, `Restaurant`.`name`, `Restaurant`.`GPS_Lat`,
`Restaurant`.`GPS_Lon`, `Restaurant`.`city_id`, `RestaurantCity`.`name`, 
`RestaurantCity`.`slug`, GROUP_CONCAT(Date.start, "|", Date.end

ORDER BY Date.start ASC SEPARATOR "||") AS EventDates
FROM `events` AS `Event`
LEFT JOIN restaurants AS `Restaurant` ON (`Restaurant`.`id` = `Event`.`restaurant_id`)
LEFT JOIN venues AS `Venue` ON (`Venue`.`id` = `Event`.`venue_id`)
LEFT JOIN cities AS `VenueCity` ON (`Venue`.`city_id` = `VenueCity`.`id`)
LEFT JOIN cities AS `RestaurantCity` ON (`Restaurant`.`city_id` = `RestaurantCity`.`id`)
INNER JOIN schedules AS `Schedule` ON (`Schedule`.`event_id` = `Event`.`id`)
INNER JOIN dates AS `Date` ON (`Date`.`schedule_id` = `Schedule`.`id`)
LEFT JOIN uploads AS `Upload` ON (`Upload`.`event_id` = `Event`.`id`)
WHERE `Event`.`approval_status_id` = 1 AND `Date`.`start` >= '2011-07-11 12:38:54'
GROUP BY `Event`.`id`
ORDER BY `Date`.`start` ASC LIMIT 10

CakePHP协会:

Event belongsTo Venue
Venue hasMany Event

Event belongsTo Restaurant
Restaurant hasmany Event

Event hasMany Upload
Upload belongsTo Event

City hasMany Restaurant
City hasMany Venue
Restaurant belongsTo City
Venue belongsTo City

Event hasMany Schedule
Schedule belongsTo Event
Schedule hasMany Date
Date belongsTo Schedule

UPDATE(根据@Zoredache请求):

这是我在选择之前添加EXPLAIN所得到的:

id  select_type  table          type  possible_keys            key              key_len   ref                             rows  Extra
1   SIMPLE       Event          ref   PRIMARY,approval status  approval status  5         const                           946   Using where; Using temporary; Using filesort
1   SIMPLE       Restaurant     ref   PRIMARY,id               id               4         medut_ent.Event.restaurant_id   1 
1   SIMPLE       Venue          ref   PRIMARY,id               id               4         medut_ent.Event.venue_id        1 
1   SIMPLE       VenueCity      ref   PRIMARY,id               id               4         medut_ent.Venue.city_id         1 
1   SIMPLE       RestaurantCity ref   PRIMARY,id               id               4         medut_ent.Restaurant.city_id    1 
1   SIMPLE       Schedule       ref   PRIMARY,index            index            5         medut_ent.Event.id              1     Using where; Using index
1   SIMPLE       Date           ref   all cols,start...        all cols         5         medut_ent.Schedule.id           8     Using where; Using index
1   SIMPLE       Upload         ALL                                                                                       4240  

4 个答案:

答案 0 :(得分:2)

SELECT STRAIGHT_JOIN
`Event`.*, `Venue`.`id`, `Venue`.`slug`, `Venue`.`name`, `Venue`.`GPS_Lon`,
`Venue`.`GPS_Lat`, `Venue`.`city_id`, `VenueCity`.`name`, `VenueCity`.`slug`,
`Restaurant`.`id`, `Restaurant`.`slug`, `Restaurant`.`name`, `Restaurant`.`GPS_Lat`,
`Restaurant`.`GPS_Lon`, `Restaurant`.`city_id`, `RestaurantCity`.`name`, 
`RestaurantCity`.`slug`, GROUP_CONCAT(Date.start, "|", Date.end ORDER BY Date.start ASC SEPARATOR "||") AS EventDates

FROM `events` AS `Event`
INNER JOIN schedules AS `Schedule` ON (`Schedule`.`event_id` = `Event`.`id`)
INNER JOIN dates AS `Date` ON (`Date`.`schedule_id` = `Schedule`.`id`)
LEFT JOIN restaurants AS `Restaurant` ON (`Restaurant`.`id` = `Event`.`restaurant_id`)
LEFT JOIN cities AS `RestaurantCity` ON (`Restaurant`.`city_id` = `RestaurantCity`.`id`)
LEFT JOIN venues AS `Venue` ON (`Venue`.`id` = `Event`.`venue_id`)
LEFT JOIN cities AS `VenueCity` ON (`Venue`.`city_id` = `VenueCity`.`id`)
LEFT JOIN uploads AS `Upload` ON (`Upload`.`event_id` = `Event`.`id`)
WHERE `Event`.`approval_status_id` = 1 
AND `Date`.`start` >= '2011-07-11 12:38:54'
GROUP BY `Event`.`id`
ORDER BY `Date`.`start` ASC 
LIMIT 10

答案 1 :(得分:0)

假设索引是正确的,请尝试移动一些连接以首先使用WHERE子句中使用的连接,同时还使用STRAIGHT_JOIN来确保您的排序不会被MySQL过度优化:

SELECT STRAIGHT_JOIN
`Event`.*, `Venue`.`id`, `Venue`.`slug`, `Venue`.`name`, `Venue`.`GPS_Lon`,
`Venue`.`GPS_Lat`, `Venue`.`city_id`, `VenueCity`.`name`, `VenueCity`.`slug`,
`Restaurant`.`id`, `Restaurant`.`slug`, `Restaurant`.`name`, `Restaurant`.`GPS_Lat`,
`Restaurant`.`GPS_Lon`, `Restaurant`.`city_id`, `RestaurantCity`.`name`, 
`RestaurantCity`.`slug`, GROUP_CONCAT(Date.start, "|", Date.end ORDER BY Date.start ASC SEPARATOR "||") AS EventDates

FROM `events` AS `Event`
INNER JOIN schedules AS `Schedule` ON (`Schedule`.`event_id` = `Event`.`id`)
INNER JOIN dates AS `Date` ON (`Date`.`schedule_id` = `Schedule`.`id`)
LEFT JOIN restaurants AS `Restaurant` ON (`Restaurant`.`id` = `Event`.`restaurant_id`)
LEFT JOIN cities AS `RestaurantCity` ON (`Restaurant`.`city_id` = `RestaurantCity`.`id`)
LEFT JOIN venues AS `Venue` ON (`Venue`.`id` = `Event`.`venue_id`)
LEFT JOIN cities AS `VenueCity` ON (`Venue`.`city_id` = `VenueCity`.`id`)
LEFT JOIN uploads AS `Upload` ON (`Upload`.`event_id` = `Event`.`id`)
WHERE `Event`.`approval_status_id` = 1 
AND `Date`.`start` >= '2011-07-11 12:38:54'
GROUP BY `Event`.`id`
ORDER BY `Date`.`start` ASC 
LIMIT 10

您可能还发现只运行一个单独的日期查询而不是GROUP_CONCAT语句会更快,因为这可能会创建TEMP TABLES(在EXPLAIN语句中会很明显)。

答案 2 :(得分:0)

尝试摆脱该group_concat。您同时使用临时表和文件排序的事实是一个标志。

您还应该在所有外键,restaurant_id,venue_id等上放置索引。这些应包括您的本地ID秒,RestaurantCity上的city_id and id`。

答案 3 :(得分:0)

事实证明,Upload表没有索引。简单地添加一个索引使其运行速度极快(142ms而不是75000 + ms)。

我通过EXPLAIN SELECT找到了这个问题...(感谢@Zoredache) - 答案“更新”中EXPLAIN的详细信息。