如何使用许多连接优化慢查询[关闭]
Posted
技术标签:
【中文标题】如何使用许多连接优化慢查询[关闭]【英文标题】:How to optimize slow query with many joins [closed] 【发布时间】:2012-03-16 23:36:53 【问题描述】:我的情况:
查询搜索了大约 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 ;
解释:
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
【问题讨论】:
【参考方案1】:为了澄清这一点作为答案:如果您还没有这些索引,您应该考虑添加它们
你是否也有这些索引:
vehicles.status
vehicles.date_from
vehicles.date_to
vehicles.type_id
vehicles.price
【讨论】:
【参考方案2】:改进 WHERE 子句
您的解释表明 mysql 仅使用一个索引 (type_id
) 来选择与 WHERE
子句匹配的行,即使您在该子句中有多个条件。
为了能够为 WHERE 子句中的所有条件使用索引,并尽快减小结果集的大小,请在车辆表的以下列上添加多列索引:
(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 次查找。这对您的数据库服务器有很多要求。
【讨论】:
如果我们比较的字段来自连接表,索引会有用吗?【参考方案3】:比索引的@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) 上设置索引。
同样,您希望根据某些“最低”条件帮助缩小标准范围。如果您在索引中添加一个可能“通常”应用的额外列,那只会有助于提高性能。
【讨论】:
我不熟悉复合索引 - 请参阅我更新的帖子。我目前的索引效率低吗? 只需添加另一个索引,而不是单列,就像上面那样,多列由列分隔...这样,一个索引可以有多个组件,以更紧密地匹配查询条件。 我有 21 个索引,我需要将它们分组,然后我需要先删除它们吗? +1 这就是我的本意……看来 WHERE 子句可能是罪魁祸首,这些过滤列上的良好索引将有很大帮助。 @ChimeraTheory,索引澄清的修订答案【参考方案4】:如果没有这个,你的 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
执行数学方程式非常昂贵
也许您应该考虑预先计算距离的物化视图,您可以从该视图中进行选择。根据您数据的动态程度,您可能不必过于频繁地刷新数据。
【讨论】:
稍微是的,但我需要那个...【参考方案5】:使用边界框并仅计算框内行的精确距离,而不是为所有行进行昂贵的精确距离计算。
最简单的示例是计算您感兴趣的最小/最大经度和纬度并将其添加到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
有关更复杂的解决方案,请参阅:
MySQL spatial extensions How to use MySQL spatial extensions https://***.com/a/5237509/342473【讨论】:
以上是关于如何使用许多连接优化慢查询[关闭]的主要内容,如果未能解决你的问题,请参考以下文章