暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

技术|时区之第2篇MySQL数据库时区

DigOps 2022-11-30
570

关于时区,第1篇介绍了Linux操作系统的时区,以及如何修改。对于MySQL来说的时区来说,是依赖于操作系统的,具体情况下面讲会展开介绍。

本文内容包括以下几方面:数据库时区查看、数据库时区修改、修改时区的影响及验证。

关于查看

时区的查看,可以通过

mysql> show variables like '%zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | CST    |
| time_zone        | SYSTEM |
+------------------+--------+
2 rows in set (0.03 sec)TEXT

这里与时区有关的参数,只有system_time_zone
time_zone

  • system_time_zone
    :全局变量,无会话级别,服务器系统时区,数据库启动时加载的值,依赖服务器。

  • time_zone
    :全局变量,数据库时区,如果为SYSTEM
    ,表示使用system_time_zone的值,也可以单独设置。

这里可以看得出,如果time_zone设置为SYSTEM
,上面两个值是相等的。

关于修改

1. system_time_zone

修改系统的时区由CTS到UTC,数据库重启后:

mysql> show variables like '%zone%';

+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | UTC    |
| time_zone        | SYSTEM |
+------------------+--------+
2 rows in set (0.03 sec)TEXT

这时数据库的时区是完全有操作系统时区决定的!!!

2. time_zone

这个参数是真正决定数据的时区的参数,可动态设置。

1️⃣ 有三种方式可以设置:

  1. 启动 mysqld_safe 时加选项--timezone
    ,或mysqld启动选项中加--default-time-zone
    (永久生效)

  2. 在数据库参数文件中加default-time-zone
    (永久生效)

  3. 在数据库启动后,通过set global time_zone=
    的命令(临时生效)。

建议使用第二种方式!

2️⃣ 下面简单的对以上方法进行实验:

(系统默认时区为UTC)

  • 方式一:

🌳 在mysqld启动项中加--default-time-zone

-bash-4.2$ date
Mon Nov 28 16:56:01 UTC 2022
-bash-4.2$ mysqld --default-time-zone='+08:00'TEXT

再开一个窗口,查询

mysql> show variables like '%zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | UTC    |
| time_zone        | +08:00 |
+------------------+--------+
2 rows in set (0.01 sec)TEXT

🌳 在mysqld_safe中开启--timezone
选项

由于我是通过RPM安装的数据库,没有mysqld_safe工具,过程和mysqld是一样的,这里不再演示。

  • 方式二:

1)修改数据库参数/etc/my.cnf

增加default-time-zone='+08:00'

2)重启数据库验证

[root@postgresql ~]# systemctl restart mysqld
[root@postgresql ~]# mysql -uroot -p123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.31 MySQL Community Server - GPL

Copyright (c) 2000, 2022, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show variables like '%zone%';
+------------------+--------+
| Variable_name    | Value  |
+------------------+--------+
| system_time_zone | UTC    |
| time_zone        | +08:00 |
+------------------+--------+
2 rows in set (0.02 sec)TEXT
  • 方式三:

这里需要注意是否使用global,不使用则表示会话级,使用则是全局级。二者的区别就在于生效范围上。会话级只针对当前会话,全局级则是针对所有新连接生效,已连接则不受影响。

【例】全局设置

set global time_zone='+00:00';TEXT

通过如下语句可以验证。

mysql> select @@global.time_zone,@@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| +00:00             | +08:00              |
+--------------------+---------------------+
1 row in set (0.00 sec)TEXT

【例】会话级设置

set  time_zone='+00:00';TEXT

通过如下语句可以验证。

mysql> select @@global.time_zone,@@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| +00:00             | +00:00              |
+--------------------+---------------------+
1 row in set (0.00 sec)TEXT

影响及验证

这一节包括两方面,方面一:对时间影响范围及验证。方面二:命名时区问题。方面三:对主从复制的影响

从官方文档上查阅到,如果要变更时区,会有如下影响:

会影响区域敏感的时间值的显示和存储。这包括NOW()
CURTIME()
等函数显示的值,以及存储在TIMESTAMP
列中并从中检索的值。TIMESTAMP
列的值从会话时区转换为UTC进行存储,从UTC转换为会话时区进行检索。

不影响UTC_TIMESTAMP()
等函数或DATE
TIME
DATETIME
列中的值显示的值。这些数据类型的值也不存储在UTC中;时区仅适用于从TIMESTAMP
值转换时。如果您想要DATE
TIME
DATETIME
值的区域设置特定算法,请将它们转换为UTC,执行算术,然后转换回来

1)先验证对函数NOW()、CURTIME()和UTC_TIMESTAMP()的影响:

mysql> select @@global.time_zone,@@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| +00:00             | +08:00              |
+--------------------+---------------------+
1 row in set (0.00 sec)

mysql> select now(),curtime(),utc_timestamp();
+---------------------+-----------+---------------------+
| now()               | curtime() | utc_timestamp()     |
+---------------------+-----------+---------------------+
| 2022-11-29 01:25:34 | 01:25:34  | 2022-11-28 17:25:34 |
+---------------------+-----------+---------------------+
1 row in set (0.00 sec)

mysql> set  time_zone='+00:00';
Query OK, 0 rows affected (0.00 sec)

mysql> select now(),curtime(),utc_timestamp();
+---------------------+-----------+---------------------+
| now()               | curtime() | utc_timestamp()     |
+---------------------+-----------+---------------------+
| 2022-11-28 17:25:50 | 17:25:50  | 2022-11-28 17:25:50 |
+---------------------+-----------+---------------------+
1 row in set (0.00 sec)TEXT

