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


    (给 ImportNew 加星标,提高Java技能)

    最近在公司开发的项目是对账系统,其中就包括每日(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. 添加 6 个 9 进行查询,查询总条数为 4 条,符合期望。

    • 使用 7 位精度的结束时间查询:也就是 2022-05-10 23.59.59. 添加 7 个 9 进行查询,查询总条数为 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. 添加 8 个 9 进行查询,查询总条数为 5 条,不符合期望。

    • 使用 9 位精度的结束时间查询:也就是 2022-05-10 23.59.59. 添加 9 个 9 进行查询,查询总条数为 5 条,不符合期望。

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

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

    时间 >= ‘2022-05-10 00:00:00’ 并且 时间 < ‘2022-05-11 00:00:00’
  • 相关阅读:
    java毕业设计汽车租赁系统Mybatis+系统+数据库+调试部署
    kali安装volatility及插件mimikatz
    虚拟化kvm操作(第四次实验)
    SpringBoot内置工具类,告别瞎写工具类了
    Springboot:静态资源映射方式
    MS5192T/MS5193T——低噪声、低功耗、16/24 位∑-ΔADC
    vue+antd——table组件实现动态列+表头下拉选择功能——技能提升
    【前端面试题】【交互】
    第 46 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(澳门),签到题4题
    Redis6笔记03 SpringBoot整合Redis,事务和锁机制,持久化操作
  • 原文地址:https://blog.csdn.net/lt_xiaodou/article/details/126558573