数据重复排查
场景描述
运营发现报表中某些订单被统计了两次,导致 GMV 虚高。需要定位重复原因并修复。
常见重复原因
| 原因 | 说明 | 排查方法 |
|---|---|---|
| 数据源重复 | 上游日志重复发送 | 检查原始日志 unique key |
| ETL 重跑 | 任务重跑未做幂等 | 检查任务日志和分区数据 |
| JOIN 膨胀 | 一对多 JOIN 产生膨胀 | 检查 JOIN 结果行数 |
| CDC 重复投递 | Kafka 消息重复消费 | 检查 offset 和消息 ID |
| 缓慢变化维 | 维度表有多条历史记录 | 检查维度表是否去重 |
排查步骤
1. 定位重复记录
-- 找出重复的订单
SELECT order_id, COUNT(*) AS cnt
FROM dwd_order_detail
WHERE dt = '2024-01-15'
GROUP BY order_id
HAVING COUNT(*) > 1
ORDER BY cnt DESC
LIMIT 20;
2. 分析重复原因
-- 对比重复记录的完整字段,找出差异
SELECT *
FROM dwd_order_detail
WHERE order_id IN ('ORD_001', 'ORD_002')
ORDER BY order_id, etl_time;
-- 如果两条记录内容完全一致 → ETL 重跑或数据源重发
-- 如果金额不同 → 订单状态变更产生多条记录
3. JOIN 膨胀检查
-- 检查 JOIN 前后行数
SELECT COUNT(*) FROM order_fact; -- 100 万
SELECT COUNT(*) FROM order_fact a
JOIN product_sku b ON a.sku_id = b.sku_id; -- 如果 > 100 万,说明膨胀了
-- 原因:product_sku 表中 sku_id 不唯一(有多个版本/颜色)
-- 修复:JOIN 前先去重
SELECT COUNT(*) FROM order_fact a
JOIN (SELECT DISTINCT sku_id, sku_name FROM product_sku) b
ON a.sku_id = b.sku_id; -- 应该 = 100 万
修复方案
去重策略
-- 方案一:ROW_NUMBER 去重(保留最新一条)
SELECT * FROM (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY order_id ORDER BY update_time DESC) AS rn
FROM dwd_order_detail
WHERE dt = '2024-01-15'
) t WHERE rn = 1;
-- 方案二:GROUP BY 聚合去重
SELECT order_id,
MAX(amount) AS amount,
MAX(update_time) AS update_time
FROM dwd_order_detail
WHERE dt = '2024-01-15'
GROUP BY order_id;
幂等设计
-- ETL 任务幂等写入:INSERT OVERWRITE 而非 INSERT INTO
INSERT OVERWRITE TABLE dwd_order_detail PARTITION (dt='2024-01-15')
SELECT DISTINCT order_id, user_id, amount, created_at
FROM ods_order_log
WHERE dt = '2024-01-15';
常见面试问题
Q1: 如何检测数据仓库中的重复数据?
答案:
- 主键检查:
SELECT pk, COUNT(*) GROUP BY pk HAVING COUNT(*) > 1 - dbt test:
unique约束测试 - 监控规则:每日自动检查核心表主键唯一性
- 行数对比:ETL 前后行数对比,膨胀超过阈值告警
Q2: Kafka + Flink 场景如何保证不重复消费?
答案:
- Exactly-Once 语义:Flink Checkpoint + Kafka 事务
- 业务层去重:用 Redis 或数据库主键约束做幂等
- 最终兜底:下游查询时用
ROW_NUMBER()去重