大家好,我是小耶,写功课只是为了我踩过的坑,你们别再踩了!
一个迁移项目,测试环境全绿,评估报告上兼容度很高。上线两周,业务跑批的数据开始对不上。
排查了一圈,不是索引问题,不是锁问题,也不是性能问题。根因是一句SQL——UPDATE t SET v=1 WHERE 1=0。
这句SQL在源库上会报错,因为约束检查发生在过滤之前。在目标库上,零行被更新,约束检查根本没触发,语句安静地返回成功。迁移评估工具把它判为“兼容”,因为它语法上能翻译。
语法能翻译,行为不一定一致。 这就是迁移里最容易被忽略的那部分。
先把这件事里绕不开的几个概念说清楚:
末梢语义——SQL语句在“正常路径”之外的行为细节:约束检查放在执行计划的哪个位置、字符串比较是按字节还是按字符、空值参与唯一性判断时算不算“相等”。这些细节在平时几乎感知不到,但在边界场景下会改变结果。
差分测试——同一批输入分别在源库和目标库执行,比对输出差异。它不是验证“SQL能不能跑”,而是验证“跑出来的结果是不是同一个”。
对象计数——迁移评估工具计算兼容度的方法:统计迁移了多少个对象,能翻译的除以总数,得出一个百分比。它不区分核心交易表和三年没跑过的存储过程,也不区分“语法通过”和“行为一致”。
一、那个百分比到底在数什么
绝大多数迁移评估工具给出的兼容度,是按对象计数算的:表、视图、存储过程、函数、触发器、序列,一个一个数。能自动转换的算兼容,需要手工改的算不兼容。
这个算法有三个盲区。
盲区一:不按业务权重加权。 一张核心交易表和一张历史归档表,在分母里一样重。一个每天跑的存储过程和一个三年没调用过的函数,在分母里也一样重。95%的兼容度,可能恰好把最核心的那5%漏掉了。
盲区二:能翻译不等于行为一致。 工具的判定标准是“语法能不能转换”,不是“转换之后执行结果会不会变”。上面那句WHERE 1=0的UPDATE,语法完全兼容,行为完全不同。
盲区三:测不到组合效应。 单个对象都兼容,组合起来可能出问题。一个存储过程调用另一个函数,单独测都正常,串联执行时求值顺序变了,结果就不一样。
二、六类末梢语义差异
第一类:求值时机。
约束检查、默认值计算、触发器执行,发生在执行计划的哪个位置。WHERE 1=0的例子就是典型——约束检查在过滤之前还是之后,决定了报错还是静默成功。
第二类:类型系统。
VARCHAR(10)在源库可能按字节计算,在目标库按字符计算。同样一个字段,能存的汉字数量差三倍。NUMBER的精度、DATE和TIMESTAMP的边界行为,都可能在迁移后改变。
第三类:三值逻辑。
SQL的NULL不等于NULL。但在唯一约束、IN子查询、GROUP BY分组里,不同数据库对NULL的处理可能有细微差异。唯一约束拦不住空值重复,是很多迁移后数据异常的来源。
第四类:隐式转换规则。
字符串和数字比较时,往哪个方向转。源库把字符串转数字,目标库把数字转字符串,索引的使用和结果都可能不同。
第五类:排序规则。
ORDER BY的默认排序,大小写是否敏感、重音如何处理、中文按什么顺序排。迁移后分页结果顺序变了,业务侧可能直接出错。
第六类:函数行为边界。
字符串截断、日期加减、取整规则。SUBSTR越界时返回空串还是报错,ROUND遇到.5时向上还是向下。这些在平时几乎不会触发,在边界数据上会暴露。
三、差分测试怎么落地
第一步:真实流量回放。
把生产环境的SQL日志脱敏后,在源库和目标库分别执行,逐条比对结果。这一步能覆盖绝大多数日常路径的差异。
第二步:按六类来源构造边界用例。
正常流量测不出末梢语义的差异,因为末梢语义只在边界上暴露。需要针对上面六类差异,专门构造用例:
-
求值时机:零行更新、约束冲突、触发器嵌套
-
类型系统:超长字符串、精度边界、日期边界值
-
三值逻辑:NULL参与唯一约束、NULL参与
IN和GROUP BY -
隐式转换:字符串与数字混比、字符集不同
-
排序规则:大小写混合、中文、特殊字符
-
函数边界:越界参数、空值参数、极值参数
第三步:发现一条差异,固化一条回归用例。
差分测试的价值不在“跑一次”,在“沉淀下来”。每发现一条差异,把它变成一条回归用例,纳入后续每次变更的验证流程。这样差异库会越来越全,后续的迁移和升级都有了可复用的基线。
四、小结
迁移评估报告上的兼容度百分比,按对象计数,不按业务权重,也不区分“语法通过”和“行为一致”。六类末梢语义差异——求值时机、类型系统、三值逻辑、隐式转换、排序规则、函数边界——能通过语法翻译,却可能改变执行结果。差分测试是发现这些差异的方法:真实流量回放覆盖日常路径,按六类来源构造边界用例覆盖末梢,发现一条差异固化一条回归用例。
百分比告诉你“翻译了多少”,差分测试告诉你“行为对不对”。迁移能不能上线,后者说了算。
小耶在手,SQL 不愁
还有什么想了解的,欢迎留言!小耶一定知无不言言无不尽……我们下次见~