njucm njucm.org
← Notes

三个由真实数据暴露的 SQL 缺陷

做 NMPARegHub 时跑真实数据(中国政府网政策文件库,62 份文件)暴露出三个 bug。 它们都不是想出来的,是被真实数据跑出来的,且都有回归测试覆盖。

一、NULL AND x 永不为真

一个看起来完全合理的排除查询:

SELECT * FROM docs
WHERE title LIKE ?      -- '药品%'
  AND doc_number NOT LIKE ?   -- '%化妆品%'

问题出在无发文字号的文件上:它们的 doc_number 是 NULL。 NULL NOT LIKE '%化妆品%' 求值为 NULL,而 TRUE AND NULL 是 NULL, WHERE 只保留 TRUE,于是这些行被静默丢弃。

结果:库里 62 份文件,其中一大半没有发文字号,查询返回的比例严重偏低, 但语句没有报错,结果看起来”正常”。

修复是把它写成显式处理:

AND COALESCE(doc_number, '') NOT LIKE ?

这类 bug 在生产环境可以潜伏很久——它不产生错误,只产生一个偏小的数字, 而偏小的数字往往看起来像是”数据本来就少”。

二、FTS5 的 NOT 无法独立成立

全文检索走 FTS5 索引时,纯排除查询(只有 NOT 条件、没有正向词)恒返回空集。

原因是 FTS5 的查询语法里 NOT 是二元运算符,必须有一个左侧项;单独的 NOT x 不是合法查询。于是 药品注册 -化妆品 这种写法在 FTS 层直接失效。

对策:排除条件改用 SQL 谓词实现,不要交给 FTS。即:

WHERE docs_fts MATCH ?              -- 只放正向词
  AND title NOT LIKE '%化妆品%'     -- 排除交给 SQL

分工明确:FTS 负责”包含”,SQL 负责”不包含”。

三、子串误匹配

分类规则里有一条把含”注册”的文件归入”药品注册”。结果**《化妆品注册备案管理 办法》被归成了药品注册** —— 因为它包含”注册”两个字。

修法是引入标题级排除规则:先看标题是否已经命中更具体的类别,命中了就短路, 不再往下走模糊规则。

这类问题的通用形态是:分层分类里,优先级顺序本身就是一条需要测试的业务规则。 不测顺序,规则集再大也会互相污染。

为什么这三个值得单独记

三个 bug 有同一个特征:都不产生错误信号。

  • 第一个返回一个偏小的数字
  • 第二个返回空集(这个至少”看起来像没数据”)
  • 第三个返回一个看起来合理的错误分类

能暴露它们的只有一件事:拿真实、脏、不整齐的数据跑。 用自己造的、字段齐全的示例数据,永远跑不出这三个问题。


延伸:NMPARegHub —— 测试覆盖发文字号 解析(含反例)、归类排除规则、效力推断、FTS 索引同步、纯排除查询与两字词回退。

相关:openFDA 接口实战:两个会静默出错的坑 · 为什么用规则分类而不是模型