Mysql 由于 Java 日期 LocalDateTime 数据精度引发的线上问题

Posted carl-zhao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mysql 由于 Java 日期 LocalDateTime 数据精度引发的线上问题相关的知识,希望对你有一定的参考价值。

最近在公司开发的项目是对账系统,其中就包括每日(T+1)对账。每日对账就是对前一日期产生的数据对于核对。举个具体的例子就是:在第三方支付公司,里面会产生支付里面的支付订单与下载渠道(微信、支付或者其它第三方支付)的每日交易账户需要进行对比。关于微信支付的 下载交易账单 地址。支付宝或者其它的下载交易单内容类似,只是文件格式不一样。

我们来看一下对账的数据流向:


今天要说的是在查询我们保存的第三方支付支付订单与微信支付的交易账单进行对比的时候,在查询我们 T 日数据的时候多查询出来了数据导致数据对不平。下面我把数据库的对账表的简化模型简单列举一下:

对账数据表

create table trade_record (
	id BIGINT(20) AUTO_INCREMENT COMMENT '主键',
	source_no varchar(32) not null DEFAULT '' COMMENT '数据来源(微信、交易系统、支付宝等)',
	order_no VARCHAR(64) not null DEFAULT '' COMMENT '订单号',
	compare_no VARCHAR(64) not null DEFAULT '' COMMENT '对比号',
	amount DECIMAL(20,4) not null default '0' COMMENT '金额',
	trade_at DATETIME not null COMMENT '交易成功时间',
	create_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
	update_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
	PRIMARY KEY (`id`),
	index idx_trade_at(trade_at),
	index idx_create_at(create_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '交易记录表';

我们先插入几条数据用于查询测试:

待对账数据

-- 支付系统订单
insert into trade_record(source_no, order_no, compare_no, amount, trade_at) 
values ('payment', '111111', '111111', '100', '2022-05-10 02:23:43');

insert into trade_record(source_no, order_no, compare_no, amount, trade_at) 
values ('payment', '222222', '222222', '200', '2022-05-10 05:23:43');

insert into trade_record(source_no, order_no, compare_no, amount, trade_at) 
values ('payment', '333333', '333333', '300', '2022-05-10 12:23:43');

insert into trade_record(source_no, order_no, compare_no, amount, trade_at) 
values ('payment', '444444', '444444', '400', '2022-05-10 23:23:43');

insert into trade_record(source_no, order_no, compare_no, amount, trade_at) 
values ('payment', '555555', '555555', '500', '2022-05-11 00:00:10');

-- 渠道返回订单
insert into trade_record(source_no, order_no, compare_no, amount, trade_at) 
values ('wechat_pay', '111111', '111111', '100', '2022-05-10 02:23:43');

insert into trade_record(source_no, order_no, compare_no, amount, trade_at) 
values ('wechat_pay', '222222', '222222', '200', '2022-05-10 05:23:43');

insert into trade_record(source_no, order_no, compare_no, amount, trade_at) 
values ('wechat_pay', '333333', '333333', '300', '2022-05-10 12:23:43');

insert into trade_record(source_no, order_no, compare_no, amount, trade_at) 
values ('wechat_pay', '444444', '444444', '400', '2022-05-10 23:23:43');

insert into trade_record(source_no, order_no, compare_no, amount, trade_at) 
values ('wechat_pay', '555555', '555555', '500', '2022-05-11 00:00:00');

插入完成后,总共 10 条数据:


比如我们需要比较 2022-05-10 的数据的时候,那些我们只需要查询出时间范围为:2022-05-10 00:00:00 ~ 2022-05-10 23:59:59 的微信支付以及支付平台的数据然后按照 compare_no 进行逐条对比就行了。

支付系统数据:

渠道返回数据:

查询出来两方数据,我们就可以根据 compare_no 进行逐一比对,可以得到两方的对账是可以对平的。虽然我们是这样设计的,然后我们验证也没有问题。但是在线上出现了对账不匹配的情况。

DateUtils.java

public abstract class DateUtils 

    /**
     * 昨天的开始时间
     * @return
     */
    public static LocalDateTime yesterdayDateBegin() 
        LocalDate currentTime = LocalDate.now();
        LocalDate yesterday = currentTime.plusDays(-1);
        return LocalDateTime.of(yesterday, LocalTime.MIN);
    

    /**
     * 昨天的结束时间
     * @return
     */
    public static LocalDateTime yesterdayDateEnd() 
        LocalDate currentTime = LocalDate.now();
        LocalDate yesterday = currentTime.plusDays(-1);
        return LocalDateTime.of(yesterday, LocalTime.MAX);
    
    

以上是日期工具类,在单元测试的时候没有问题。但是上面的工具类得到的开始结束时间以今日是: 2022-05-10 为例:

  • 昨日开始时间:2022-05-09T00:00
  • 昨日结束时间:2022-05-09T23:59:59.999999999

然后以上面的时间进行查询的得到的结果是:

结果查询出来了一条交易成功时间为:2022-05-11 00:00:00 的数据,与我们的期望不符。我们可以查看 Mysql 官网对于 Datetime 这个日期类型的描述。可以看到 Datetime 这个日期类型的精度为小数点后面 6 位。而我们在查询的时候使用的是 9 位。于是我就使用不同的精度的结束时间进行查询:

  • 使用 6 位精度的结束时间查询:也就是 2022-05-10 23.59.59. 添加 69 进行查询,查询总条数为 4 条,符合期望。
  • 使用 7 位精度的结束时间查询:也就是 2022-05-10 23.59.59. 添加 79 进行查询,查询总条数为 5 条,不符合期望。
  • 使用 7 位精度的结束时间查询:也就是 2022-05-10 23.59.59. 添加 9999991 进行查询,查询总条数为 4条,符合期望。
  • 使用 7 位精度的结束时间查询:也就是 2022-05-10 23.59.59. 添加 9999992 进行查询,查询总条数为 4条,符合期望。
  • 使用 7 位精度的结束时间查询:也就是 2022-05-10 23.59.59. 添加 9999993 进行查询,查询总条数为 4条,符合期望。
  • 使用 7 位精度的结束时间查询:也就是 2022-05-10 23.59.59. 添加 9999994 进行查询,查询总条数为 4条,符合期望。
  • 使用 7 位精度的结束时间查询:也就是 2022-05-10 23.59.59. 添加 9999995 进行查询,查询总条数为 5条,不符合期望。
  • 使用 8 位精度的结束时间查询:也就是 2022-05-10 23.59.59. 添加 89 进行查询,查询总条数为 5 条,不符合期望。
  • 使用 9 位精度的结束时间查询:也就是 2022-05-10 23.59.59. 添加 99 进行查询,查询总条数为 5 条,不符合期望。

所以我们可以得出结论:在使用 mysql datetime 进行数据查询的时候只能使用正确的精度才能得出符合期望的数据。并且当使用 datetime 大于精度的时候是进行四舍五入的。

建议大家在查询一天内的数据的时候使用 左关右开 模式也就是:

时间 >= ‘2022-05-10 00:00:00’ 并且 时间 < ‘2022-05-11 00:00:00’

参考文章:

  • https://dev.mysql.com/doc/refman/5.7/en/fixed-point-types.html

以上是关于Mysql 由于 Java 日期 LocalDateTime 数据精度引发的线上问题的主要内容,如果未能解决你的问题,请参考以下文章

Mysql 由于 Java 日期 LocalDateTime 数据精度引发的线上问题

如何使用 JPA 将 Java 日期存储到 Mysql 日期时间

MYSQL 日期和 Java 日期

在 Java 中处理 MySQL 日期时间和时间戳

在 Java 中转换 MySQL 日期格式

java中日期类型和mysql中日期类型进行整合