返回博客

DDL 有字段不等于有数据:外部同步表的 100% 空字段踩坑

接了一个数据分析需求:对某 POS 系统同步过来的购买记录按商品品类做 AI 聚合分析。架构师在方案评审会上说"直接用 product_type 字段分类就行,表里有"。我扫了一眼表结构,VARCHAR(500),字段存在,索引也在,看起来没问题。然后就进了坑。

现象

按方案写好分类逻辑,本地测试没报错,数据也返回了。但无论怎么查询,所有记录都落入 'others' 分支——分类器完全失效,好像整张表的品类信息都不存在。

排查时跑了一句:

SELECT product_type, COUNT(*) AS cnt
FROM purchase_records
GROUP BY product_type
ORDER BY cnt DESC
LIMIT 5;

结果只有两行:

NULL   2514950
''     1824884

全表 433 万行,product_type 100% 为 NULL 或空字符串,没有任何有效值。

根因

外部系统的同步脚本是另一个团队维护的。他们只映射了他们关心的字段product_type 这个字段是历史迁移时 ALTER TABLE ADD COLUMN 加进来占位用的,backfill 脚本从来没跑过,"等 phase 2 再启用"——然后就再也没人提了。

坑:DESCRIBE 有字段 ≠ pipeline 已填

DESCRIBESHOW COLUMNS 只能告诉你 DDL 层面字段存在,无法告诉你该字段是否被数据管道实际写入。外部同步表常见三种空字段来源:

  1. 同步脚本只映射本团队关心的列,其余留空
  2. 历史 ADD COLUMN 加了占位字段,backfill 从未执行
  3. 业务方导入 Excel 时只填了部分列

LIMIT 10 也不够,头部行可能是历史补录的特殊数据,不代表整体。信任"架构师说有"、"文档写了"更不靠谱——文档描述的往往是目标状态,不是当前状态。

正解

在任何外部同步字段进入业务逻辑之前,先跑完整度探针

SELECT
  COUNT(*)                                                         AS total_rows,
  SUM(CASE WHEN product_type IS NULL THEN 1 ELSE 0 END)           AS null_cnt,
  SUM(CASE WHEN product_type = ''   THEN 1 ELSE 0 END)            AS empty_cnt,
  COUNT(DISTINCT product_type)                                     AS distinct_cnt,
  ROUND(
    100.0 * SUM(CASE WHEN product_type IS NULL OR product_type = '' THEN 1 ELSE 0 END)
    / COUNT(*), 2
  )                                                                AS empty_pct
FROM purchase_records;

判断标准:

  • empty_pct > 5% → 字段不可用,必须换方案
  • distinct_cnt < 3 且行数 > 1000 → 字段从未被填过(只有 NULL + 空串)
  • empty_pct ≈ 0 → 可用

当字段不可用时,按硬度递减的备选思路:

| 方案 | 说明 | |------|------| | A. 关键词映射表 | 从 product_name 等可用字段用字符串匹配派生品类 | | B. 一次性回填 | 规则明确 + 数据量适中时,UPDATE 全表补上历史值 | | C. JOIN 主数据 | 本团队自己维护的主数据表作为可靠 source | | D. 推动上游补数 | 长周期,不阻塞当前迭代 |

本次实际选了 A 方案:建关键词映射表,从商品名称字符串派生品类 L1/L2,迭代两轮后 GMV 覆盖率达到 99.86%。

正解:用完整度 SQL 做字段可用性门禁

把上面的完整度 SQL 固化为项目级约定:新依赖外部同步字段前必跑一次,把 empty_pct 实测值写进 PR 描述。5 分钟探针省掉联调阶段 2-3 天返工。

一句话外卖

外部同步表的字段可用性只有一个可信来源——COUNT + SUM(CASE WHEN empty) 实测,不是 DDL,不是文档,不是同步方的口头确认。