当where语句中的日期更改时,MySQL EXPLAIN'type'从'range'更改为'ref'?
Posted
技术标签:
【中文标题】当where语句中的日期更改时,MySQL EXPLAIN\'type\'从\'range\'更改为\'ref\'?【英文标题】:MySQL EXPLAIN 'type' changes from 'range' to 'ref' when the date in the where statement is changed?当where语句中的日期更改时,MySQL EXPLAIN'type'从'range'更改为'ref'? 【发布时间】:2011-10-06 06:12:21 【问题描述】:我一直在测试不同的想法来优化我们系统中的一些工作表。今天我遇到了一张表格,它可以跟踪我们系统中每辆车的每个视图。在下面创建表格。
SHOW CREATE TABLE vehicle_view_tracking;
CREATE TABLE `vehicle_view_tracking` (
`vehicle_view_tracking_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`public_key` varchar(45) NOT NULL,
`vehicle_id` int(10) unsigned NOT NULL,
`landing_url` longtext NOT NULL,
`landing_port` int(11) NOT NULL,
`http_referrer` longtext,
`created_on` datetime NOT NULL,
`created_on_date` date NOT NULL,
`server_host` longtext,
`server_uri` longtext,
`referrer_host` longtext,
`referrer_uri` longtext,
PRIMARY KEY (`vehicle_view_tracking_id`),
KEY `vehicleViewTrackingKeyCreatedIndex` (`public_key`,`created_on_date`),
KEY `vehicleViewTrackingKeyIndex` (`public_key`)
) ENGINE=InnoDB AUTO_INCREMENT=363439 DEFAULT CHARSET=latin1;
我在玩多列和单列索引。我运行了以下查询:
EXPLAIN EXTENDED SELECT dealership_vehicles.vehicle_make, dealership_vehicles.vehicle_model, vehicle_view_tracking.referrer_host, count(*) AS count
FROM vehicle_view_tracking
LEFT JOIN dealership_vehicles
ON dealership_vehicles.dealership_vehicle_id = vehicle_view_tracking.vehicle_id
WHERE vehicle_view_tracking.created_on_date >= '2011-09-07' AND vehicle_view_tracking.public_key IN ('ab12c3')
GROUP BY (dealership_vehicles.vehicle_make) ASC , dealership_vehicles.vehicle_model, referrer_host
+----+-------------+-----------------------+--------+----------------------------------------------------------------+------------------------------------+---------+----------------------------------------------+-------+----------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------------+--------+----------------------------------------------------------------+------------------------------------+---------+----------------------------------------------+-------+----------+----------------------------------------------+
| 1 | SIMPLE | vehicle_view_tracking | range | vehicleViewTrackingKeyCreatedIndex,vehicleViewTrackingKeyIndex | vehicleViewTrackingKeyCreatedIndex | 50 | NULL | 23086 | 100.00 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | dealership_vehicles | eq_ref | PRIMARY | PRIMARY | 8 | vehicle_view_tracking.vehicle_id | 1 | 100.00 | |
+----+-------------+-----------------------+--------+----------------------------------------------------------------+------------------------------------+---------+----------------------------------------------+-------+----------+----------------------------------------------+
(实际选择查询的执行时间为 0.309 秒)
然后我将 where 子句中的日期从 '2011-09-07' 更改为 '2011-07-07' 并得到以下解释结果
EXPLAIN EXTENDED SELECT dealership_vehicles.vehicle_make, dealership_vehicles.vehicle_model, vehicle_view_tracking.referrer_host, count(*) AS count
FROM vehicle_view_tracking
LEFT JOIN dealership_vehicles
ON dealership_vehicles.dealership_vehicle_id = vehicle_view_tracking.vehicle_id
WHERE vehicle_view_tracking.created_on_date >= '2011-07-07' AND vehicle_view_tracking.public_key IN ('ab12c3')
GROUP BY (dealership_vehicles.vehicle_make) ASC , dealership_vehicles.vehicle_model, referrer_host
+----+-------------+-----------------------+--------+----------------------------------------------------------------+-----------------------------+---------+----------------------------------------------+-------+----------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-----------------------+--------+----------------------------------------------------------------+-----------------------------+---------+----------------------------------------------+-------+----------+----------------------------------------------+
| 1 | SIMPLE | vehicle_view_tracking | ref | vehicleViewTrackingKeyCreatedIndex,vehicleViewTrackingKeyIndex | vehicleViewTrackingKeyIndex | 47 | const | 53676 | 100.00 | Using where; Using temporary; Using filesort |
| 1 | SIMPLE | dealership_vehicles | eq_ref | PRIMARY | PRIMARY | 8 | vehicle_view_tracking.vehicle_id | 1 | 100.00 | |
+----+-------------+-----------------------+--------+----------------------------------------------------------------+-----------------------------+---------+----------------------------------------------+-------+----------+----------------------------------------------+
(实际选择查询的执行时间为 0.670 秒)
我看到了 4 个主要变化:
-
类型从范围更改为参考
键从 vehicleViewTrackingKeyCreatedIndex 更改为 vehicleViewTrackingKeyIndex
key_len 由 50 变为 47(由 key 变化引起)
行数从 23086 更改为 53676(由键更改引起)
此时,慢查询的执行时间仅为 0.6 秒,但我们的数据库中只有大约 10% 的车辆。
已经很晚了,我可能忽略了 mysql 文档中的某些内容,但我似乎无法找到为什么在 where 子句中更改日期时键(以及类型和行)会发生变化。
非常感谢您的帮助。我搜索了与导致此更改的日期有相同/相似问题的人,但找不到任何东西。如果我错过了以前的帖子,请链接我:-)
【问题讨论】:
【参考方案1】:不同的搜索策略对不同的数据有意义。特别是,索引扫描(例如范围)通常必须进行查找才能实际读取行。在某些时候,执行所有这些查找比根本不使用索引要慢。
举个简单的例子,一个包含三列的表:id(主键)、name(索引)、生日。说它有很多数据。如果您让 MySQL 查找 Bob 的生日,它可以很快完成:首先,它在名称索引中找到 Bob(这需要几次查找,log(n),其中 n 是行数),然后再查找一次读取数据文件中的实际行并从中读取生日。这非常快,而且比扫描整个表格要快得多。
接下来,考虑做一个name like 'Z%'
。这可能只是表格的一小部分。因此,在名称索引中找到 Z 开始的位置仍然更快,然后为每个查找数据文件以读取该行。 (这是范围扫描)。
最后,考虑询问所有以 M-Z 开头的名称。这可能是数据的一半左右。它可以进行范围扫描,然后进行 lot 次搜索,但是在数据文件上随机搜索以读取一半行的最终目标并不是最佳的:只进行一次搜索会更快对数据文件进行大顺序读取。因此,在这种情况下,索引将被忽略。
这就是你所看到的——除了你的情况,还有另一个可以依赖的键。 (如果它没有另一个,它也可能实际使用日期索引,它应该选择最快的索引。注意 MySQL 的优化器经常在这方面出错。)
所以,简而言之,这是意料之中的。查询没有说明如何 检索数据,而是说明要检索什么 数据。数据库的优化器应该找到最快的方法来检索它。
您可能会在 both 列上找到一个索引,在这两种情况下,按照 (public_key,created_on_date) 的顺序都是首选的,这样可以加快查询速度。这是因为 MySQL 每个表(每个查询)只能使用一个索引。此外,日期在最后,因为范围扫描只能在索引的最后一列上有效地完成。
[我相信,InnoDB 实际上还有另一层间接性,但这只会混淆这一点。这对解释没有影响。]
【讨论】:
所以简而言之,mysql 优化器认为进行更改并使用其他索引会更好/更快。我又进行了一次测试,删除了第二个索引(vehicleViewTrackingKeyIndex),查询时间在 0.01 秒内。似乎随着结果集的增加,它意识到使用 2 列索引没有意义。 @CriticalSpeak:是的,简而言之。与 MySQL 相比,您经常需要更多地使用索引(和重写查询),因为它的优化器有很多缺陷。如果您对此感到厌烦,请尝试使用 PostgreSQL。以上是关于当where语句中的日期更改时,MySQL EXPLAIN'type'从'range'更改为'ref'?的主要内容,如果未能解决你的问题,请参考以下文章