Java

把报表导出从分页查询中拆出来

报表能正常显示一页筛选结果,导出相同范围却明显更慢。流程已经使用流式工作簿写入器,批次大小和写入器配置自然成为排查方向。

沿数据提供层继续检查,发现每个输出批次都会重新进入列表分页流程:计算总数、执行分页查询、转换这一批数据,再追加到工作簿。文件在逐批写入,数据库执行却不断重新开始。

本次本地修复让导出拥有独立的执行生命周期。下文依据本地测试和受控只读比对说明已完成的部分;修复包尚未部署,生产 HTTP 提速也尚未验收。

先统计语句执行次数

列表页需要一段结果,有时还需要总数来计算页码。全量下载需要读完本次选中的结果,再生成完整文件。

旧实现把每个写入批次绑定到新的分页操作,反复支付计数和分页 …

把批量删除收口到持久化与事务边界

用户在列表中选中多条记录后点击删除,请求返回 HTTP 500。数据库最早错误来自一条拼接明细文本的分组查询,这条查询原本服务于页面展示。删除操作因此继承了一个自身并不需要的读取依赖。

问题还跨越了多个层次:浏览器逐条发起删除请求,控制层没有稳定保留服务结果,服务层先加载扩展展示对象,再定位需要删除的持久化记录。后续步骤一旦失败,页面很难确认整批选择究竟已提交、部分执行,还是完整回滚。

修复为破坏性操作建立了一个清晰边界:服务端一次接收整批目标,读取精确的持久化模型,先校验全部记录,再在同一事务内按依赖顺序删除,最后把真实结果返回页面。

沿最早错误定位,再继续检查调用链

数据库日志记录了聚合投 …

用明确日界线修正“今日”指标

一个运营页面把两项顶部异常数标成“今日”。只读查询按本地当天筛选后,得到的记录数明显更少。排查期间业务持续流转,数量自然会变化,但每轮比较都稳定出现一批属于昨日的记录。

源码中的日期范围解释了差异:下界是昨日零点,上界是今日最后一秒。查询返回的异常记录都真实存在,区间却覆盖了两个自然日。页面标签与实现分别给出了不同的指标定义。

修复为“今日”建立唯一且明确的契约。两项顶部查询都基于同一个请求时刻和指定业务时区生成边界,再用相同条件下的完整明细及业务主键去重数进行验证。

修改查询前先证明日期区间

排查对齐了四类证据:

  • 页面上的指标名称和刷新行为;
  • 服务传入两项顶部查询的参数;
  • 线上数据库语句缓 …

从 Dubbo Connection …

应用日志不断出现 Dubbo RemotingException,最深层异常是 Connection refused。堆栈很长,前面还夹杂业务接口、代理类和重试线程,很容易让人先去检查调用参数或客户端代码。

但 Connection refused 的含义非常具体:目标地址可以路由到,TCP 连接却被立即拒绝。通常是目标端口没有进程监听,或容器刚好停止。它与请求超时、DNS 失败和业务异常不是同一类问题。

沿最深层异常向外验证

排查顺序从网络事实开始:

  1. 消费端最终尝试连接哪个 host 和 port;
  2. 对应端口是否存在监听;
  3. 服务容器是否正在运行、重启或已经退出;
  4. 注册中心是否还保留着失效 …

复制出库单为何消失在默认总览:继承旧时间导致查询窗 …

一个刚创建的出库单,在订单状态页面按单号搜索可以查到,但直接打开默认出库总览却看不到。接口、权限和仓库范围都没有报错,这种现象很像列表 SQL 漏查或缓存没有刷新。

排查首先对比了两条查询路径。按单号查询使用明确的唯一条件,默认总览还会附加最近时间窗口。数据库中的单据状态正常,但创建时间沿用了被复制旧单的日期,没有落在当天。

根因在复制语义

复制功能复用了原单对象和明细。业务字段需要继承,但审计和生命周期字段也被一起带了过来,包括单头与明细的创建时间、修改时间。

于是系统出现了一个矛盾的新单:

  • 单号和数据库记录是新生成的;
  • 状态也是新流程的初始状态;
  • 时间却属于历史单据;
  • 默认总览只展示最近 …

本地 WMS 看似误连生产:拆解 …

一个本地 WMS 实例的 JDBC 配置明确指向测试数据库,但登录用户、权限和部分页面行为却像来自另一套环境。单看配置文件,很容易得出“应用偷偷连错数据库”的结论。

排查后发现,同一个 Web 应用同时依赖多种数据来源;这些来源指向不同环境,造成了看似数据库串线的现象:

  • 业务数据通过 JDBC 访问数据库;
  • 登录、用户和权限通过远程 UPM 服务取得;
  • Dubbo 服务地址由注册中心或缓存决定;
  • Redis 保存会话、权限或服务发现相关缓存;
  • 本机还可能同时运行多个 Tomcat,每个实例加载不同的配置目录。

因此,业务列表来自测试库,并不代表权限也来自测试环境。

先确认真正处理请求的进程 …