文档首页
MySQL 8.4 参考手册
相关文档 下载本手册
PDF (US Ltr) - 39.9Mb
PDF (A4) - 40.0Mb
手册页 (TGZ) - 258.5Kb
手册页 (Zip) - 365.5Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 参考手册  /  ...  /  来自 TIMESTAMP 列的索引查找

10.3.14 来自 TIMESTAMP 列的索引查找

时间值存储在 TIMESTAMP 列中作为 UTC 值,插入和检索到 TIMESTAMP 列中的值会在会话时区和 UTC 之间进行转换。(这与 CONVERT_TZ() 函数执行的转换类型相同。如果会话时区是 UTC,则实际上不会进行时区转换。)

由于诸如夏令时 (DST) 之类的当地时区更改的约定,UTC 与非 UTC 时区之间的转换在两个方向上都不是一一对应的。不同的 UTC 值在另一个时区中可能不不同。以下示例显示了在非 UTC 时区中变得相同的不同 UTC 值

mysql> CREATE TABLE tstable (ts TIMESTAMP);
mysql> SET time_zone = 'UTC'; -- insert UTC values
mysql> INSERT INTO tstable VALUES
       ('2018-10-28 00:30:00'),
       ('2018-10-28 01:30:00');
mysql> SELECT ts FROM tstable;
+---------------------+
| ts                  |
+---------------------+
| 2018-10-28 00:30:00 |
| 2018-10-28 01:30:00 |
+---------------------+
mysql> SET time_zone = 'MET'; -- retrieve non-UTC values
mysql> SELECT ts FROM tstable;
+---------------------+
| ts                  |
+---------------------+
| 2018-10-28 02:30:00 |
| 2018-10-28 02:30:00 |
+---------------------+
注意

要使用诸如 'MET''Europe/Amsterdam' 之类的命名时区,必须正确设置时区表。有关说明,请参阅 第 7.1.15 节,“MySQL 服务器时区支持”

您可以看到,当转换为 'MET' 时区时,两个不同的 UTC 值是相同的。这种现象会导致针对给定 TIMESTAMP 列查询的不同结果,具体取决于优化器是否使用索引来执行查询。

假设一个查询从前面显示的表中选择值,并使用 WHERE 子句在 ts 列中搜索单个特定值,例如用户提供的时间戳文字

SELECT ts FROM tstable
WHERE ts = 'literal';

假设查询在以下条件下执行

  • 会话时区不是 UTC,并且具有 DST 偏移。例如

    SET time_zone = 'MET';
  • 存储在 TIMESTAMP 列中的唯一 UTC 值由于 DST 偏移在会话时区中并不唯一。(前面的示例说明了这种情况如何发生。)

  • 查询指定一个搜索值,该值位于会话时区进入 DST 的小时内。

在这些条件下,WHERE 子句中的比较对于非索引和索引查找的方式不同,并导致不同的结果

  • 如果没有索引或优化器无法使用索引,则比较在会话时区中进行。优化器执行表扫描,在扫描中它检索每个 ts 列值,将其从 UTC 转换为会话时区,并将其与搜索值(也在会话时区中解释)进行比较

    mysql> SELECT ts FROM tstable
           WHERE ts = '2018-10-28 02:30:00';
    +---------------------+
    | ts                  |
    +---------------------+
    | 2018-10-28 02:30:00 |
    | 2018-10-28 02:30:00 |
    +---------------------+

    由于存储的 ts 值已转换为会话时区,因此查询可能会返回两个时间戳值,这些值作为 UTC 值是不同的,但在会话时区中是相等的:一个值发生在时钟更改时的 DST 偏移之前,另一个值发生在 DST 偏移之后。

  • 如果有可用的索引,则比较在 UTC 中进行。优化器执行索引扫描,首先将搜索值从会话时区转换为 UTC,然后将结果与 UTC 索引条目进行比较

    mysql> ALTER TABLE tstable ADD INDEX (ts);
    mysql> SELECT ts FROM tstable
           WHERE ts = '2018-10-28 02:30:00';
    +---------------------+
    | ts                  |
    +---------------------+
    | 2018-10-28 02:30:00 |
    +---------------------+

    在这种情况下,(转换后的)搜索值仅与索引条目匹配,并且由于不同存储的 UTC 值的索引条目也是不同的,因此搜索值只能与其中一个条目匹配。

由于对于非索引和索引查找,优化器操作不同,因此查询在每种情况下都产生不同的结果。非索引查找的结果返回会话时区中匹配的所有值。索引查找不能这样做

  • 它在存储引擎内执行,存储引擎只了解 UTC 值。

  • 对于映射到相同 UTC 值的两个不同的会话时区值,索引查找仅匹配相应的 UTC 索引条目,并且仅返回一行。

在前面的讨论中,存储在 tstable 中的数据集恰好由不同的 UTC 值组成。在这种情况下,所有形式如所示的索引查询最多匹配一个索引条目。

如果索引不是 UNIQUE,则表(和索引)可能存储给定 UTC 值的多个实例。例如,ts 列可能包含 UTC 值 '2018-10-28 00:30:00' 的多个实例。在这种情况下,使用索引的查询将返回它们中的每一个(在结果集中转换为 MET 值 '2018-10-28 02:30:00')。仍然正确的是,使用索引的查询将转换后的搜索值与 UTC 索引条目中的单个值匹配,而不是将多个转换为会话时区的搜索值的 UTC 值匹配。

如果必须返回在会话时区中匹配的所有 ts 值,则解决方法是使用 IGNORE INDEX 提示来抑制索引的使用。

mysql> SELECT ts FROM tstable
       IGNORE INDEX (ts)
       WHERE ts = '2018-10-28 02:30:00';
+---------------------+
| ts                  |
+---------------------+
| 2018-10-28 02:30:00 |
| 2018-10-28 02:30:00 |
+---------------------+

双向时区转换的这种一对多映射缺失也出现在其他上下文中,例如使用 FROM_UNIXTIME()UNIX_TIMESTAMP() 函数执行的转换。参见 第 14.7 节,“日期和时间函数”