MySQL時間戳2038年災難:你的數(shù)據(jù)還能撐過去嗎?
Timestamp 類型在MySQL中通常用于存儲日期和時間。然而,Timestamp類型的一個限制是其存儲范圍,它使用4字節(jié)(32位)整數(shù)來表示秒數(shù),從而導致在2038年01月19日03:14:07之后無法正確存儲時間戳。這是因為32位整數(shù)最大可表示的秒數(shù)是2^31 - 1,即2147483647秒,相當于約68年。因此,如果使用了timestamp類型則需要考慮在達到時間范圍前進行相應處理。
一、案例演示
1、創(chuàng)建測試表
創(chuàng)建一張測試表,存儲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');
再插入一個超過timestamp范圍的數(shù)據(jù)時,結果如下:
insert into tb1 (ts, dt) values ('2039-01-01','2039-01-01');
報錯信息為:
ERROR 1292 (22007): Incorrect datetime value: '2039-01-01' for column 'ts' at row 1
調(diào)整一下:可見datetime類型字段可以正常寫入超過2038年的時間數(shù)據(jù)。
insert into tb1 (ts, dt) values ('2038-01-01','2039-01-01');
可見,timestamp寫入失敗,而datetime可正常寫入。
2、數(shù)據(jù)范圍
因timestamp為4字節(jié),因此最大值為 2147483647 (同int的最大值),換算為時間則為 2038-01-19 03:14:07(UTC時間),即北京時間2038-01-19 11:14:07。而datetime為8個字節(jié),存儲時間可超過9999年,理論上足夠用。
3、時區(qū)展示問題
由于timestamp類型是時區(qū)無關的,因此時區(qū)變化時,所展示的數(shù)據(jù)也是會不一樣,因此在處理涉及時區(qū)的應用時,需謹慎考慮時差的影響。如不希望變化,可以考慮使用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時會得到如下結果:
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版本),則可以正常獲取對應的時間戳值。
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類型,且版本較低,可以通過如下方式進行處理。
改為datetime 類型:datetime 類型的范圍更廣,它能夠表示的時間范圍是從 '1000-01-01 00:00:00' 到 '9999-12-31 23:59:59'。然而,datetime 類型在存儲上可能會占用更多的空間。
使用 bigint 存儲時間戳:如果你需要更大的時間范圍,并且需要毫秒級別的精度,可以考慮使用 bigint 類型存儲時間戳。將時間戳以毫秒或微秒的形式存儲在 bigint 字段中,可以更靈活地處理大范圍的時間。在這種情況下,你需要在應用中負責將時間戳轉換為適當?shù)母袷胶蜁r區(qū)。
數(shù)據(jù)庫升級:如果你的 MySQL版本較低,可以考慮進行數(shù)據(jù)庫升級來解決,且MySQL5.7已經(jīng)EOL,建議盡快升級至新版本。