全量比对必须用哈希降维:先COALESCE处理NULL、固定分隔符与大小写,再HASHBYTES计算并索引存储;增量比对须依赖非空单调字段+控制表管理基准值;差异判断需显式处理NULL;存储过程须加应用锁、异常捕获与结果截断。

全量比对必须用哈希降维,不能直接 EXCEPT 或逐字段 JOIN
百万级以上数据做全量比对,EXCEPT 或 FULL OUTER JOIN 逐字段比较会触发全表扫描+内存膨胀,SQL Server 很容易超时或 OOM。真正能落地的方案是预计算行级哈希。
关键动作有三步:拼接字段前用 COALESCE(col, '') 处理 NULL;固定分隔符(如 '|')和大小写(建议统一 UPPER());把哈希结果存进带索引的临时表,而不是在 WHERE 里实时算。
-
HASHBYTES('SHA2_256', CONCAT(COALESCE(a.name,''), '|', COALESCE(a.amount,''), '|', UPPER(ISNULL(a.status,''))))是安全拼法,漏掉任意一个COALESCE都会导致整列哈希为0x - 临时表必须对
row_hash建非聚集索引,否则后续JOIN仍走嵌套循环 - 别用
SELECT * INTO #tmp FROM ...,它不自动建索引,得补一句CREATE INDEX IX_hash ON #tmp(row_hash)
增量比对依赖可信赖变更字段,不能靠时间窗口硬切
用 WHERE update_time > DATEADD(HOUR, -1, GETDATE()) 这类硬窗口,任务延迟就漏数据。必须依赖源表真实更新的、已建索引的单调字段,比如 update_time 或自增 id。
常见错误是忽略 NULL:如果 update_time 允许为空,WHERE update_time > @last_time 会自动过滤掉所有 NULL 行,而这些行可能正是业务未更新但需保留的记录。
- 增量条件必须显式补上
AND update_time IS NOT NULL - 基准值不能从变量读,得查控制表:
SELECT @last_time = last_sync_time FROM sync_control WHERE table_name = 'orders' - 同步完成后立即更新控制表,且要加事务包裹,避免断电后状态丢失
FULL OUTER JOIN 差异分类必须拆解 NULL 判断逻辑
只写 a.val != b.val 在 SQL Server 中无法正确识别 NULL 差异——因为 NULL != 'x' 返回的是 UNKNOWN,不是 TRUE,会被 WHERE 过滤掉。
安全写法只有两种:要么展开三段判空(a.val != b.val OR (a.val IS NULL AND b.val IS NOT NULL) OR (a.val IS NOT NULL AND b.val IS NULL)),要么用 ISNULL(a.val, '') != ISNULL(b.val, ''),但要注意空字符串和 NULL 在业务语义是否等价。
- 时间字段慎用
!=:DATETIME和DATETIME2(3)精度不同,直接比会误判;建议先转成CHAR(19)格式再比 - 主键字段允许 NULL 时,
ON a.id = b.id会跳过所有含 NULL 的行,导致“仅 A 有”类差异完全漏掉 - 如果用
MERGE做增量同步,记得补独立DELETE步骤清理孤儿数据,MERGE本身不处理“源删目标留”场景
存储过程必须防重入、捕获异常、限制输出量
巡检类比对存储过程一旦被 SQL Server Agent 多次调度,或人工误点两次,就会并发写同一张临时表甚至目标表,轻则结果错乱,重则死锁。
核心防护点就三个:用 sp_getapplock 加应用级锁;所有 DML 包在 TRY...CATCH 块里;绝不直接 SELECT * FROM #diff_result 返回大结果集,而是写入日志表或只返回统计行数。
-
EXEC @result = sp_getapplock @Resource = 'table_compare_orders', @LockMode = 'Exclusive', @LockOwner = 'Session',失败就直接RETURN -
CATCH块里必须写日志:INSERT INTO compare_log(table_name, error_msg, run_time) VALUES (@tbl, ERROR_MESSAGE(), GETDATE()) - 差异明细超过 1000 行就截断,只留前 1000 条进日志表,避免拖慢整个比对流程
实际最难的不是写对语法,而是让哈希拼接逻辑、NULL 处理边界、锁粒度这三者在不同环境里始终一致——开发库跑通,不等于生产库不出问题。











