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 或 SHOW COLUMNS 只能告诉你 DDL 层面字段存在,无法告诉你该字段是否被数据管道实际写入。外部同步表常见三种空字段来源:
- 同步脚本只映射本团队关心的列,其余留空
- 历史
ADD COLUMN加了占位字段,backfill 从未执行 - 业务方导入 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 固化为项目级约定:新依赖外部同步字段前必跑一次,把 empty_pct 实测值写进 PR 描述。5 分钟探针省掉联调阶段 2-3 天返工。
一句话外卖
外部同步表的字段可用性只有一个可信来源——
COUNT + SUM(CASE WHEN empty)实测,不是 DDL,不是文档,不是同步方的口头确认。