# 1. 数据类型简介
MySQL中关于日期和时间有DATE、TIME、DATETIME、TIMESTAMP这几种数据类型:
- DATE:储存日期,格式为YYYY-MM-DD,范围1000-01-01到9999-12-31;
- TIME:储存时间,格式为hhh:mm:ss,范围-838:59:59 到838:59:59;
- DATETIME:储存日期和时间,格式为YYYY-MM-DD hh:mm:ss,范围1000-01-01 00:00:00到9999-12-31 23:59:59;
- TIMESTAMP:储存时间戳,格式与DATETIME相同,范围1970-01-01 00:00:01到2038-01-19 03:14:07;
赋值时通过上述格式的字符串赋值即可,还有一些如同NOW()之类的函数也可赋值,但本质可以理解为返回了格式化字符串后赋值的。
# 2. 方便的处理
# 2.1 默认值与自动更新
对TIMESTAMP和DATETIME类型字段,都可以将当前时间戳指定为默认值和/或自动更新值,只需要在创建表格时添加DEFAULT描述和ON UPDATE描述:
// ts和dt将在创建和修改时,被赋值为当前时间
CREATE TABLE t1 (
ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
dt DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
2
3
4
5
官方文档提到CURRENT_TIMESTAMP有很多同义词,他们效果相同:
- NOW()
- CURRENT_TIMESTAMP()
- LOCALTIME/LOCALTIME()
- LOCALTIMESTAMP/LOCALTIMESTAMP()
# 2.2 毫秒及更高精度
可以在声明字段时添加小数点精度,只需要在TIMESTAMP后添加(n)即可,其中0 ≤ n ≤ 6。
CREATE TABLE t1 (
ts TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),--正确
dt DATETIME(4) DEFAULT CURRENT_TIMESTAMP(4) ON UPDATE CURRENT_TIMESTAMP(6) -- 错误
);
2
3
4
# 3. 业务表结构设计实战
# 3.1 DATETIME vs TIMESTAMP vs INT,怎么选?
在做表结构设计时,对日期字段的存储,开发人员通常会有 3 种选择:DATETIME、TIMESTAMP、INT。
INT 类型就是直接存储 ‘1970-01-01 00:00:00’ 到现在的毫秒数,本质和 TIMESTAMP 一样,因此用 INT 不如直接使用 TIMESTAMP。
当然,有些同学会认为 INT 比 TIMESTAMP 性能更好。但是,由于当前每个 CPU 每秒可执行上亿次的计算,所以无须为这种转换的性能担心。更重要的是,在后期运维和数据分析时,使用 INT 存储日期,是会让 DBA 和数据分析人员发疯的,INT的可运维性太差。
也有的同学会热衷用类型 TIMESTEMP 存储日期,因为类型 TIMESTAMP 占用 4 个字节,比 DATETIME 小一半的存储空间。
但若要将时间精确到毫秒,TIMESTAMP 要 7 个字节,和 DATETIME 8 字节差不太多。另一方面,现在距离 TIMESTAMP 的最大值‘2038-01-19 03:14:07’已经很近,这是需要开发同学好好思考的问题。
总的来说,我建议你使用类型 DATETIME。对于时区问题,可以由前端或者服务这里做一次转化,不一定非要在数据库中解决。
# 3.2 不要忽视 TIMESTAMP 的性能问题
前面已经提及,TIMESTAMP 的上限值 2038 年很快就会到来,那时业务又将面临一次类似千年虫的问题。另外,TIMESTAMP 还存在潜在的性能问题。
虽然从毫秒数转换到类型 TIMESTAMP 本身需要的 CPU 指令并不多,这并不会带来直接的性能问题。但是如果使用默认的操作系统时区,则每次通过时区计算时间时,要调用操作系统底层系统函数 __tz_convert(),而这个函数需要额外的加锁操作,以确保这时操作系统时区没有修改。所以,当大规模并发访问时,由于热点资源竞争,会产生两个问题。
性能不如 DATETIME:DATETIME 不存在时区转化问题。
性能抖动:海量并发时,存在性能抖动问题。
为了优化 TIMESTAMP 的使用,强烈建议你使用显式的时区,而不是操作系统时区。比如在配置文件中显示地设置时区,而不要使用系统时区:
[mysqld]
time_zone = "+08:00"
2
最后,通过命令 mysqlslap 来测试 TIMESTAMP、DATETIME 的性能,命令如下:
比较time_zone为System和Asia/Shanghai的性能对比
mysqlslap -uroot --number-of-queries=1000000 --concurrency=100 --query='SELECT NOW()'
2
最后的性能对比如下:
从表中可以发现,显式指定时区的性能要远远好于直接使用操作系统时区。所以,日期字段推荐使用 DATETIME,没有时区转化。即便使用 TIMESTAMP,也需要在数据库中显式地配置时区,而不是用系统时区。
时区 | 耗时[秒] | 性能提升 |
---|---|---|
System | 6.674 | / |
Asia/Shanghai | 4.584 | 45.59% |
# 4. 总结
日期类型通常就是使用 DATETIME 和 TIMESTAMP 两种类型,然而由于类型 TIMESTAMP 存在性能问题,建议你还是尽可能使用类型 DATETIME。我总结一下今天的重点内容:
MySQL 5.6 版本开始 DATETIME 和 TIMESTAMP 精度支持到毫秒;
DATETIME 占用 8 个字节,TIMESTAMP 占用 4 个字节,DATETIME(6) 依然占用 8 个字节,TIMESTAMP(6) 占用 7 个字节;
TIMESTAMP 日期存储的上限为 2038-01-19 03:14:07,业务用 TIMESTAMP 存在风险;
使用 TIMESTAMP 必须显式地设置时区,不要使用默认系统时区,否则存在性能问题,推荐在配置文件中设置参数 time_zone = ‘+08:00’;
推荐日期类型使用 DATETIME,而不是 TIMESTAMP 和 INT 类型;
表结构设计时,每个核心业务表,推荐设计一个 last_modify_date 的字段,用以记录每条记录的最后修改时间。
CREATE TABLE User (
id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
sex CHAR(1) NOT NULL,
password VARCHAR(1024) NOT NULL,
money INT NOT NULL DEFAULT 0,
register_date DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
last_modify_date DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
CHECK (sex = 'M' OR sex = 'F'),
PRIMARY KEY(id)
);
2
3
4
5
6
7
8
9
10
11
# 参考资料
- https://dev.mysql.com/doc/refman/8.0/en/fractional-seconds.html (opens new window)
- https://dev.mysql.com/doc/refman/8.0/en/datetime.html (opens new window)
- https://blog.csdn.net/li02112017/article/details/123574038 (opens new window)
- https://blog.csdn.net/weixin_44183847/article/details/124094499 (opens new window)