如何使用多个连接优化慢查询

时间:2012-02-27 13:42:51

标签: mysql performance join query-optimization

我的情况:

  • 查询搜索大约90,000辆车
  • 每次查询都需要很长时间
  • 我已经有了所有正在加入的字段的索引。

如何优化呢?

以下是查询:

SELECT vehicles.make_id,
       vehicles.fuel_id,
       vehicles.body_id,
       vehicles.transmission_id,
       vehicles.colour_id,
       vehicles.mileage,
       vehicles.vehicle_year,
       vehicles.engine_size,
       vehicles.trade_or_private,
       vehicles.doors,
       vehicles.model_id,
       Round(3959 * Acos(Cos(Radians(51.465436)) *
                         Cos(Radians(vehicles.gps_lat)) *
                                           Cos(
                                           Radians(vehicles.gps_lon) - Radians(
                                           -0.296482)) +
                               Sin(
                                      Radians(51.465436)) * Sin(
                               Radians(vehicles.gps_lat)))) AS distance
FROM   vehicles
       INNER JOIN vehicles_makes
         ON vehicles.make_id = vehicles_makes.id
       LEFT JOIN vehicles_models
         ON vehicles.model_id = vehicles_models.id
       LEFT JOIN vehicles_fuel
         ON vehicles.fuel_id = vehicles_fuel.id
       LEFT JOIN vehicles_transmissions
         ON vehicles.transmission_id = vehicles_transmissions.id
       LEFT JOIN vehicles_axles
         ON vehicles.axle_id = vehicles_axles.id
       LEFT JOIN vehicles_sub_years
         ON vehicles.sub_year_id = vehicles_sub_years.id
       INNER JOIN members
         ON vehicles.member_id = members.id
       LEFT JOIN vehicles_categories
         ON vehicles.category_id = vehicles_categories.id
WHERE  vehicles.status = 1
       AND vehicles.date_from < 1330349235
       AND vehicles.date_to > 1330349235
       AND vehicles.type_id = 1
       AND ( vehicles.price >= 0
             AND vehicles.price <= 1000000 )  

这是车辆表架构:

