### 5.1.15 MySQL 服务器时区支持

[](<>)

本节介绍 MySQL 维护的时区设置，如何加载命名时间支持所需的系统表，如何保持最新的时区变化，以及如何启用闰秒支持。

从 MySQL 8.0.19 开始，插入的日期时间值也支持时区偏移；看[第 11.2.2 节，“DATE、DATETIME 和 TIMESTAMP 类型”](datetime.html)， 了解更多信息。

有关复制设置中的时区设置的信息，请参阅[第 17.5.1.14 节，“复制和系统功能”](replication-features-functions.html)和[第 17.5.1.33 节，“复制和时区”](replication-features-timezone.html).

-   [时区变量](time-zone-support.html#time-zone-variables)

-   [填充时区表](time-zone-support.html#time-zone-installation)

-   [与时区变化保持同步](time-zone-support.html#time-zone-upgrades)

-   [时区闰秒支持](time-zone-support.html#time-zone-leap-seconds)

#### 时区变量

MySQL 服务器维护几个时区设置：

-   服务器系统时区。当服务器启动时，它会尝试确定主机的时区并使用它来设置[`system_time_zone`](server-system-variables.html#sysvar_system_time_zone)系统变量。

    [](<>)[](<>)[](<>)[](<>)

    要在启动时显式指定 MySQL 服务器的系统时区，请设置`太极`开始之前的环境变量[**mysqld**](mysqld.html).如果您使用启动服务器[**mysqld\_安全的**](mysqld-safe.html)， 它的[`- 时区`](mysqld-safe.html#option_mysqld_safe_timezone)选项提供了另一种设置系统时区的方法。的允许值`TZ`和[`--时区`](mysqld-safe.html#option_mysqld_safe_timezone)它们依赖于系统。请查阅操作系统文档，查看哪些值是可接受的。

-   服务器当前时区。全球[`时区`](server-system-variables.html#sysvar_time_zone)系统变量表示服务器当前运行的时区。首字母[`时区`](server-system-variables.html#sysvar_time_zone)价值是`“系统”`，表示服务器时区与系统时区相同。

    [](<>)[](<>)

    笔记

    如果设置为`系统`，每个需要进行时区计算的MySQL函数调用都会进行系统库调用，以确定当前的系统时区。此调用可能受到全局互斥锁的保护，从而导致争用。

    初始全局服务器时区值可以在启动时使用[`--默认时区`](server-options.html#option_mysqld_default-time-zone)选项，也可以在选项文件中使用以下行：

    ```
    default-time-zone='timezone'
    ```

    如果你有[`系统变量管理`](privileges-provided.html#priv_system-variables-admin)特权（或不推荐的[`超级的`](privileges-provided.html#priv_super)特权），可以使用以下语句在运行时设置全局服务器时区值：

    ```
    SET GLOBAL time_zone = timezone;
    ```

-   每个会话的时区。每个连接的客户端都有自己的会话时区设置，由会话指定[`时区`](server-system-variables.html#sysvar_time_zone)变量最初，session变量的值来自全局变量[`时区`](server-system-variables.html#sysvar_time_zone)变量，但客户端可以使用以下语句更改自己的时区：

    ```
    SET time_zone = timezone;
    ```

    会话时区设置会影响对区域敏感的时间值的显示和存储。这包括由以下函数显示的值：[`现在（）`](date-and-time-functions.html#function_now)或[`柯蒂姆`](date-and-time-functions.html#function_curtime)，以及存储在中并从中检索的值[`时间戳`](datetime.html)柱。价值观[`时间戳`](datetime.html)列从会话时区转换为UTC进行存储，从UTC转换为会话时区进行检索。

    会话时区设置不影响以下功能显示的值：[`UTC_时间戳（）`](date-and-time-functions.html#function_utc-timestamp)或价值观[`日期`](datetime.html), [`时间`](time.html)或[`约会时间`](datetime.html)柱。这些数据类型中的值也不会存储在UTC中；时区仅在从转换时适用[`时间戳`](datetime.html)价值观如果您想要特定于语言环境的算法[`日期`](datetime.html), [`时间`](time.html)或[`约会时间`](datetime.html)值，将其转换为UTC，执行算术运算，然后再转换回。

    当前全局和会话时区值可以如下方式检索：


```
SELECT @@GLOBAL.time_zone, @@SESSION.time_zone;
```

*`时区`*可以以多种格式给出值，但没有一种格式区分大小写：

-   作为价值`“系统”`，表示服务器时区与系统时区相同。

-   作为一个字符串，指示窗体的UTC偏移量`[*`H`*]*`H`*:*`嗯`*`，前缀为`+`或`-`例如`'+10:00'`, `'-6:00'`或`'+05:30'`.前导零可选择性地用于小于10的小时值；在这种情况下，MySQL在存储和检索值时会预先设置一个前导零。MySQL转换`'-00:00'`或`'-0:00'`到`'+00:00'`.

    在MySQL 8.0.19之前，该值必须在`'-12:59'`到`'+13:00'`包含全部费用从MySQL 8.0.19开始，允许的范围是`'-13:59'`到`'+14:00'`包含全部费用

-   作为命名时区，例如`“欧洲/赫尔辛基”`, `“美国/东部”`, `“大都会”`或`“UTC”`.

    笔记

    仅当中的时区信息表`mysql`已经创建并填充了数据库。否则，使用命名时区会导致错误：

    [](<>)[](<>)

    ```
    mysql> SET time_zone = 'UTC';
    ERROR 1298 (HY000): Unknown or incorrect time zone: 'UTC'
    ```

#### 填充时区表

房间里有几张桌子`mysql`存在存储时区信息的系统架构（请参阅[第5.3节，“mysql系统模式”](system-schema.html)).MySQL安装过程会创建时区表，但不会加载它们。要手动执行此操作，请使用以下说明。

笔记

加载时区信息不一定是一次性操作，因为信息偶尔会更改。发生此类更改时，使用旧规则的应用程序将过时，您可能会发现有必要重新加载时区表，以保持MySQL服务器使用的信息最新。看见[跟上时区的变化](time-zone-support.html#time-zone-upgrades).

如果系统有自己的zoneinfo数据库（描述时区的一组文件），请使用[**mysql_tzinfo\_到\_sql**](mysql-tzinfo-to-sql.html)加载时区表的程序。例如Linux、macOS、FreeBSD和Solaris。这些文件的一个可能位置是`/usr/share/zoneinfo`目录如果您的系统没有zoneinfo数据库，则可以使用可下载的软件包，如本节后面所述。

要从命令行加载时区表，请将zoneinfo目录路径名传递给[**mysql_tzinfo\_到\_sql**](mysql-tzinfo-to-sql.html)并将输出发送到[**mysql**](mysql.html)程序例如：

```
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
```

这个[**mysql**](mysql.html)此处显示的命令假定您使用以下帐户连接到服务器：`根`具有在中修改表的权限`mysql`系统模式。根据需要调整连接参数。

[**mysql_tzinfo\_到\_sql**](mysql-tzinfo-to-sql.html)读取系统的时区文件并从中生成SQL语句。[**mysql**](mysql.html)处理这些语句以加载时区表。

[**mysql_tzinfo\_到\_sql**](mysql-tzinfo-to-sql.html)也可用于加载单个时区文件或生成闰秒信息：

-   加载单个时区文件*`tz_文件`*对应于时区名称的*`祖名`*援引[**mysql_tzinfo\_到\_sql**](mysql-tzinfo-to-sql.html)这样地：

    ```
    mysql_tzinfo_to_sql tz_file tz_name | mysql -u root -p mysql
    ```

    使用这种方法，必须执行单独的命令，为服务器需要了解的每个命名区域加载时区文件。

-   如果您的时区必须考虑闰秒，请像这样初始化闰秒信息，其中*`tz_文件`*是时区文件的名称：

    ```
    mysql_tzinfo_to_sql --leap tz_file | mysql -u root -p mysql
    ```

    跑步后[**mysql_tzinfo\_到\_sql**](mysql-tzinfo-to-sql.html)，重新启动服务器，使其不再继续使用以前缓存的任何时区数据。

    如果您的系统没有zoneinfo数据库（例如Windows），则可以使用包含SQL语句的软件包，可在MySQL开发者专区下载：


```
https://dev.mysql.com/downloads/timezones.html
```

警告

做*不*如果您的系统有zoneinfo数据库，请使用可下载的时区软件包。使用[**mysql_tzinfo\_到\_sql**](mysql-tzinfo-to-sql.html)取而代之的是公用事业。否则，您可能会导致MySQL和系统上的其他应用程序在日期时间处理上存在差异。

要使用下载的SQL语句时区包，请将其解压缩，然后将解压缩的文件内容加载到时区表中：

```
mysql -u root -p mysql < file_name
```

然后重启服务器。

警告

做*不*使用可下载的时区软件包，其中包含`米萨姆`桌子。这是针对较旧的MySQL版本的。MySQL现在使用`InnoDB`时区表。试图用`米萨姆`表格会导致问题。

#### 跟上时区的变化

[](<>)

当时区规则更改时，使用旧规则的应用程序将过时。要保持最新状态，必须确保系统使用当前时区信息。对于MySQL来说，在当前的潮流中有多种因素需要考虑：

-   如果MySQL服务器的时区设置为`系统`。确保您的操作系统使用最新的时区信息。对于大多数操作系统，最新的更新或service pack会为系统的时间变化做好准备。请访问您的操作系统供应商的网站，以获取解决时间更改的更新。

-   如果更换系统的`/etc/当地时间`时区文件，其版本使用的规则与当前生效的规则不同[**mysqld**](mysqld.html)启动，重启[**mysqld**](mysqld.html)以便使用更新的规则。否则[**mysqld**](mysqld.html)可能不会注意到系统何时更改其时间。

-   如果在MySQL中使用命名时区，请确保`mysql`数据库是最新的：

    -   如果您的系统有自己的zoneinfo数据库，请在zoneinfo数据库更新时重新加载MySQL时区表。

    -   对于没有自己的zoneinfo数据库的系统，请查看MySQL开发者专区以获取更新。当有新的更新可用时，下载它并使用它替换当前时区表的内容。

        有关这两种方法的说明，请参阅[填充时区表](time-zone-support.html#time-zone-installation). [**mysqld**](mysqld.html)缓存它查找的时区信息，因此在更新时区表后，重新启动[**mysqld**](mysqld.html)确保它不会继续提供过时的时区数据。

    如果不确定指定的时区是否可用（用作服务器的时区设置或由设置自己时区的客户端使用），请检查时区表是否为空。以下查询确定包含时区名称的表是否有任何行：


```
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;
```

这两个时间值表示DST更改发生的时间，使用命名时区需要使用时区表。期望的结果是两个查询返回相同的结果（输入时间，转换为“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`.这意味着[`现在（）`](date-and-time-functions.html#function_now)可以在闰秒期间连续两秒或三秒返回相同的值。文字时间值的时间部分以`:59:60`或`:59:61`被认为无效。

如果有必要搜索[`时间戳`](datetime.html)值在闰秒前一秒，如果与`'*`YYYY-MM-DD hh:mm:ss`*'`价值观下面的示例演示了这一点。它将会话时区更改为UTC，因此内部[`时间戳`](datetime.html)值（以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)
```
