有一天午饭后,我正要睡午觉,就被测试女孩叫了起来。传票原因是:某服务突然报错(早上可以正常使用,期间没有做任何修改)。无论我怎么改变调用姿势,都返回异常。本着负责任又不负责任的工作态度,我们登录了测试服务器。

问题现象

先说现象:对表进行插入操作时,mysql报错:

com.mysql.jdbc.MysqlDataTruncation:数据截断:日期时间值不正确:第 1 行的列“REQUEST_TIME”为“2038-01-19 13:15:24”

问题原因

光看这个错误报告就让人很困惑。明明时间是对的,但为什么会不正确呢?带着疑惑,我检查了`REQUEST_TIME`字段的定义,如下所示。

表结构截图

原来`REQUEST_TIME`被定义为时间戳类型,而时间戳是有范围限制的。我查了时间戳的相关信息:

这样就可以找出问题所在了。由于数据超出了时间戳允许的范围,会出现各种错误情况。咨询了测试小姐姐,原来是最近服务器资源有限,这台服务器被分配给了另一个项目组作为测试服务器。在测试过程中,日期会不断切换,从而暴露了这个问题。

解决方案

由于时间戳仅支持 '2038-01-19 03:14:07' UTC,如何保存后续时间?事实上,除了时间戳之外,MySql还提供了datetime类型来保存日期和时间。 datetime 支持的时间范围为:'1000-01-01 00:00:00' 到 '9999-12-31 23:59:59'。

如果需要保存2038年1月19日之后的时间,只需将字段的数据类型更改为datetime即可。在Java中,日期和时间的维护大多是通过PreparedStatement.setTimestamp()方法来实现的,即使在MyBatis中也是如此(详细信息参见:org.apache.ibatis.type.DateTypeHandler.setNonNullParameter()方法)。因此,您只需要将`REQUEST_TIME`字段的数据类型从timestamp改为datetime即可!

与测试女孩互动(提问)

问题:时间戳的取值范围是 '1970-01-01 00:00:01' UTC 到 '2038-01-19 03:14:07' UTC。为什么早上可以,但是早上的日期也是2038-01-19? 。

答:UTC是国际标准时间(格林威治标准时间),而我们的时区是东8,所以数值范围需要加上8个小时,以便我们测试一下,更新于2038-01-19 11: 14:07 进入数据库即可正常更新。更新时2038-01-19 11:14:08 进入数据库也会报同样的错误


问:节省的时间相同,精度也相同。为什么时间戳和日期时间的取值范围不同?

答:这是因为默认情况下时间戳和日期时间占用的字节数不同,并且存储数据的方式不同。 timestamp 占用 4 个字节,存储自 '1970-01-01 00:00:00' UTC 以来经过的秒数; datetime占用5个字节,包括1个标志位和17位年月。 ,5 位日期,5 位小时,6 位分钟,6 位秒,正好 5 个字节。由于时间戳仅使用4个字节来保存秒数,这意味着它最多只能保存Integer.MAX_VALUE秒数。转换后只能保存为 '2038-01-19 03:14:07' UTC。

测试Integer.MAX_VALUE秒对应的deadLine


问题:我了解时间戳如何节省时间。 datetime中的日期、小时、分钟、秒都很容易理解。 17 位数字的年份和月份是多少?需要 14 位数字才能保存年份 9999,需要 4 位数字保存 12 月。总数应为 18 位。怎么用17位数字来保存呢?

答:这里的年和月分别用除数/余数来表示,也就是说实际保存的值=年*13个月。即使是 9999 年 12 月,使用这个算法计算出来的值也是 9999*13 12=129999,可以用 17 位来保存(画外音:我不同意,而且 datetime 还保留了一位符号,这是我们必须的)支持BC的节奏)。

日期时间编码格式


问题:既然datetime只比timestamp多占用1个字节,却可以保存数千年,为什么还要使用timestamp呢?

答:我承认在这个地方使用时间戳的时候我很困惑,但是我不能完全否认时间戳的优点。 timestamp 直接保存第二个值。时间戳的结构比日期时间简单得多。遇到操作或者索引的时候效率比较高。而且timestamp可以根据连接的时区显示对应时区的时间。另外,时间戳还提供了一些天然的非标准功能。空能力(如果为空,将使用当前时间戳保存)。


问题:我刚才提到时间戳和日期时间默认为秒,但它们可以支持微秒精度。字节本身不是只存储第二个值吗?毫秒和微秒存在于哪里?

答:我们所说的4字节和5字节都是默认提到的,即精度为0时。当精度发生变化时,占用的字节数也会相应增加。我们需要严格表达,4/5字节是默认大小。

日期/时间类型存储说明


问题:由于时间戳只能支持到2038年,那么如果服务器上安装数据库的时间变成2038年会怎样?

答:嗯,我没试过。来吧,我们一起来尝试一下。使用root用户设置系统时间:

日期-s 2038-01-01

数据库好像没有反应,我们现在执行()

立即选择();

select now()的查询结果

看来日期还没有到,那就把时间改到1月19日11点14分吧。

日期 011911142038.00

奇怪,为什么MySql突然断线了。查看mysql服务的错误日志,发现mysql确实杀掉了自己。

mysql 关闭

看起来mysql是在发现服务器的时间超出了他所能承受的范围后自杀的。不过,我觉得他还有挽救的余地,就试一试吧。结果...

mysql启动错误


或许测试妹子对自己亲手杀死了MySql感到有些愧疚,于是二话没说就回到了自己的工作站。我终于可以喝一杯水让自己平静下来。不过,我似乎已经成功引起了测试妹子对mysql的兴趣。半小时后,测试妹子又找到我,说发现了mysql的bug。

问题:datetime支持的时间不是'1000-01-01 00:00:00'到'9999-12-31 23:59:59'。为什么'0000-00-00 00:00:00'可以正常保存?

我:这个很容易重现,所以我会尝试一下。果然和测试妹子的描述相符,下图就是证据。

mysql官方文档

表结构。工具版本比较旧,datetime看不到长度。实际长度为3

查询结果

测试女孩看到我一脸困惑的表情,鄙视地看了我一眼,然后就离开了。我依然很迷茫,很茫然。谁能给我一些建议吗?

我是电脑程序编制员。更多精彩内容请关注:空心小窝头

,