在 MySQL 中存储日期和时间有几种不同的方法,想要选择正确的方式就需要大家理解要存储的数据类型以及 MySQL 如何处理这些数据类型。
这篇文章就带大家梳理一下MySQL的这些日期与时间的类型。
MySQL的五种时间数据类型
在MySQL中,提供了五种可以存储时间数据的列类型:
- DATE
- DATETIME
- TIMESTAMP
- YEAR
- TIME
每种列类型存储的数据略有不同,具有不同的最小值和最大值,并且需要不同的存储空间大小。
下表显示了每种列类型及其相关属性:
列类型 | 数据类型 | 字节 | 最小值 | 最大值 |
---|---|---|---|---|
DATE | 仅日期 | 3 | 1000-01-01 | 9999-12-31 |
DATETIME | 日期 + 时间 | 8 | 1000-01-01 00:00:00 | 9999-12-31 23:59:59 |
TIMESTAMP | 日期 + 时间 | 4 | 1970-01-01 00:00:00 | 2038-01-19 03:14:17 |
YEAR | 仅年份 | 1 | 1901 | 2155 |
TIME | 仅时间 | 3 | -838:59:59 | 838:59:59 |
下面,我们针对上述的数据类型,来进行相应的分析和讲解。
日期、年份和时间
上面的表中展示了五种类型,在实践的过程中,我们首先需要思考的一个问题就是:在此场景下,我需要存储什么样的数据?
DATE类型
如果只需要存储日期而无需时间信息,那么选择非常简单------使用 DATE 类型。例如,它适合存储生日、纪念日、员工入职日期等日期相关信息。
DATE 类型非常简洁,仅需要 3 字节即可存储从公元 1000 年到 9999 年范围内的大量日期。
YEAR类型
类似地,如果只需要存储年份而无需日期信息,可以使用 YEAR 类型。虽然这种列类型不常见,但当你确实需要它时,它也能有效地达成目标。
YEAR 类型非常紧凑,仅占用 1 字节。不过其范围较小,仅覆盖从 1901 年到 2155 年。如果你需要更大的范围,可以考虑使用带符号或无符号的小整数(SMALLINT
)。
TIME类型
最后是 TIME 类型,用于存储时间(hh:mm:ss
格式)。它的有效范围比常规时间(00:00:00
到 23:59:59
)更广,因为它可以存储时间间隔和墙上时钟时间。其范围为 -838:59:59
到 838:59:59
,大约覆盖正负 35 天。
如果你只需要存储时间或持续时长而无需日期,TIME 类型非常适合。如果需要一起存储日期和时间,最好使用 DATETIME 或 TIMESTAMP ,以一种逻辑单元存储它们,而不要分成两列。
TIMESTAMP 与 DATETIME 的合法范围
在MySQL中,DATETIME 和 TIMESTAMP 类型都适合存储特定时间的引用。例如,如果需要存储 2029 年 2 月 14 日上午 8:47,可以使用任意一种列类型。
选择 DATETIME 或 TIMESTAMP 的依据就是具体的业务场景所需,两者本身没有优劣之分。
存储大小
TIMESTAMP 类型的存储大小为 4 字节 ,而 DATETIME 列类型为 8 字节。虽然存储空间并不昂贵,但设计表结构时,我们通常希望选择能够容纳所有数据范围的最小列类型。设计时无需"过度慷慨"。
由于 TIMESTAMP 的存储大小较小,其合法范围也相应较窄。
MySQL 中的 2038 问题
TIMESTAMP 的时间范围从 1970-01-01 00:00:00
到 2038-01-19 03:14:17
。但在使用TIMESTAMP时,不可避免的会让我们想起著名的"2038 年问题",它也被标记为Y2038、Y2K38 或"Epochalypse"(时间纪元末日)。
2038 年问题源于计算 Unix 时间的系统。Unix 时间是从 1970 年 1 月 1 日 00:00:00 UTC(Unix 纪元)开始的经过的秒数,这些系统使用带符号的 32 位整数来存储时间。
带符号的 32 位整数只能存储范围为 -2^31
到 2^31 - 1
的值。能够准确表示的最大时间戳对应于 Unix 纪元后 2^31 - 1
秒,即 2038 年 1 月 19 日 03:14:07 UTC。
因此,如果需要存储 2038 年以后的日期,那就应当避免使用 TIMESTAMP,而应该选择 DATETIME。
对于当前应用,仍有许多场景可以使用 TIMESTAMP。例如,当记录动作发生的时间时,例如以下列:
- updated_at
- created_at
- deleted_at
- archived_at
- posted_at
这些列通常由应用程序填充,只需记录事件发生的当前时间。在未来十多年内,TIMESTAMP 完全能够满足此类需求。即便到了 2038 年,相信也会有解决方案的。
何时使用 DATETIME
鉴于 TIMESTAMP 的合法值范围较窄,当需要存储 1970 至 2038 年范围之外的日期时间时,通常使用 DATETIME。
如果该字段是用户输入内容,可能需要选择 DATETIME 或验证输入内容是否在 TIMESTAMP 的范围内。
DATETIME 与 TIMESTAMP 的时区处理
除了存储大小和合法范围不同之外,DATETIME 与 TIMESTAMP 的另一个区别在于它们对时区的处理方式。时区对几乎所有开发者来说都是难题,而在 MySQL 中也不例外。
DATETIME 的时区处理
DATETIME 不会对时区进行任何处理。如果你向数据库写入一个值 2029-02-14 08:47
,那么无论服务器或连接的时区如何设置,你始终会得到 2029-02-14 08:47
。
TIMESTAMP 的时区处理
TIMESTAMP 则会尝试"帮助你"处理时区。根据 MySQL 文档:
MySQL 会将 TIMESTAMP 类型的数据从当前时区转换为 UTC 存储,并在查询时从 UTC 转为当前时区。(这不会发生在其他类型,例如 DATETIME。)默认情况下,每个连接的当前时区是服务器的时区。
我们通过小示例验证这一点。首先创建包含 DATETIME 和 TIMESTAMP 列的表:
sql
CREATE TABLE timezone_test (
`timestamp` TIMESTAMP,
`datetime` DATETIME
);
然后将连接时区设置为 UTC 并插入数据:
sql
SET SESSION time_zone = '+00:00';
INSERT INTO timezone_test VALUES ('2029-02-14 08:47', '2029-02-14 08:47');
SELECT * FROM timezone_test;
-- | timestamp | datetime |
-- |---------------------|---------------------|
-- | 2029-02-14 08:47:00 | 2029-02-14 08:47:00 |
目前看起来不错,插入和读取的值完全一致。接下来我们将时区改为 -05:00
并再次查询表:
sql
SET SESSION time_zone = '-05:00';
SELECT * FROM timezone_test;
-- | timestamp | datetime |
-- |---------------------|---------------------|
-- | 2029-02-14 03:47:00 | 2029-02-14 08:47:00 |
你会发现 TIMESTAMP 列被调整了 5 小时,而 DATETIME 列保持不变。
在实践中,如果服务器和连接时区总是设置为 UTC,这通常不会有问题。但如果不是这样,你可能会惊讶地发现存入数据库的值与取出的值不完全相同。
小结
本篇文章多个维度的对比,科普了一下MySQL中日期、时间和时间戳等数据类型在具体使用时的区别,在每个类型的具体实践中,还有许多最佳实践,在使用时可以尝试更进一步深入的了解。