CREATE TABLE IF NOT EXISTS `vehicles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `number_plate` varchar(100) NOT NULL,
  `type_id` int(11) NOT NULL,
  `make_id` int(11) NOT NULL,
  `model_id` int(11) NOT NULL,
  `model_sub_type` varchar(250) NOT NULL,
  `engine_size` decimal(12,1) NOT NULL,
  `vehicle_year` int(11) NOT NULL,
  `sub_year_id` int(11) NOT NULL,
  `mileage` int(11) NOT NULL,
  `fuel_id` int(11) NOT NULL,
  `transmission_id` int(11) NOT NULL,
  `price` decimal(12,2) NOT NULL,
  `trade_or_private` tinyint(4) NOT NULL,
  `postcode` varchar(25) NOT NULL,
  `gps_lat` varchar(50) NOT NULL,
  `gps_lon` varchar(50) NOT NULL,
  `img1` varchar(100) NOT NULL,
  `img2` varchar(100) NOT NULL,
  `img3` varchar(100) NOT NULL,
  `img4` varchar(100) NOT NULL,
  `img5` varchar(100) NOT NULL,
  `img6` varchar(100) NOT NULL,
  `img7` varchar(100) NOT NULL,
  `img8` varchar(100) NOT NULL,
  `img9` varchar(100) NOT NULL,
  `img10` varchar(100) NOT NULL,
  `is_featured` tinyint(4) NOT NULL,
  `body_id` int(11) NOT NULL,
  `colour_id` int(11) NOT NULL,
  `doors` tinyint(4) NOT NULL,
  `axle_id` int(11) NOT NULL,
  `category_id` int(11) NOT NULL,
  `contents` text NOT NULL,
  `date_created` int(11) NOT NULL,
  `date_edited` int(11) NOT NULL,
  `date_from` int(11) NOT NULL,
  `date_to` int(11) NOT NULL,
  `member_id` int(11) NOT NULL,
  `inactive_id` int(11) NOT NULL,
  `status` tinyint(4) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `type_id` (`type_id`),
  KEY `make_id` (`make_id`),
  KEY `model_id` (`model_id`),
  KEY `fuel_id` (`fuel_id`),
  KEY `transmission_id` (`transmission_id`),
  KEY `body_id` (`body_id`),
  KEY `colour_id` (`colour_id`),
  KEY `axle_id` (`axle_id`),
  KEY `category_id` (`category_id`),
  KEY `vehicle_year` (`vehicle_year`),
  KEY `mileage` (`mileage`),
  KEY `status` (`status`),
  KEY `date_from` (`date_from`),
  KEY `date_to` (`date_to`),
  KEY `trade_or_private` (`trade_or_private`),
  KEY `doors` (`doors`),
  KEY `price` (`price`),
  KEY `engine_size` (`engine_size`),
  KEY `sub_year_id` (`sub_year_id`),
  KEY `member_id` (`member_id`),
  KEY `date_created` (`date_created`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=136237 ;

EXPLAIN:

1   SIMPLE  vehicles    ref     type_id,make_id,status,date_from,date_to,price,mem...   type_id     4   const   85695   Using where
1   SIMPLE  members     index   PRIMARY     PRIMARY     4   NULL    3   Using where; Using index; Using join buffer
1   SIMPLE  vehicles_makes  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.make_id    1   Using index
1   SIMPLE  vehicles_models     eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.model_id   1   Using index
1   SIMPLE  vehicles_fuel   eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.fuel_id    1   Using index
1   SIMPLE  vehicles_transmissions  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.transmission_id    1   Using index
1   SIMPLE  vehicles_axles  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.axle_id    1   Using index
1   SIMPLE  vehicles_sub_years  eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.sub_year_id    1   Using index
1   SIMPLE  vehicles_categories     eq_ref  PRIMARY     PRIMARY     4   tvs.vehicles.category_id    1   Using index

5 个答案:

答案 0 :(得分:12)

改进WHERE子句

您的EXPLAIN显示MySQL只使用一个索引(type_id)来选择与WHERE子句匹配的行,即使该子句中有多个条件。

为了能够为WHERE子句中的所有条件使用索引,并尽可能快地减小结果集的大小,请在vehicles表的以下列中添加多列索引: / p>

(status, date_from, date_to, type_id, price)

列应按最高基数的顺序排列。

例如,vehicles.date_from的值可能比status更明显,因此请将date_from列放在status之前,如下所示:

(date_from, date_to, price, type_id, status)

这应该减少查询执行的第一部分返回的行,并且应该在EXPLAIN结果的第一行显示较低的行数。

您还会注意到MySQL将在EXPLAIN结果中使用WHERE的多列索引。如果不是偶然的话,你应该提示或强制多列索引。

删除不必要的JOIN

您似乎没有使用任何连接表中的任何字段,因此请删除连接。这将删除查询的所有其他工作,并将您简化为一个简单的执行计划(EXPLAIN结果中的一行)。

每个JOINed表都会导致结果集的每一行进行额外的查找。因此,如果WHERE子句从车辆中选择5,000行,因为您有8个连接到车辆,您将有5,000 * 8 = 40,000个查找。从您的数据库服务器那里要问的很多。

答案 1 :(得分:4)

不使用昂贵的所有行的精确距离计算,而是使用边界框并仅计算框内行的精确距离。

最简单的示例是计算您感兴趣的最小/最大经度和纬度,并将其添加到WHERE子句中。这样,距离将仅针对行的子集计算。

WHERE
    vehicles.gps_lat > min_lat ANDd vehicles.gps_lat < max_lat AND
    vehicles.gps_lon > min_lon AND vehicles.gps_lon < max_lon

对于更复杂的解决方案,请参阅:

答案 2 :(得分:3)

没有这个,你的SQL会更快吗?

Round(3959 * Acos(Cos(Radians(51.465436)) *
  Cos(Radians(vehicles.gps_lat)) *
  Cos(Radians(vehicles.gps_lon) - 
  Radians(-0.296482)) + 
  Sin(Radians(51.465436)) * 
  Sin(Radians(vehicles.gps_lat)))) AS distance

执行数学方程式非常昂贵

也许您应该考虑预先计算距离的物化视图,然后您可以从该视图中进行选择。根据数据的动态程度,您可能不必经常刷新数据。

答案 3 :(得分:1)

澄清这个答案:如果你还没有这些索引,你应该考虑添加它们

你们还有这些索引:

vehicles.status
vehicles.date_from
vehicles.date_to
vehicles.type_id
vehicles.price

答案 4 :(得分:1)

为了比索引的@Randy更具体一点,我相信他的意图是有一个COMPOUND索引来利用你的查询标准......一个建立在MINIMUM上的索引...

( status, type_id, date_from )

但可以扩展为包含date_to和price,但不知道该粒度级别的索引实际上可以提供多少帮助

( status, type_id, date_from, date_to, price )

每条评论的编辑

您不应该需要所有这些单独的索引......是的,主键本身。但是,对于其他人,您应该根据您的常见查询条件获得复合索引并删除其他索引......引擎可能会对最适合查询的内容感到困惑。如果您知道自己一直在寻找某种状态,输入和日期(假设车辆搜索),请将其作为一个索引。如果查询正在查找此类信息,而且该条件中的价格已经非常接近于几个符合条件的记录,这些记录符合条件并且只是作为额外标准飞越价格。

如果您提供的查询类似于仅自动与手动传输,无论年份/制造,那么是的,这可能是它自己的索引。但是,如果您通常会有一些其他“常见”标准,那么可以将其作为可以在查询中使用的辅助设备。例如:如果您查找的是2门与4门的手动变速箱,请将您的索引设为(transmission_id,category_id)。

同样,您希望基于某些“最小”条件缩小标准范围的任何内容。如果您将一个额外的列添加到可能“通常”应用的索引,那么这应该只有助于提高性能。