如果查询中的日期时间有时区,MariaDB MySQL 查询要长得多,但如果没有添加时区,则非常快 - 为啥?

Posted

技术标签:

【中文标题】如果查询中的日期时间有时区,MariaDB MySQL 查询要长得多,但如果没有添加时区,则非常快 - 为啥?【英文标题】:MariaDB MySQL query is much longer if datetime in query has timezone, but very fast if no timezone is added - why?如果查询中的日期时间有时区,MariaDB MySQL 查询要长得多,但如果没有添加时区,则非常快 - 为什么? 【发布时间】:2021-06-04 01:09:47 【问题描述】:

我在 MariaDB (mysql) 中有一个表,其中包含 start_date_time 类型为 DATETIME 的字段(未存储 TZ)。

当我做如下请求时,我得到

MariaDB [db]> explain 
SELECT * 
FROM mytable  
WHERE start_date_time>= '2021-03-04 00:00:00+00:00' 
  AND start_date_time<='2021-03-04 11:08:00+00:00' 
ORDER BY start_date_time;
+------+-------------+----------+-------+---------------+---------+---------+------+---------+-------------+
| id   | select_type | table    | type  | possible_keys | key     | key_len | ref  | rows    | Extra       |
+------+-------------+----------+-------+---------------+---------+---------+------+---------+-------------+
|    1 | SIMPLE      | mytable  | index | PRIMARY       | PRIMARY | 5       | NULL | 4504011 | Using where |
+------+-------------+----------+-------+---------------+---------+---------+------+---------+-------------+

在实践中需要很长时间才能完成。该表包含多年的数据,请求大约需要 30 秒,看起来类似于完整扫描。

但是,如果我只是在查询时间中删除 +00:002021-03-04 00:00:00+00:00 变为 2021-03-04 00:00:00),则解释如下所示,查询速度更快:

MariaDB [db]> explain 
SELECT * 
FROM mytable 
WHERE start_date_time>= '2021-03-04 00:00:00' 
  AND start_date_time<='2021-03-04 11:08:00' 
ORDER BY  start_date_time;
+------+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
| id   | select_type | table    | type  | possible_keys | key     | key_len | ref  | rows | Extra       |
+------+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
|    1 | SIMPLE      | mytable  | range | PRIMARY       | PRIMARY | 5       | NULL |    1 | Using where |
+------+-------------+----------+-------+---------------+---------+---------+------+------+-------------+
1 row in set (0.000 sec)

我想了解为什么会出现这种执行时间差异。理想情况下,我希望在查询中保留时区,以避免数据库和客户端之间对时区的任何假设、转换或误解。

更新 根据要求,我还在下面提供 JSON 格式的说明

第一个(慢)查询

 
  "query_block": 
    "select_id": 1,
    "table": 
      "table_name": "mytable",
      "access_type": "index",
      "possible_keys": ["PRIMARY"],
      "key": "PRIMARY",
      "key_length": "5",
      "used_key_parts": ["start_date_time"],
      "rows": 11403954,
      "filtered": 100,
      "attached_condition": "mytable.start_date_time >= '2021-03-04 00:00:00+00:00' and mytable.start_date_time <= '2021-03-04 11:08:00+00:00'"
    
  
 

第二个(快速)查询


| 
  "query_block": 
    "select_id": 1,
    "table": 
      "table_name": "mytable",
      "access_type": "range",
      "possible_keys": ["PRIMARY"],
      "key": "PRIMARY",
      "key_length": "5",
      "used_key_parts": ["start_date_time"],
      "rows": 1,
      "filtered": 100,
      "attached_condition": "mytable.start_date_time >= '2021-03-04 00:00:00' and mytable.start_date_time <= '2021-03-04 11:08:00'"
    
  
 
SHOW CREATE TABLE:

 CREATE TABLE `mytable` (
  `start_date_time` datetime NOT NULL,
  ... more fields...
  PRIMARY KEY (`start_date_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci 

【问题讨论】:

第二个查询没有ORDER BY start_date_time。我认为这是一个错字? 另外,请注明:1)表的主键是什么,2)表的索引列表(MariaDB术语中的“键”)。 MySQL 不支持包括时区在内的日期时间文字。见Date and Time Literals 【参考方案1】:

这看起来像是由于隐式类型转换导致的索引滥用情况。当没有发生类型转换时,它会扫描索引范围 - 找到两个边界值并返回它们之间的所有内容。如果它必须对存储的值应用某种转换函数,那么它会从头到尾扫描整个索引,并根据给定的谓词测试每一行。

我认为您可以尝试将查询中的常量显式转换为DATETIME 以确认此猜想。喜欢:

  WHERE start_date_time >= cast('2021-03-04 00:00:00+00:00' as datetime)
  AND start_date_time <= cast('2021-03-04 11:08:00+00:00' as datetime)

尽管如此,以上可能会丢弃时区信息。我相信,像 convert_tz('2021-03-04 00:00:00', '+00:00', @@GLOBAL.time_zone) 这样的东西会处理得更好。

【讨论】:

【参考方案2】:

一个可能的答案:'2021-03-04 00:00:00+00:00' 只是一个字符串,但'2021-03-04 00:00:00' 被识别为DATETIME。也就是说,MySQL 不支持时区附加,不支持该语法,也不支持带有“T”和/或“Z”的语法。

由于是字符串,在比较之前可能需要将该列转换为字符串;禁止使用任何INDEX。请提供两个查询的EXPLAIN FORMAT=JSON SELECT ...。也许它会指出我的怀疑。

请提供SHOW CREATE TABLE。听起来你有PRIMARY KEY(start_date_time),但我需要确定一下。

DATETIME 没有被 MySQL 调整为时区; TIMESTAMP 是。见(至少)SHOW VARIABLES LIKE '%zone%';

【讨论】:

这也是我的理解。 Date and Time Literals 的手册部分没有提及任何带时区的字符串。 @Rick James,我已将您要求的信息添加到问题中。 @Nick 当您从与数据库服务器所在的 TZ 不同的计算机上的客户端查询数据库时,TZ 怎么办?查询中的时间戳会被 TZ 校正还是精确匹配数据库内容? @AskarIbragimov - 我添加了一段关于“调整 TZ”的段落。 谢谢!我怎么能确定“因为它是一个字符串,所以在比较之前可能需要将该列转换为字符串;禁止使用任何索引” - 发生?

以上是关于如果查询中的日期时间有时区,MariaDB MySQL 查询要长得多,但如果没有添加时区,则非常快 - 为啥?的主要内容,如果未能解决你的问题,请参考以下文章

获取带有时区偏移的日期

如何选择不同时区的日期?

减去不同时区的日期时间

kbmmw 中的日期时间操作

在带有/不带时区的日期或时间戳的查询中处理 generate_series()

Django 日期时间查询