沉默是金,总会发光
大家好,我是沉默
昨天下班前,运营同学抛来一个灵魂拷问:
“用户黑名单设置 2 天后解封,为什么提示是 第 3 天 才能恢复?”
比如 6 月 16 日被拉黑,应该在 6 月 18 日 23:59:59 自动解封,但系统却显示 6 月 19 日 00:00:00。
一秒之差,却让用户多等了一天,属实离谱。
更诡异的是——数据库里有 一半数据对的(23:59:59) ,另一半却是 第二天的 00:00:00。这下真让人百思不得其解 🤯。
**-**01-
排查过程
排查思路可以总结成三步走:
第一步:排除逻辑错误
确认了代码里只有一处写 DeblockTime,没有被覆盖。
核心逻辑
LocalDateTime currentTime = LocalDateTime.now();LocalDateTime futureTime = currentTime.plus(2, ChronoUnit.DAYS);LocalDateTime resultTime = futureTime.withHour(23).withMinute(59).withSecond(59);entity.setDeblockTime( Date.from(resultTime.atZone(ZoneId.systemDefault()).toInstant()));
看似没毛病,为什么一半会变成 第二天 00:00:00?
第二步:批量插入复现
写了个小 demo,每隔 100ms 插入一次。结果一眼就发现:
- 一半数据是 23:59:59
- 另一半变成 次日 00:00:00
bug 完美复现
第三步:锁定真凶
原来罪魁祸首是 —— Java 的毫秒 vs 数据库的秒精度差异
java.util.Date精度:毫秒(1/1000 秒)- Postgres/MySQL 默认
timestamp精度:秒 - 入库时如果毫秒 ≥ 500,就会 四舍五入 进 1 秒 → 变成第二天 00:00:00
就像“本来应该关门的地铁,偏偏被最后一个挤上来的秒针硬生生推迟到下一站”。
- 02-
解决方案
这类问题的关键是:对齐 Java 和数据库的时间精度
方案 1:清空毫秒(推荐)
futureTime.withHour(23).withMinute(59).withSecond(59).withNano(0);
确保毫秒恒为 000,入库就不会四舍五入。
方案 2:调整数据库精度
把数据库的 timestamp 设置成毫秒或微秒精度(如 timestamp(3) 或 timestamp(6)),和 Java 对齐。
- 03-
知识扩展(必备面试点)
Java 时间类对比
| 特性 | java.util.Date | LocalDateTime |
|---|---|---|
| 精度 | 毫秒 | 纳秒 |
| 可变性 | 可变 | 不可变 |
| 时区感知 | 不存储时区 | 无时区(需结合 ZonedDateTime) |
| 线程安全 | ❌ | ✅ |
| 推荐程度 | ⭐ | ⭐⭐⭐⭐⭐ |
MySQL DATETIME vs TIMESTAMP
| 特性 | DATETIME | TIMESTAMP |
|---|---|---|
| 范围 | 1000-9999 年 | 1970-2038 年 |
| 精度 | 可到微秒 (6) | 可到微秒 (6) |
| 时区 | 无 | 有(存 UTC,取时转会话时区) |
| 存储大小 | 8 字节 | 4 字节 |
| 适用场景 | 历史事件/本地时间 | 跨时区/自动更新时间 |
**-**04-
总结
-
这次 bug 的本质,是 Java 的毫秒精度 + 数据库的秒精度 → 四舍五入进位
-
解决办法有两种:清零毫秒 或 提升 DB 精度
-
扩展知识里,掌握 Java 时间类 & MySQL 时间类型对比,是面试必考点
一句话 takeaway:
在时间处理上,不要轻视“1 秒” ,它可能让你的业务逻辑整个跑偏一天!
问题来了:
如果你在实际业务里,遇到过 “1 秒 bug” ,是怎么排查出来的?
**-**05-
粉丝福利
点点关注,送你互联网大厂面试题库,如果你正在找工作,又或者刚准备换工作。可以仔细阅读一下,或许对你有所帮助!