msyql5.7日期进位问题

yuyu888 于 2025-10-31 发布

背景

在项目中有一个表存了一个日期字段: payroll_cycle_end_day date NOT NULL COMMENT ‘计薪周期结束日期’, java里的entity字段为日期 date 格式, 在设置的时候使用了cn.hutool.core.date.DateUtil.endOfMonth(payrollCycleBeginDay) 获取月末时间 后面很自然的程序执行save操作就朝mysql里插入了

结果,本地测试没问题, 测试环境也没异常,线上数据库里本来是十月的数据; 开始时间存在的是2025-10-01没问题, 结束时间就存成了 2025-11-01, 而不是预期的 2025-10-31

经过几轮排查定位,发现是mysql的版本不一致导致的,本地和测试连的同一个库,版本是:mysql5.5; 线上用的是:mysql5.7

SQL测试(Mysql5.7)

字段:

  `attendance_cycle_end_day` date NOT NULL COMMENT '考勤周期结束日期',

测试case:

update payroll_cycle_attendance_snapshot set payroll_cycle_end_day="2025-10-31 23:59:59" where id = 1094799  
### 结果:2025-10-31


update payroll_cycle_attendance_snapshot set payroll_cycle_end_day="2025-10-31 23:59:59.9999" where id = 1094799  
### 结果:2025-11-01

### 进一步测试
update payroll_cycle_attendance_snapshot set payroll_cycle_end_day="2025-10-31 23:59:59.4" where id = 1094799  
### 结果:2025-10-31

update payroll_cycle_attendance_snapshot set payroll_cycle_end_day="2025-10-31 23:59:59.5" where id = 1094799  
### 结果:2025-11-01

测试结论

在mysql5.7版本时,date类型的字段当存入数据带毫秒的时候,小数点后的数会遵守四舍五入原则,可能发生时间进位

解决办法

1、再entity设置的时候,如果mysql字段是date类型可以粗暴点直接取当天的开始时间, 比如:DateUtil.beginOfDay(DateUtil.endOfMonth(payrollCycleBeginDay));

2、entity的字段上加注解 @Temporal(TemporalType.DATE)

使用场景对比


@Entity
public class Employee {
    // 只关心日期,比如生日、入职日期
    @Temporal(TemporalType.DATE)
    private Date hireDate;          // 存储为:2025-10-31
    
    // 只关心时间,比如打卡时间
    @Temporal(TemporalType.TIME)
    private Date punchTime;         // 存储为:14:30:00
    
    // 关心完整的日期时间,比如创建时间、更新时间
    @Temporal(TemporalType.TIMESTAMP)
    private Date createTime;        // 存储为:2025-10-31 14:30:00.123
}

如果想要 年-月-日 时:分:秒 舍弃毫秒, 得java特殊处理下

扩展到DATETIME/TIMESTAMP类型

依然会发生进位问题

执行:

### 字段设置:
  `update_time` datetime NOT NULL COMMENT '更新时间',


### sql语句: 
update payroll_cycle_attendance_snapshot set update_time='2025-10-31 17:30:56.9999' where id = 1094799  

### 结果: 2025-10-31 17:30:57

总结

这种坑很恶心, 不容易察觉,如果是日期多了一天可能导致大问题,如果是秒数进位,不容易发现,而且有的进位有的不进位, 如果没想到这块,复现结果莫名其妙如抽风

所以使用java朝mysq数据库里插入时间的时候,在逻辑许可的情况下,最好能特殊处理下,把毫秒去掉

或者直接就用int存时间戳, 或者用字符串存储(php就习惯这么处理); 这样也能规避一下问题