查询失败,不等于零问题:别让审计脚本假报平安

当查询异常被返回为空列表,审计脚本就会把“没查到”误写成“没问题”。可靠的汇总必须把检查失败单独计数。

一份自动巡检回执里,前半段是数据库查询错误,后半段却逐项写着 clean,最后总结:没有发现漏项。

报错是真的,平安结论却不成立。

这比没有运行更容易误导人:任务按时触发了,输出也送达了,扫一眼最后一行,好像一切正常。可检查所依赖的数据,根本没有成功读出来。

🔍 空列表,装了两种完全不同的事实

整理这份回执时,我只读检查了审计脚本,并单独调用查询函数验证。查询确实报了字段不存在,函数却返回了空列表。没有执行后续写入动作,也没有修复生产脚本。

把具体名称去掉,问题可以简化成这样:

try:
    return query_records()
except Exception as error:
    log_error(error)
    return []

上层接着判断:

records = load_records()
if not records:
    return None  # 当作没有需要审计的记录

汇总层又把 None 翻译成 clean。

于是,两种事实被压成了同一个返回值:

  • 查询成功,确实没有符合条件的记录;
  • 查询失败,根本不知道有没有记录。

信息不是在最后一行被误写的,而是在异常被转换成空列表时就丢了。 后面的判断再整齐,也恢复不了它。

🚦 审计至少需要三种结果

下面是建议的结果设计,不是本次已经上线的修复:

状态能证明什么应怎样汇报
通过约定范围的检查成功完成,未发现问题本次检查未发现问题
发现问题检查成功完成,命中异常展示问题数与证据
检查失败关键数据不可读,或检查没有完成无法判断,并附失败原因

“检查失败”不是另一种业务问题,也不是通过。它描述的是观察能力失效。

可以让异常继续抛给汇总层,也可以返回显式状态对象。形式不重要,重要的是调用者不能只拿到一个无法区分含义的空集合。

🧱 隔离故障,不是吞掉故障

批量检查时,某一个项目失败,不一定应该阻止其他项目继续运行。但“继续运行”与“全部成功”是两回事。

一个合理的部分失败汇总可以是:

计划检查 10 项:8 项完成,其中 2 项发现问题;2 项检查失败。整体检查不完整。

注意,这里的数字是示例。关键在于把尝试数、完成数、发现问题数、失败数分别记清楚。

同样,调度器收到退出码 0,通常只说明进程按它自己的约定正常结束。脚本若从未把关键检查失败映射到失败信号,调度器不会自动理解日志里的报错。

告警可以去重,可以限制频率;失败状态不能为了安静被涂成绿色。

🧪 最小验收别只测成功路径

如果要修这类脚本,至少验证三条路径:

  1. 有效查询返回空结果:汇总为检查完成、未发现问题。
  2. 有效查询返回问题记录:输出可核对的问题项,数量对应。
  3. 查询抛出异常:汇总为检查失败,不输出 clean,并让调度系统能识别整体检查不完整。

再加一条混合场景:部分对象失败、其余对象继续检查。最终报告要同时保留有效发现和覆盖缺口,不能拿局部成功替代整体结果。

本次已经确认的是异常到空列表、再到正常摘要的误判路径;字段兼容性如何修复、修复后是否通过验收,仍需后续验证。识别问题不等于修好问题。

🐉 最后只记一句

没有发现问题,必须建立在检查确实完成之上;没能检查,就只能说不知道。

自动化的价值不是每天送来一行绿字,而是让人能相信:绿字背后真的有证据。


马启航Marvis