很明显,改变了会话的时区后,NOW()、CURTIME()的时间日期显示变化了,而UTC_TIMESTAMP()则不变。

2)再验证数据类型TIMESTAMP、DATE、TIME、DATETIME的影响。

mysql> select @@global.time_zone,@@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| +00:00             | +00:00              |
+--------------------+---------------------+
1 row in set (0.00 sec)

mysql>  insert into tz values('2022-11-28 10:24:48','2022-11-28 10:24:48','2022-11-28 10:24:48','2022-11-28 10:24:48');
Query OK, 1 row affected, 2 warnings (0.04 sec)

mysql> show warnings;
+-------+------+---------------------------------------------------------------------+
| Level | Code | Message                                                             |
+-------+------+---------------------------------------------------------------------+
| Note  | 1292 | Incorrect date value: '2022-11-28 10:24:48' for column 'b' at row 1 |
| Note  | 1292 | Incorrect time value: '2022-11-28 10:24:48' for column 'c' at row 1 |
+-------+------+---------------------------------------------------------------------+
2 rows in set (0.00 sec)

mysql> select * from tz;
+---------------------+------------+----------+---------------------+
| a                   | b          | c        | d                   |
+---------------------+------------+----------+---------------------+
| 2022-11-28 10:24:48 | 2022-11-28 | 10:24:48 | 2022-11-28 10:24:48 |
+---------------------+------------+----------+---------------------+
1 row in set (0.00 sec)

mysql> set  time_zone='+08:00';
Query OK, 0 rows affected (0.00 sec)

mysql> select * from tz;
+---------------------+------------+----------+---------------------+
| a                   | b          | c        | d                   |
+---------------------+------------+----------+---------------------+
| 2022-11-28 18:24:48 | 2022-11-28 | 10:24:48 | 2022-11-28 10:24:48 |
+---------------------+------------+----------+---------------------+
1 row in set (0.00 sec)TEXT

这里建了一张表,包含了所涉及的时间类型,在时区发生变化后,只有timestamp类型是随着时区而变化的。也就证明这个类型确实是存在UTC中的。

3)命名时区问题,其实是时区的别名问题。

比如,CST是"+08:00"的命名时区。默认情况下,是无法直接使用CST来指定时区的,这需要在数据库中填充时区表。

mysql> SET time_zone = 'UTC';
ERROR 1298 (HY000): Unknown or incorrect time zone: 'UTC'TEXT
  • 如何填充时区表?

一个前提:操作系统需要支持zoneinfo数据库,如:Linux、macOS、FreeBSD和Solaris都有的。windows没有,需要去官网下载

[root@postgresql ~]# mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -uroot -p123456 mysql
mysql: [Warning] Using a password on the command line interface can be insecure.
Warning: Unable to load '/usr/share/zoneinfo/iso3166.tab' as time zone. Skipping it.
Warning: Unable to load '/usr/share/zoneinfo/leapseconds' as time zone. Skipping it.
Warning: Unable to load '/usr/share/zoneinfo/tzdata.zi' as time zone. Skipping it.
Warning: Unable to load '/usr/share/zoneinfo/zone.tab' as time zone. Skipping it.
Warning: Unable to load '/usr/share/zoneinfo/zone1970.tab' as time zone. Skipping it.TEXT

这时,我们可以通过查询mysql.time_zone_name
时区表是否已经填充了

mysql> SELECT COUNT(*) FROM mysql.time_zone_name;
+----------+
| COUNT(*) |
+----------+
|     1780 |
+----------+
1 row in set (0.06 sec)

mysql> SELECT * FROM mysql.time_zone_name;
+----------------------------------------+--------------+
| Name                                   | Time_zone_id |
+----------------------------------------+--------------+
| Africa/Abidjan                         |            1 |
| Africa/Accra                           |            2 |
| Africa/Addis_Ababa                     |            3 |
| Africa/Algiers                         |            4 |
| Africa/Asmara                          |            5 |
| Africa/Asmera                          |            6 |
| Africa/Bamako                          |            7 |
| Africa/Bangui                          |            8 |
| Africa/Banjul                          |            9 |
| Africa/Bissau                          |           10 |
| Africa/Blantyre                        |           11 |
| Africa/Brazzaville                     |           12 |
| Africa/Bujumbura                       |           13 |
| Africa/Cairo                           |           14 |
| Africa/Casablanca                      |           15 |
| Africa/Ceuta                           |           16 |
| Africa/Conakry                         |           17 |
...TEXT

这时再通过命名时区去设置

mysql> set time_zone='UTC';
Query OK, 0 rows affected (0.00 sec)

mysql> select @@global.time_zone,@@session.time_zone;
+--------------------+---------------------+
| @@global.time_zone | @@session.time_zone |
+--------------------+---------------------+
| +00:00             | UTC                 |
+--------------------+---------------------+
1 row in set (0.00 sec)TEXT

4)主从复制的影响

引用官方原文

默认情况下,源服务器和副本服务器假设它们位于同一时区。如果您在不同时区的服务器之间复制,则必须在源服务器和副本上设置时区。否则,根据源上的本地时间而定的语句不会正确复制,例如使用NOW()
FROM_UNIXTIME()
函数的语句。

验证源和副本上系统时区(system_time_zone
)、服务器当前时区(time_zone
的全局值)和每个会话时区(time_zone
的会话值)的设置组合是否产生正确的结果。特别是,如果time_zone
系统变量设置为值SYSTEM
,表明服务器时区与系统时区相同,这可能会导致源和副本应用不同的时区。

为了避免因时区导致的异常,对于国内大多数企业的业务仍多以国内为主。建议将数据库的时区进行明确设置,且不依赖操作系统的设置。


文章转载自DigOps,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论