本节描述了 MySQL 维护的时间区域设置、如何加载系统表以支持命名时间支持、如何跟踪时间区域变化以及如何启用闰秒支持。
时间区域偏移量也支持插入的日期时间值;请参阅 第 13.2.2 节,“日期、日期时间和时间戳类型”,以获取更多信息。
有关复制设置中的时间区域设置的信息,请参阅 第 19.5.1.14 节,“复制和系统函数” 和 第 19.5.1.33 节,“复制和时间区域”。
MySQL 服务器维护多个时间区域设置:
-
服务器系统时间区域。当服务器启动时,它尝试确定主机机器的时间区域并使用它来设置
system_time_zone
系统变量。要在 MySQL 服务器启动时明确指定系统时间区域,请在启动服务器之前设置
TZ
环境变量。如果您使用 mysqld_safe 启动服务器,其--timezone
选项提供了另一种设置系统时间区域的方法。TZ
和--timezone
的可接受值取决于操作系统。请参阅操作系统文档,以了解哪些值是可接受的。 -
服务器当前时间区域。全局
time_zone
系统变量指示服务器当前正在操作的时间区域。初始time_zone
值为'SYSTEM'
,这表明服务器时间区域与系统时间区域相同。Note如果设置为
SYSTEM
,每个 MySQL 函数调用都需要时间区域计算都会进行系统库调用,以确定当前系统时间区域。这可能会受到全局互斥锁的保护,导致争用。可以在启动时使用
--default-time-zone
选项或在选项文件中使用以下行来指定初始全局服务器时间区域值:default-time-zone='timezone'
如果您拥有
SYSTEM_VARIABLES_ADMIN
权限(或已弃用的SUPER
权限),可以在运行时使用以下语句设置全局服务器时间区域值:SET GLOBAL time_zone = timezone;
-
每会话时间区域。每个连接的客户端都有其自己的会话时间区域设置,给定会话
time_zone
变量。初始情况下,会话变量从全局time_zone
变量获取其值,但客户端可以使用以下语句更改其自己的时间区域:SET time_zone = timezone;
会话时间区域设置影响显示和存储时区敏感的时间值。这包括由函数显示的值,例如 NOW()
或 CURTIME()
,以及存储在和从 TIMESTAMP
列中的值。这些值从会话时间区域转换为 UTC 进行存储,并从 UTC 转换回会话时间区域进行检索。
会话时间区域设置不影响由函数显示的值,例如 UTC_TIMESTAMP()
,或在 DATE
、TIME
或 DATETIME
列中的值。这些数据类型的值也不存储在 UTC 中;时间区域仅在从 TIMESTAMP
值转换时应用。如果您想要对 DATE
、TIME
或 DATETIME
值执行区域特定的算术运算,请将其转换为 UTC,执行算术运算,然后将其转换回。
当前的全局和会话时间区域值可以通过以下方式检索:
SELECT @@GLOBAL.time_zone, @@SESSION.time_zone;
timezone
值可以以多种格式给出,均不区分大小写:
-
作为值
'SYSTEM'
,表示服务器时间区域与系统时间区域相同。 -
作为字符串,表示 UTC 的偏移量,格式为
[
,以H
]H
:MM
+
或-
开头,例如'+10:00'
、'-6:00'
或'+05:30'
。小时值小于 10 时,可以选择性地使用前导零;MySQL 在存储和检索时将添加前导零。该值必须在
'-13:59'
到'+14:00'
之间(包括两端)。 -
作为命名的时区,例如
'Europe/Helsinki'
、'US/Eastern'
、'MET'
或'UTC'
。
在 mysql
系统架构中存在多个表来存储时区信息(见 第 7.3 节,“mysql 系统架构”)。MySQL 安装过程创建了时区表,但不加载它们。要手动加载,请按照以下说明操作。
加载时区信息不是一次性的操作,因为时区信息会偶尔更改。当这种更改发生时,使用旧规则的应用程序将变得过时,您可能需要重新加载时区表以保持 MySQL 服务器的时区信息当前。请参阅 跟踪时区更改。
如果您的系统具有自己的 zoneinfo 数据库(时区文件集),请使用 mysql_tzinfo_to_sql 程序加载时区表。例如 Linux、macOS、FreeBSD 和 Solaris 等系统。这些文件的可能位置是 /usr/share/zoneinfo
目录。如果您的系统没有 zoneinfo 数据库,可以使用可下载的包,如本节后面所述。
从命令行加载时间区表,传递zoneinfo目录路径名到 mysql_tzinfo_to_sql 并将输出发送到 mysql 程序中。例如:
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
该 mysql 命令假设您使用诸如 root
的账户连接到服务器,该账户具有修改 mysql
系统模式表的权限。根据需要调整连接参数。
mysql_tzinfo_to_sql 读取系统的时间区文件并生成 SQL 语句。 mysql 处理这些语句以加载时间区表。
mysql_tzinfo_to_sql 也可以用于加载单个时间区文件或生成闰秒信息:
-
要加载单个时间区文件
tz_file
,对应于时间区名称tz_name
,invoke mysql_tzinfo_to_sql 如下:mysql_tzinfo_to_sql tz_file tz_name | mysql -u root -p mysql
使用这种方法,您必须执行单独的命令来加载每个命名的时间区文件。
-
如果您的时间区必须考虑闰秒,初始化闰秒信息,如下所示,其中
tz_file
是您的时间区文件名称:mysql_tzinfo_to_sql --leap tz_file | mysql -u root -p mysql
在运行 mysql_tzinfo_to_sql 后,重新启动服务器,以便它不再使用以前缓存的时间区数据。
如果您的系统没有 zoneinfo 数据库(例如 Windows),您可以从 MySQL 开发者区域下载包含 SQL 语句的包:
https://dev.mysql.com/downloads/timezones.html
不要使用可下载的时间区包,如果您的系统有 zoneinfo 数据库。使用 mysql_tzinfo_to_sql 实用程序。否则,您可能会在 MySQL 和系统上的其他应用程序之间引起日期时间处理的差异。
要使用下载的 SQL 语句时间区包,解压缩它,然后将解压缩的文件内容加载到时间区表中:
mysql -u root -p mysql < file_name
然后重新启动服务器。
不要使用包含 MyISAM
表的可下载时间区包。那是为旧版本的 MySQL 设计的。MySQL 现在使用 InnoDB
来存储时间区表。尝试用 MyISAM
表替换它们将引起问题。
当时间区规则变化时,使用旧规则的应用程序将变得过时。为了跟踪最新的信息,确保您的系统使用最新的时间区信息。对于 MySQL,有多个因素需要考虑以跟踪最新的信息:
-
操作系统的时间影响了 MySQL 服务器使用的时间值,如果其时间区设置为
SYSTEM
。确保您的操作系统使用最新的时间区信息。大多数操作系统的最新更新或服务包将您的系统准备好以应对时间变化。检查操作系统供应商的网站,以获取解决时间变化的更新。 -
如果您替换系统的
/etc/localtime
时间区文件,以使用与 mysqld 启动时不同的规则,重新启动 mysqld,以便它使用更新的规则。否则,mysqld 可能不会注意到系统的时间变化。 -
如果您使用命名的时间区与 MySQL,确保
mysql
数据库中的时间区表是最新的:-
如果您的系统有自己的 zoneinfo 数据库,重新加载 MySQL 时间区表,每当 zoneinfo 数据库更新时。
-
对于没有自己的zoneinfo数据库的系统,请检查MySQL开发者区域中的更新。当有新的更新可用时,下载它并用它替换当前时间区表的内容。
有关两种方法的说明,请参阅填充时间区表。mysqld缓存它查找的时间区信息,因此在更新时间区表后,重新启动mysqld,以确保它不继续提供过时的时间区数据。
-
如果您不确定命名时间区是否可用,用于服务器的时间区设置或客户端设置自己的时间区,请检查您的时间区表是否为空。以下查询确定了包含时间区名称的表是否有行:
mysql> SELECT COUNT(*) FROM mysql.time_zone_name;
+----------+
| COUNT(*) |
+----------+
| 0 |
+----------+
零计数表示表为空。在这种情况下,当前没有应用程序使用命名时间区,因此您不需要更新表(除非您想启用命名时间区支持)。大于零的计数表示表不为空,并且其内容可用于命名时间区支持。在这种情况下,请确保重新加载时间区表,以便使用命名时间区的应用程序可以获得正确的查询结果。
要检查您的MySQL安装是否正确更新了夏令时规则的变化,请使用以下测试。该示例使用了适合2007年夏令时1小时变化的值,该变化发生在美国3月11日凌晨2点。
该测试使用以下查询:
SELECT
CONVERT_TZ('2007-03-11 2:00:00','US/Eastern','US/Central') AS time1,
CONVERT_TZ('2007-03-11 3:00:00','US/Eastern','US/Central') AS time2;
两个时间值表示夏令时变化的时间,并且使用命名时间区需要使用时间区表。所需的结果是两个查询返回相同的结果(输入时间,转换为等效的'US/Central'时间区值)。
在更新时间区表之前,您将看到不正确的结果:
+---------------------+---------------------+
| time1 | time2 |
+---------------------+---------------------+
| 2007-03-11 01:00:00 | 2007-03-11 02:00:00 |
+---------------------+---------------------+
更新表后,您应该看到正确的结果:
+---------------------+---------------------+
| time1 | time2 |
+---------------------+---------------------+
| 2007-03-11 01:00:00 | 2007-03-11 01:00:00 |
+---------------------+---------------------+
跳秒值以:59:59
结尾的时间部分返回。这意味着函数,如NOW()
,可以在连续的两到三秒内返回相同的值期间夏令时跳秒。这仍然是正确的,字面时间值的时间部分以:59:60
或:59:61
结尾被认为无效。
如果需要搜索TIMESTAMP
值,在夏令时跳秒之前的一秒内,可能会获得异常结果,如果您使用与'
值的比较。以下示例演示了这一点。它将会话时间区更改为UTC,以便没有内部YYYY-MM-DD hh:mm:ss
'TIMESTAMP
值(在UTC中)和显示值(具有时间区校正应用)之间的差异。
mysql> CREATE TABLE t1 (
a INT,
ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (ts)
);
Query OK, 0 rows affected (0.01 sec)
mysql> -- change to UTC
mysql> SET time_zone = '+00:00';
Query OK, 0 rows affected (0.00 sec)
mysql> -- Simulate NOW() = '2008-12-31 23:59:59'
mysql> SET timestamp = 1230767999;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO t1 (a) VALUES (1);
Query OK, 1 row affected (0.00 sec)
mysql> -- Simulate NOW() = '2008-12-31 23:59:60'
mysql> SET timestamp = 1230768000;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO t1 (a) VALUES (2);
Query OK, 1 row affected (0.00 sec)
mysql> -- values differ internally but display the same
mysql> SELECT a, ts, UNIX_TIMESTAMP(ts) FROM t1;
+------+---------------------+--------------------+
| a | ts | UNIX_TIMESTAMP(ts) |
+------+---------------------+--------------------+
| 1 | 2008-12-31 23:59:59 | 1230767999 |
| 2 | 2008-12-31 23:59:59 | 1230768000 |
+------+---------------------+--------------------+
2 rows in set (0.00 sec)
mysql> -- only the non-leap value matches
mysql> SELECT * FROM t1 WHERE ts = '2008-12-31 23:59:59';
+------+---------------------+
| a | ts |
+------+---------------------+
| 1 | 2008-12-31 23:59:59 |
+------+---------------------+
1 row in set (0.00 sec)
mysql> -- the leap value with seconds=60 is invalid
mysql> SELECT * FROM t1 WHERE ts = '2008-12-31 23:59:60';
Empty set, 2 warnings (0.00 sec)
要解决这个问题,可以使用基于实际存储在列中的UTC值的比较,该值具有夏令时跳秒校正应用:
mysql> -- selecting using UNIX_TIMESTAMP value return leap value
mysql> SELECT * FROM t1 WHERE UNIX_TIMESTAMP(ts) = 1230768000;
+------+---------------------+
| a | ts |
+------+---------------------+
| 2 | 2008-12-31 23:59:59 |
+------+---------------------+
1 row in set (0.00 sec)