硬要在SQL存储过程中清洗生产脏数据,必须接受其非最优解的本质,仅限轻量确定性操作如TRIM、UPPER、REPLACE等;禁用正则捕获、HTTP校验、模糊匹配及游标逐行处理;超5万行须分批+带WHERE条件限定范围;清洗前后必须校验并记录变更与异常。

硬要在SQL存储过程中清洗生产脏数据,得先接受一个前提:这不是最优解,而是被权限、流程或历史包袱卡住后的折中方案。真要长期维护,迟早得推到ETL或应用层重做。
只做轻量、确定性高的清洗操作
存储过程不是Python,别指望它做地址分词、正则捕获、HTTP校验或模糊匹配。这些操作要么不支持(如MySQL 8.0前无REGEXP_REPLACE),要么性能爆炸(游标遍历10万行+REPLACE嵌套调用)。
-
TRIM()、UPPER()、REPLACE(phone, '-', '')这类单函数、无状态、可向量化执行的操作可以放心用 -
CONVERT(DATE, date_str)或CAST()仅在源格式高度统一时才安全;否则先加WHERE date_str REGEXP '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'过滤 - 用
ISNULL()或COALESCE()补默认值没问题,但别用SELECT TOP 1 fallback_value FROM config_table动态查配置——这会把简单UPDATE变成阻塞点
批量清洗必须分片 + 带条件 WHERE
全表UPDATE在生产环境等于主动触发锁表和日志膨胀。哪怕只是清理空格,也得控制影响范围。
- 永远带上限定条件,例如
WHERE status = 'raw' AND phone IS NOT NULL,避免对已清洗过的记录重复操作 - 超5万行建议分批:MySQL用
LIMIT 5000配合循环;SQL Server用主键范围,如WHERE id BETWEEN @start AND @end - 每次提交后加
WAITFOR DELAY '00:00:00.1'(SQL Server)或SLEEP(0.1)(MySQL),缓解日志压力和锁竞争 - 别依赖
DECLARE CONTINUE HANDLER吞掉所有错误——至少让SQLSTATE 'HY000'冒出来,否则脏数据静默落库
SQL Server里坚决不用CURSOR做清洗
有人写个游标逐行UPDATE电话字段,跑47分钟;等价的集合语句3秒跑完。这不是技巧问题,是违背SQL本质。
- 拆分字符串优先用
STRING_SPLIT()(2016+)配STRING_AGG()(2017+),而不是游标+WHILE循环 - 真要用游标,只许
FAST_FORWARD类型,FETCH后立刻UPDATE单行,绝不攒批量再更新 - 避免在游标体内调用函数(尤其是标量函数),它们会在每行执行时重复编译,拖慢10倍以上
清洗前后必须校验,且校验逻辑不能省
清洗不是“执行完就完事”,而是“改了什么、漏了什么、崩了什么”都要可追溯。
- 清洗前先建临时表存原始快照:
SELECT id, phone INTO #raw_backup FROM users WHERE status = 'raw' - 清洗后跑校验:
SELECT COUNT(*) FROM users WHERE status = 'raw' AND phone REGEXP '[^0-9]',结果必须为0 - 记录清洗统计:
SELECT COUNT(*) AS total, COUNT(CASE WHEN phone != original_phone THEN 1 END) AS changed FROM #raw_backup JOIN users USING(id) - 把校验失败的ID导出到日志表,别只打
PRINT——运维查问题时看不到控制台输出
最常被跳过的其实是校验环节:有人改完字段就上线,结果发现某批数据因编码异常被CONVERT截断成1900-01-01,而这条记录既没进错误日志,也没被监控捕获。清洗动作本身越简单,校验越不能偷懒。

















