文章目录
官方文档地址: 5.1.15 MySQL Server Time Zone Support
本节介绍 MySQL 维护的时区设置,如何加载支持命名时间所需的系统表,如何跟上时区的变化,以及如何启用跳秒支持。
从 MySQL 8.0.19 开始,时区偏移也支持插入DATETIME
值;参见 11.2.2 DATE,DATETIME,和 TIMESTAMP 类型。
有关复制设置中的时区设置的信息,参见 17.5.1.14 复制与系统功能 和 17.5.1.33 复制和时区。
时区变量
MySQL 服务器维护几个时区设置:
1、系统时区。当服务器启动时,它尝试自动确定主机的时区,并使用它来设置system_time_zone
系统变量。此后该值不会改变。
要在启动时显式地指定 MySQL 服务器的系统时区,在启动mysqld
之前设置环境变量TZ
。如果使用mysqld_safe
启动服务器,它的--timezone
选项提供了另一种设置系统时区的方法。TZ
和--timezone
的允许值依赖于系统。参考操作系统文档,看看哪些值是可以接受的。
2、服务器当前时区。全局系统变量time_zone
表示服务器当前运行的时区。初始的time_zone
值为'SYSTEM'
,表示服务器时区与系统时区一致。
注意
如果设置为 SYSTEM,每个需要时区计算的 MySQL 函数调用都会调用一个系统库来确定当前的系统时区。这个调用可能被一个全局互斥锁保护,从而导致争用。
全局服务器的初始时区值可以在启动时通过命令行上的--default time-zone
选项显式指定,也可以在选项文件中(比如my.cnf
(Unix 操作系统)或者my.ini
(Windows))添加以下行:
default-time-zone='timezone'
如果你有SYSTEM_VARIABLES_ADMIN
权限(或者已弃用的SUPER
权限),你可以在运行时使用下面语句设置全局服务器时区值:
SET GLOBAL time_zone = timezone;
3、每个会话的时区。每个连接的客户端都有自己的会话时区设置,由会话time_zone
变量给出。最初,会话变量的值来自全局time_zone
变量,但是客户端可以通过以下语句更改自己的时区:
SET time_zone = timezone;
设置会话时区会影响时区敏感的时间值的显示和存储。这包括NOW()
或CURTIME()
等函数显示的值,以及TIMESTAMP
列中存储的值和从TIMESTAMP
列中检索的值。TIMESTAMP
列的值将从会话时区转换为 UTC 用于存储,从 UTC 转换为会话时区用于检索。
会话时区设置不影响UTC_TIMESTAMP()
等函数显示的值,也不影响DATE
、TIME
或DATETIME
列中的值。这些数据类型的值也不存储 UTC;只有在TIMESTAMP
值转换时用到时区。如果需要DATE
、TIME
或DATETIME
值的特定于区域设置的计算,请将它们转换为 UTC,执行计算,然后再转换回来。
当前全局时区和会话时区值可以这样检索:
SELECT @@GLOBAL.time_zone, @@SESSION.time_zone;
时区值可以用几种格式给出,但都不区分大小写:
- 一个
'SYSTEM'
值,表示服务器时区与系统时区一致。 - 一个表示相对于 UTC 的偏移量的字符串,格式为
[H]H:MM
,前缀为+
或-
,例如'+10:00'
,'-6:00'
,或'+05:30'
。前导零可用于小时值小于 10 的情况;在这种情况下,MySQL在存储和检索值时,前面加一个 0。MySQL 将'-00:00'
或'-0:00'
转换为'+00:00'
。
在 MySQL 8.0.19 之前,这个值必须在'-12:59'
到'+13:00'
范围内,包括这两个值;从 MySQL 8.0.19 开始,允许的范围是'-14:00'
到'+14:00'
,包括这两值在内。 - 一个命名的时区,如
'Europe/Helsinki'
、'US/Eastern'
、'MET'
或'UTC'
。
注意
只有已经创建并填充了mysql
数据库中的时区信息表,才可以使用命名时区。否则,使用指定时区会导致错误:mysql> SET time_zone = 'UTC';
ERROR 1298 (HY000): Unknown or incorrect time zone: 'UTC'
填充时区表
mysql
系统模式中存在几个表来存储时区信息(参见 5.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
也可以用于加载单个时区文件或生成跳秒信息:
1、要加载与时区名称tz_name
对应的单个时区文件tz_file
,请如下所示调用mysql_tzinfo_to_sql
:
mysql_tzinfo_to_sql tz_file tz_name | mysql -u root -p mysql
使用这种方法,您必须执行一个单独的命令,为服务器需要了解的每个指定区域加载时区文件。
2、如果你的时区必须考虑跳秒,初始化闰秒信息如下,其中tz_file
是你的时区文件的名称:
mysql_tzinfo_to_sql --leap tz_file | mysql -u root -p mysql
在运行mysql_tzinfo_to_sql
之后,重新启动服务器,这样它就不会继续使用任何以前缓存的时区数据。
如果您的系统没有 zoneinfo 数据库(例如 Windows),你可以使用包含 SQL 语句的包,可以在 MySQL 开发者社区下载:
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
,操作系统的时间会影响 MySQL 服务器的时间。确保您的操作系统使用最新的时区信息。对于大多数操作系统,最新的更新或服务包会让系统为时间更改做好准备。请查看您的操作系统供应商的网站,以获取处理时间变化的更新。 - 如果您将系统的
/etc/localtime
时区文件替换为使用不同于mysqld
启动时使用的规则的版本,重新启动mysqld
,使其使用更新后的规则。否则,mysqld
可能不会注意到系统更改了它的时间。 - 如果你在 MySQL 中使用命名时区,请确保 MySQL 数据库中的时区表是最新的:
如果您的系统有自己的 zoneinfo 数据库,请在 zoneinfo 数据库更新时重新加载 MySQL 时区表。
对于没有自己的 zoneinfo 数据库的系统,请检查 MySQL 开发者社区的更新。当新的更新可用时,下载它并使用它来替换当前时区表的内容。
对于这两种方法的说明,请参考填充时区表,mysqld
缓存它所查找的时区信息,因此在更新时区表后,重启mysqld
以确保它不会继续提供过时的时区数据。
如果您不确定指定的时区是否可用(用于服务器的时区设置或由客户端设置自己的时区),请检查时区表是否为空。下面的查询确定包含时区名的表是否有行:
mysql> SELECT COUNT(*) FROM mysql.time_zone_name;
+----------+
| COUNT(*) |
+----------+
| 0 |
+----------+
计数为零表示表为空。在这种情况下,目前没有应用程序使用命名时区,您不需要更新表(除非您希望启用命名时区支持)。大于零的计数表明表不是空的,其内容可用于指定时区支持。在这种情况下,请确保重新加载时区表,以便使用已命名时区的应用程序可以获得正确的查询结果。
为了检查您的 MySQL 安装是否正确地更新了夏令时规则,使用如下的测试。该示例使用的值是美国 3 月 11 日凌晨 2 点发生的 2007 年 DST 1 小时更改。
测试使用这个查询:
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
值,则使用'YYYY-MM-DD hh:mm:ss'
值进行比较,可能会得到异常结果。下面的示例演示了这一点。它将会话时区更改为 UTC,这样内部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> -- 改为 UTC
mysql> SET time_zone = '+00:00';
Query OK, 0 rows affected (0.00 sec)
mysql> -- 模拟 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> -- 模拟 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> -- 值内部不同,但显示相同
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> -- 只有非跳跃值匹配
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> -- 秒=60 的跳跃值无效
mysql> SELECT * FROM t1 WHERE ts = '2008-12-31 23:59:60';
Empty set, 2 warnings (0.00 sec)
为了解决这个问题,你可以使用一个基于 UTC 值的比较,该值实际上存储在列中,并应用了跳秒校正:
mysql> -- 选择使用 UNIX_TIMESTAMP 函数返回跳跃值
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)