排查了一则MySQLdatetime写入 诡异进位 的问题,通过查阅官方文档的方式找到了问题原因。顺带巩固下基础知识。

背景:

问题由最近QA同事给我提的一个工单引起。

相关表结构:

1
2
3
4
5
CREATE TABLE `t_test` (
  `c1` datetime DEFAULT NULL,
  `c2` datetime(3) DEFAULT NULL,
  `c3` datetime(6) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

其中我们业务时间字段类型为 c1 datetime 类型。

过程:

  1. 前端传入 某天最后毫秒的13位时间戳
  2. 后端通过 LocalDateUtils.convertMilliToLocalDateTime(timestamp) 传到 dao,进行sql写入

示例时间戳: 1691769599999 2023-08-11T23:59:59.999

问题表现: 写入到mysql后,数据变成了 2023-08-12T00:00:00

分析:

最佳实践应该是找mysql官方文档,根据我们使用的5.7 找到了mysql关于datetime类型的描述: https://dev.mysql.com/doc/refman/5.7/en/fractional-seconds.html

而我们表中字段设置为datetime,未保留毫秒精度,因此传入 2023-08-11T23:59:59.999 时四舍五入进位,变成了 2023-08-12T00:00:00

顺便复习下rdbms中各种数据类型后面可指定长度、精度的情况:

  1. 整数类型:
  • TINYINT:可选长度参数指定显示宽度。
  • SMALLINT:可选长度参数指定显示宽度。
  • MEDIUMINT:可选长度参数指定显示宽度。
  • INT:可选长度参数指定显示宽度。
  • BIGINT:可选长度参数指定显示宽度。
  1. 浮点数类型:
  • FLOAT:可选精度参数指定小数位数。
  • DOUBLE:可选精度参数指定小数位数。
  1. 定点数类型:
  • DECIMAL:必需的精度和标度参数分别指定总位数和小数位数。
  1. 日期和时间类型:
  • DATE:无需额外参数。
  • TIME:可选的小数秒精度参数指定秒的小数位数。
  • DATETIME:可选的小数秒精度参数指定秒的小数位数。
  • TIMESTAMP:可选的小数秒精度参数指定秒的小数位数。
  1. 字符串类型:
  • CHAR:必需的长度参数指定字符串的固定长度。
  • VARCHAR:必需的长度参数指定字符串的最大长度。
  • BINARY:必需的长度参数指定二进制字符串的固定长度。
  • VARBINARY:必需的长度参数指定二进制字符串的最大长度。
  • ENUM:必需的值列表参数指定列允许的值。
  • SET:必需的值列表参数指定列允许的值。
  • TEXT:无需额外参数。
  • BLOB:无需额外参数。
  1. 其他类型:
  • BOOLEAN:无需额外参数。
  • JSON:无需额外参数。

解决:

方案1:

数据库字段设置为 datetime(3),保留毫秒三位精度。

方案2:

前端或后端将时间戳进行特殊处理,比如 -1s 。

推荐使用方案1。

Ref