MySQL 總是差八個(gè)小時(shí),如何破?
今天來(lái)聊一個(gè)簡(jiǎn)單的話題,這是一個(gè)小伙伴在微信上問(wèn)我的,對(duì)于初學(xué)者我非常能理解這類問(wèn)題帶來(lái)的困擾,各種嘗試,各種搜索,別人說(shuō)的頭頭是道,但是就是解決不了自己的問(wèn)題,今天我簡(jiǎn)單從兩個(gè)方面來(lái)和大家聊聊這個(gè)問(wèn)題,如果小伙伴們有其他的解決思路,也可以留言一起分享。
這個(gè)問(wèn)題我們可以從兩方面來(lái)分析:
- MySQL 本身的問(wèn)題。
- Java 代碼的問(wèn)題。
1. MySQL 本身問(wèn)題
MySQL 本身問(wèn)題,這個(gè)其實(shí)很好驗(yàn)證,不就是時(shí)間么,我們執(zhí)行如下 SQL 看看 MySQL 上的時(shí)間跟我的電腦時(shí)間是否是一致的:
select now();
可以看到,MySQL 的這個(gè)時(shí)間跟我系統(tǒng)的時(shí)間其實(shí)就差了 8 小時(shí),MySQL 本身的時(shí)間都不對(duì),那你將來(lái)插入/查詢的時(shí)間肯定也不對(duì)。
這個(gè)查詢大家注意,要么使用命令行操作,要么使用 Sqlyog、Navicat 或者 Sequel Pro 之類的數(shù)據(jù)庫(kù)工具來(lái)操作,切勿使用 JDBC 來(lái)查詢,具體原因一會(huì)看完第二小節(jié)就明白了。
出現(xiàn)這個(gè)問(wèn)題,多半是 MySQL 的時(shí)區(qū)不太對(duì),我們重新給其設(shè)置一下時(shí)區(qū)即可。
首先我們通過(guò)如下指令來(lái)查看一下 MySQL 當(dāng)前的時(shí)區(qū):
show variables like '%time_zone%';
可以看到,MySQL 說(shuō)它的時(shí)區(qū)是 SYSTEM,那 SYSTEM 又是啥呢?第一條說(shuō)了 SYSTEM 是 UTC(協(xié)調(diào)世界時(shí),又稱世界標(biāo)準(zhǔn)時(shí)間或世界協(xié)調(diào)時(shí)間)。而我們的北京時(shí)間比 UTC 快了 8 小時(shí),即 UTC+8。
所以我們現(xiàn)在要把 MySQL 的時(shí)區(qū)先給改對(duì),可以通過(guò)修改配置文件來(lái)實(shí)現(xiàn)( /etc/mysql/mysql.conf.d/mysqld.cnf ),如下:
修改完成后,重啟 MySQL,再來(lái)查看 MySQL 的時(shí)區(qū):
可以看到,此時(shí)的 MySQL 時(shí)區(qū)就正常了。
那么此時(shí)再執(zhí)行 select now(); 也就不會(huì)有問(wèn)題了:
有的小伙伴可能嫌修改配置文件太麻煩了,那么也可以通過(guò) SQL 來(lái)修改時(shí)區(qū):
set global time_zone = Asia/Shanghai
注意我們所在的時(shí)區(qū)是 Asia/Shanghai,小伙伴們不要自由發(fā)揮寫其他城市。
首先我們要確認(rèn) MySQL 沒(méi)問(wèn)題。
2. JDBC 連接問(wèn)題
當(dāng)確認(rèn)了 MySQL 沒(méi)有問(wèn)題后,如果你的 MySQL 時(shí)間還是不對(duì),那么就有可能是 JDBC 連接的問(wèn)題了。
這里我用大家常見(jiàn)的 JdbcTemplate 來(lái)舉個(gè)例子,其他的數(shù)據(jù)庫(kù)框架操作也都是一樣的,我這里主要是演示時(shí)區(qū)問(wèn)題,數(shù)據(jù)操作細(xì)節(jié)問(wèn)題就不再展示了。
首先我們來(lái)準(zhǔn)備一個(gè)表,如下:
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`createTime` datetime DEFAULT NULL,
`updateTime` timestamp NULL DEFAULT NULL,
`username` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
很簡(jiǎn)單的幾個(gè)字段,createTime 是 datetime 類型,updateTime 是 Timestamp 類型。
然后向表中添加一條記錄:
并且這個(gè)數(shù)據(jù)庫(kù)的時(shí)區(qū)是 Asia/Shanghai
接下來(lái)我們創(chuàng)建一個(gè) Spring Boot 項(xiàng)目,引入 Web、JDBC API 依賴和 MySQL 驅(qū)動(dòng),如下:
然后我們來(lái)配置一下 MySQL 的連接信息,如下:
spring.datasource.username=root
spring.datasource.password=123
spring.datasource.url=jdbc:mysql:///test01?serverTimezone=UTC
小伙伴們看一下,在數(shù)據(jù)庫(kù)連接地址中,我特意設(shè)置了時(shí)區(qū)為 UTC,這個(gè)時(shí)區(qū)比我們目前的時(shí)區(qū)慢了 8 小時(shí),我們來(lái)看看用這樣一個(gè)錯(cuò)誤的時(shí)區(qū),操作的結(jié)果是什么樣子的。
@Autowired
JdbcTemplate jdbcTemplate;
@Test
void contextLoads() {
List<User> list = jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));
System.out.println("list = " + list);
}
大家看到,這個(gè)查詢結(jié)果查到的時(shí)間是 21 點(diǎn),跟 13 點(diǎn)相比快了 8 小時(shí)。
為啥呢?
因?yàn)槲覀冞B接地址中加了 serverTimezone=UTC 參數(shù),這個(gè)時(shí)候,系統(tǒng)會(huì)把從數(shù)據(jù)庫(kù)查詢到的數(shù)據(jù)當(dāng)成是 UTC 時(shí)區(qū)的,即把 13 點(diǎn)當(dāng)成 UTC 時(shí)區(qū)的,但是我自己當(dāng)前設(shè)備又是 Asia/Shanghai 時(shí)區(qū),UTC 時(shí)區(qū)的 13 點(diǎn)轉(zhuǎn)成 Asia/Shanghai 時(shí)區(qū)之后就是 21 點(diǎn)了。
相同道理,大家也可以自行嘗試設(shè)置 serverTimezone=Asia/Tokyo ,時(shí)區(qū)設(shè)置為東京,東京比我們?cè)缫粋€(gè)小時(shí),東京的 13 點(diǎn)就是我們的 12 點(diǎn),那么最終查詢結(jié)果就是 12 點(diǎn)。
從這個(gè)案例中我們可以看到,jdbc 連接參數(shù)中的時(shí)區(qū)優(yōu)先級(jí)高于 MySQL 服務(wù)器的時(shí)區(qū)參數(shù),所以這個(gè)連接參數(shù)大家也要尤其注意。
3. 題外話
有的小伙伴遇到的時(shí)區(qū)問(wèn)題則是另外一種,返回 JSON 的時(shí)候時(shí)間不對(duì)。
如果在項(xiàng)目中用了 jackson,并且使用 @JsonFormat 注解來(lái)格式化日期,就有可能出現(xiàn)時(shí)區(qū)問(wèn)題,如下:
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "Asia/Shanghai")
大家看到,這段代碼如果沒(méi)有設(shè)置 timezone 屬性,那么默認(rèn)的時(shí)區(qū)就是 UTC,也會(huì)導(dǎo)致最終的時(shí)間差了 8 小時(shí)。
4. 小結(jié)
好啦,這就是松哥總結(jié)的數(shù)據(jù)庫(kù)的幾種情況。