当前位置: 首页 > 科技观察

MySQL时间戳精度引发的一场屠杀

时间:2023-03-12 02:31:26 科技观察

前线最近在工作中遇到两例mysql时间戳相关的问题,一是mysql-connector-java和msyql的精度不一致,导致找不到数据;另一个是应用服务器的时区错误导致查询不到数据。通过这篇文章,希望能解答mysql中关于时间戳的几个问题:为什么mysql中的DATETIME精度只支持秒?mysql中的DATETIME类型与时区有关吗?mysql设计表时,如何选择代表时间的字段?案例分析DATETIME精度问题在我负责的应用中,有一个功能需要像下面这样使用SQL,即以时间戳为查询条件,查询某个时间戳后的所有数据。select*fromadu_userwheregmt_modified>=#{date}前段时间将负责应用的mysql-connector-java版本从5.1.16升级到5.1.30。在做功能回归的时候,发现使用了类似上述SQL的用例,会存在运行时数据的遗漏,导致功能问题。经过排查,发现mysql-connector-java会丢弃秒后的精度,再传给5.1.23之前的MySQL服务器。正好我们用的mysql版本的DATETIME精度是秒;在我升级mysql-connector-java5.1.30后,java应用通过mysql-connector-java向MySQL服务器传输时间戳时,不会丢弃毫秒数。从mysql-connector-java的角度来看,修复了一个BUG,但是对于我的应用,却触发了一个BUG。如果你遇到这个问题,你会如何解决它?我们当时想到了三种解决方案:将mybatis的Mapper接口中timestamp参数的类型从java.util.Date改为java.sql.Date;取positive,代码如下:查询前,传入时间戳减去1秒;经验证,方案1,从java.util.Date传来的java.sql.Date对象会丢失日期后的所有精度,从而查询到更多不需要的数据;方案3是可以的,即可能多找到一两个数据;方案2也是可以的,相当于从代码上弥补了mysql-connector-java的特性。最后我选择了方案2。案例重现使用homebrew安装MySQL,版本为8.0.15,安装后建表存储用户信息。SQL如下:使用spirngboot+mybatis作为开发框架,定义一个用户实体,代码如下:定义实体对应的Mapper,代码如下:设置连接mysql相关配置,代码如下:编写测试代码,先插入一条数据,然后以时间戳为查询条件进行查询,代码如下:运行单机测试,果然和我们想象的一样,如果数据没有查询到,结果如下:然后修改代码,使用上面的代码,对查询时间戳进行秒级校正,代码如下:再次运行单机测试,和我们想象的一样,这次可以查询到数据了.不过,这里有个小插曲。当初设计表的时候,我使用的SQL语句如下。聪明的你一定发现了,这里的datetime已经支持小数点后更小的时间精度,最高支持6位,Bitness支持到微观级别。这个功能是什么时候引入的?查了下MySQL官方文档,发现mysql5.6.4以后才支持这个特性。知识点总结经过前面的实际案例分析和案例复现,读者想必对mysql中的DATETIME类型有了一定的了解。接下来,让我看看我们可以从这个案例中总结出哪些经验。1、mysql-connector-java的版本需要和mysql的版本一起使用。比如5.6.4之前的版本,最好不要使用mysql-connector-java的5.1.23之前的版本,否则可能会遇到我们这次遇到的问题。2、MySQL中用来表示时间的字段类型有:DATE、DATETIME、TIMESTAMP。它们有相似之处,也各有特点。我总结了一个表如下:3、MySQL中的DATETIME类型是以整数形式存储的,格式为“YYYYMMDDHHMMSS”,与时区无关,占用8个字节的空间;4、TIMESTAMP类型可以保存的时间范围要小很多,显示的值取决于时区,MySQL服务器、操作系统、客户端连接都有时区设置。5、一般情况下,建议使用DATETIME作为时间戳字段,不建议使用bigint类型来存储时间。6、在开发中,尽量避免使用时间戳作为查询条件。如果一定要用,需要充分考虑MySQL的准确性和查询参数的准确性。