当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'?的主要内容,如果未能解决你的问题,请参考以下文章

MySQL WHERE语句筛选操作符

将表中的日期与 TableAU 中的多个日期范围进行比较:构建 WHERE 语句

MySQL:当 WHERE 找到相似条目时选择最高列值

简单选择语句的where子句中的MySQL未知列

Linq中,怎么写日期类型字段的Where条件语句

正确格式化的 MySQL 日期插入语句返回全 0