MySQL時(shí)間戳2038年災(zāi)難:你的數(shù)據(jù)還能撐過去嗎?
Timestamp 類型在MySQL中通常用于存儲(chǔ)日期和時(shí)間。然而,Timestamp類型的一個(gè)限制是其存儲(chǔ)范圍,它使用4字節(jié)(32位)整數(shù)來表示秒數(shù),從而導(dǎo)致在2038年01月19日03:14:07之后無法正確存儲(chǔ)時(shí)間戳。這是因?yàn)?2位整數(shù)最大可表示的秒數(shù)是2^31 - 1,即2147483647秒,相當(dāng)于約68年。因此,如果使用了timestamp類型則需要考慮在達(dá)到時(shí)間范圍前進(jìn)行相應(yīng)處理。
一、案例演示
1、創(chuàng)建測(cè)試表
創(chuàng)建一張測(cè)試表,存儲(chǔ)timestamp及 datetime兩種類型。
CREATE TABLE tb1 (
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
ts TIMESTAMP,
dt DATETIME
);
插入正常的timestamp及datetime類型數(shù)據(jù):均可以寫入成功。
insert into tb1 (ts, dt) values ('2038-01-01','2038-01-01');
再插入一個(gè)超過timestamp范圍的數(shù)據(jù)時(shí),結(jié)果如下:
insert into tb1 (ts, dt) values ('2039-01-01','2039-01-01');
報(bào)錯(cuò)信息為:
ERROR 1292 (22007): Incorrect datetime value: '2039-01-01' for column 'ts' at row 1
調(diào)整一下:可見datetime類型字段可以正常寫入超過2038年的時(shí)間數(shù)據(jù)。
insert into tb1 (ts, dt) values ('2038-01-01','2039-01-01');
可見,timestamp寫入失敗,而datetime可正常寫入。
2、數(shù)據(jù)范圍
因timestamp為4字節(jié),因此最大值為 2147483647 (同int的最大值),換算為時(shí)間則為 2038-01-19 03:14:07(UTC時(shí)間),即北京時(shí)間2038-01-19 11:14:07。而datetime為8個(gè)字節(jié),存儲(chǔ)時(shí)間可超過9999年,理論上足夠用。
3、時(shí)區(qū)展示問題
由于timestamp類型是時(shí)區(qū)無關(guān)的,因此時(shí)區(qū)變化時(shí),所展示的數(shù)據(jù)也是會(huì)不一樣,因此在處理涉及時(shí)區(qū)的應(yīng)用時(shí),需謹(jǐn)慎考慮時(shí)差的影響。如不希望變化,可以考慮使用datetime等類型。
mysql> SET SESSION time_znotallow='+00:00';
Query OK, 0 rows affected (0.00 sec)
mysql> select * from tb1;
+----+---------------------+---------------------+
| id | ts | dt |
+----+---------------------+---------------------+
| 1 | 2037-12-31 16:00:00 | 2038-01-01 00:00:00 |
| 2 | 2037-12-31 16:00:00 | 2039-01-01 00:00:00 |
+----+---------------------+---------------------+
2 rows in set (0.00 sec)
mysql> SET SESSION time_znotallow='+08:00';
Query OK, 0 rows affected (0.01 sec)
mysql> select * from tb1;
+----+---------------------+---------------------+
| id | ts | dt |
+----+---------------------+---------------------+
| 1 | 2038-01-01 00:00:00 | 2038-01-01 00:00:00 |
| 2 | 2038-01-01 00:00:00 | 2039-01-01 00:00:00 |
+----+---------------------+---------------------+
2 rows in set (0.00 sec)
二、MySQL8.0版本中的改變
MySQL8.0之前,如果使用超過范圍的timestamp時(shí)會(huì)得到如下結(jié)果:
mysql> select version();
+---------------+
| version() |
+---------------+
| 5.7.38-41-log |
+---------------+
1 row in set (0.00 sec)
mysql> SELECT FROM_UNIXTIME(2147483648);
+---------------------------+
| FROM_UNIXTIME(2147483648) |
+---------------------------+
| NULL |
+---------------------------+
1 row in set (0.00 sec)
而在MySQL8.0版本中(本例使用8.0.33版本),則可以正常獲取對(duì)應(yīng)的時(shí)間戳值。
mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.33-25 |
+-----------+
1 row in set (0.00 sec)
mysql> SELECT UNIX_TIMESTAMP('2039-01-01');
+------------------------------+
| UNIX_TIMESTAMP('2039-01-01') |
+------------------------------+
| 2177424000 |
+------------------------------+
1 row in set (0.00 sec)
三、解決方案
如果使用了timestamp類型,且版本較低,可以通過如下方式進(jìn)行處理。
改為datetime 類型:datetime 類型的范圍更廣,它能夠表示的時(shí)間范圍是從 '1000-01-01 00:00:00' 到 '9999-12-31 23:59:59'。然而,datetime 類型在存儲(chǔ)上可能會(huì)占用更多的空間。
使用 bigint 存儲(chǔ)時(shí)間戳:如果你需要更大的時(shí)間范圍,并且需要毫秒級(jí)別的精度,可以考慮使用 bigint 類型存儲(chǔ)時(shí)間戳。將時(shí)間戳以毫秒或微秒的形式存儲(chǔ)在 bigint 字段中,可以更靈活地處理大范圍的時(shí)間。在這種情況下,你需要在應(yīng)用中負(fù)責(zé)將時(shí)間戳轉(zhuǎn)換為適當(dāng)?shù)母袷胶蜁r(shí)區(qū)。
數(shù)據(jù)庫(kù)升級(jí):如果你的 MySQL版本較低,可以考慮進(jìn)行數(shù)據(jù)庫(kù)升級(jí)來解決,且MySQL5.7已經(jīng)EOL,建議盡快升級(jí)至新版本。