SQL存储过程无法直接跨库对比数据,因INFORMATION_SCHEMA和sys.columns仅限本库元数据,跨库需同一SQL Server实例内限定db_src与db_tgt,用EXCEPT或FULL OUTER JOIN行级比对,并严格控制主键对齐、NULL处理及类型转换。

SQL存储过程无法直接做跨库数据差异对比
存储过程天然被绑定在单个数据库实例内,INFORMATION_SCHEMA 和 sys.columns 只能查本库元数据,SELECT * FROM db1.table1 在 SQL Server 里访问另一库需显式前缀,而 MySQL 不支持跨库 JOIN(除非用 FEDERATED 引擎,但已弃用且不稳定)。所谓“通用对比逻辑”若默认支持任意两库,实际会卡死在连接层——连表都查不到,更别说比字段值。
真正可落地的对比逻辑必须限定在同一实例内
同一 SQL Server 实例下两个库(如 db_src 和 db_tgt)才能安全使用 EXCEPT 或 FULL OUTER JOIN 做行级比对。关键不是写多复杂的 SQL,而是控制三处:主键对齐、NULL 处理、类型隐式转换。
- 主键字段名和类型必须完全一致,否则
ON a.id = b.id可能因隐式转换导致索引失效或匹配错误 -
WHERE a.val b.val会漏掉任一端为NULL的行,必须补全:(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) - 避免
SELECT *对比:datetime和datetime2(3)虽然能 JOIN,但EXCEPT会因精度差异判定为不同行;必须显式 CAST 统一类型
同步动作不能封装进对比过程里
把 MERGE 或 INSERT/UPDATE/DELETE 和比对逻辑塞进同一个存储过程,会导致事务边界失控。调用方传入 @table_name,过程内部拼 EXEC('MERGE ' + @table_name + '...'),一旦失败,错误堆栈指向动态 SQL 行号,根本没法定位是哪张表哪条数据出问题。
更稳妥的做法是分两步:
- 第一步:运行纯比对过程,输出差异结果集(例如
SELECT 'INSERT' op, src.* FROM src EXCEPT SELECT 'INSERT' op, tgt.* FROM tgt),不修改任何数据 - 第二步:由外部调度器(如 SQL Server Agent)根据上一步结果,调用预定义的、针对具体表的同步过程,如
usp_sync_orders,每个过程只处理一张表,参数固定、事务可控、日志可追溯
字段映射差异必须由调用方显式声明
没有“自动识别 mobile ↔ contact_phone”的魔法。如果源表字段叫 mobile,目标表叫 contact_phone,过程里就得硬编码映射:src.mobile AS contact_phone。试图用 SELECT @col_map 动态拼列名,会触发 SQL Server 的执行计划缓存污染——每次不同映射都生成新计划,内存撑爆。
真实项目中,这类映射关系应存在控制表里:
CREATE TABLE sync_column_mapping ( table_name VARCHAR(64), src_col VARCHAR(64), tgt_col VARCHAR(64), cast_expr VARCHAR(128) -- 如 'CAST(mobile AS VARCHAR(20))' );
过程内用 JOIN sync_column_mapping 构建 SELECT 列表,但注意:不能直接 EXEC 拼出来的完整 SQL,而是用它生成静态语句模板,再通过参数化方式传入值。
字段类型兼容性、NULL 语义、时区偏移、字符集转换——这些都不是 SQL 存储过程能自动兜底的。所谓“通用”,其实是把变化点(表名、字段映射、时间窗口)暴露为参数,把不变点(比对逻辑结构、事务粒度、错误传播方式)固化为约定。

















