SQL Server百万级数据比对必须降维:用HASHBYTES('SHA2_256', CONCAT(COALESCE(c1,''), '|', COALESCE(c2,'')))预计算哈希并建索引;JOIN须确保字段类型一致、有索引且NULL安全;FULL OUTER JOIN差异判断需显式处理NULL;存储过程须防重入、加TRY...CATCH并避免直接返回大结果集。

用 HASHBYTES 预计算行级哈希再比对
直接逐字段 JOIN 或 EXCEPT 在百万级以上数据量下极易超时或 OOM,必须降维。SQL Server 中最稳的方案是用 HASHBYTES('SHA2_256', ...) 对关键字段拼接后统一哈希,再比对哈希值。
实操要点:
- 拼接前必须用
COALESCE(col, '')处理 NULL,否则CONCAT整体返回 NULL,所有哈希值都变成0x - 字段顺序、分隔符(如
'|')、大小写(建议全用UPPER())必须两端完全一致,否则哈希不同但数据实际相同 - 别在 WHERE 里实时算哈希——先存到临时表并建索引,否则每次扫描都重算,性能雪崩
- 示例片段:
SELECT id, HASHBYTES('SHA2_256', CONCAT(COALESCE(name,''), '|', COALESCE(value,''), '|', COALESCE(env,''))) AS row_hash FROM table_a
LEFT JOIN + IS NULL 是最易踩坑的写法
很多人直接写 WHERE b.id IS NULL 找“仅 A 有”的记录,但一上生产就慢或结果错。根本原因不是语法错,而是执行路径失控。
常见陷阱:
- JOIN 字段没索引:哪怕只是
id,如果table_b.id没索引,SQL Server 会强制走 Nested Loop,大表变全表扫描 - 类型不一致:比如
table_a.id是BIGINT,table_b.id是INT,隐式转换导致索引失效,执行计划里出现CONVERT_IMPLICIT - NULL 值干扰:若用非主键字段 JOIN(如
name),而该字段允许 NULL,ON a.name = b.name会跳过所有含 NULL 的行,漏比对
FULL OUTER JOIN 分类差异必须显式处理 NULL 相等性
要一次看出“仅 A 有”“仅 B 有”“同 key 值不同”,得靠 FULL OUTER JOIN,但字段级差异判断不能只写 a.val != b.val。
安全写法必须拆开:
- 数值/字符串字段:
(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) - 更简洁等价写法(SQL Server 不支持
IS DISTINCT FROM):ISNULL(a.val, '') != ISNULL(b.val, ''),但注意空字符串和 NULL 在业务语义上是否等价 - 时间字段慎用
!=:毫秒精度、时区、默认值(如GETDATE()vsSYSDATETIME())都会导致误判,建议先转成CHAR(19)标准化再比
存储过程里必须加防重入和异常捕获
巡检类存储过程一旦被 Agent 多次调度或手动误点,可能并发读写同一张临时表,或重复发告警。这不是功能问题,是运行稳定性底线。
必须做:
- 用
IF OBJECT_ID('tempdb..#hash_a') IS NOT NULL DROP TABLE #hash_a开头清残留,避免上次失败留下的脏临时表阻塞本次 - 用
TRY...CATCH包住核心逻辑,CATCH块里写日志到持久表(别只 PRINT),并THROW保证调用方知道失败 - 加锁检查:
IF EXISTS (SELECT 1 FROM sys.dm_exec_sessions WHERE program_name = 'DiffCheckProc' AND session_id != @@SPID),简单粗暴但有效 - 别把差异结果直接 SELECT 回客户端——大结果集拖慢整个连接,改用
INSERT INTO @result_table后聚合统计(如“共发现 12 行差异,其中 8 行仅 A 有”)
COALESCE、JOIN 字段少建了一个索引、或者没意识到 IS NULL 和 = NULL 在执行计划里触发的是完全不同的运算符。这些点不手动验证一遍执行计划,光看语句永远是对的。

















