SQL存储过程中仅宜做轻量确定性清洗,如TRIM、大小写转换等;禁用正则提取、模糊去重、HTTP调用及深度嵌套逻辑;超10万行须分批处理并校验脏数据。

SQL存储过程里做数据清洗,真不如用ETL工具或应用层处理
绝大多数场景下,硬在SQL Server或MySQL的存储过程中写复杂清洗逻辑,会拖慢执行、难调试、更难维护。数据库该干的是存和查,不是当Python用。
但如果你确实被卡在必须用存储过程的环境里(比如老系统只开放了存储过程调用权限,或上游无法改),那得先认清边界:只做轻量、确定性高、无外部依赖的操作。
- 适合做:
TRIM()空格、UPPER()/LOWER()大小写统一、REPLACE()简单字符替换、CONVERT()类型强制转换(如字符串转日期)、ISNULL()或COALESCE()补默认值 - 不适合做:
正则提取(MySQL 8.0前没原生支持)、地址分词、模糊去重、调用HTTP接口验手机号、任何需要循环+条件分支嵌套超过3层的逻辑 - 性能红线:单次清洗超过10万行,且含
LIKE '%xxx%'或CURSOR遍历,基本等于给数据库埋雷
MySQL 8.0+ 存储过程中批量格式化字符串字段
MySQL 8.0起支持REGEXP_REPLACE(),这是能真正替代部分应用层清洗的关键函数。但注意它不支持捕获组回溯(比如$1引用),只能做固定模式替换。
常见错误是直接对大表UPDATE全量字段——锁表时间长,还可能触发max_allowed_packet超限。
- 务必加
WHERE条件限定范围,例如只处理status = 'raw'的记录 - 把清洗拆成小批次,用
LIMIT+OFFSET或主键范围(如id BETWEEN 1000 AND 2000)分批提交 - 别用
DECLARE CONTINUE HANDLER吞掉所有异常,至少保留SQLSTATE 'HY000'类报错,否则脏数据静默入库 - 示例:清洗电话字段,去掉括号、空格、横线,只留数字
UPDATE users SET phone = REGEXP_REPLACE(phone, '[^0-9]', '') WHERE status = 'raw' AND phone REGEXP '[^0-9]';
SQL Server 存储过程中避免游标(CURSOR)做逐行清洗
用CURSOR处理清洗,等于主动放弃SQL的集合操作优势。哪怕只是把varchar字段按逗号拆开再拼回去,也该优先考虑STRING_SPLIT()(SQL Server 2016+)配合FOR XML或STRING_AGG()(2017+)。
真实踩坑点:有人用CURSOR读取10万行,每行调一次REPLACE()和CONVERT(),结果执行47分钟,而等价的集合语句只要3秒。
- 禁用
FAST_FORWARD以外的游标类型,尤其别用SCROLL或KEYSET - 如果非用游标不可,确保
FETCH后立刻UPDATE单行,别攒一堆再批量更新——SQL Server不会自动优化这种“伪批量” - 日期格式化优先用
FORMAT(@date, 'yyyy-MM-dd')而非字符串拼接,后者在DATEFIRST设置不同时会出错
清洗后校验必须独立成步,不能合并在存储过程里
很多人把清洗和校验写进同一个存储过程,以为“一步到位”。结果清洗完发现有500条变成NULL,却因没显式返回或日志,上线后业务方才发现订单地址全丢了。
校验不是锦上添花,是止损底线。尤其涉及CAST或TRY_CONVERT()时,失败不报错,只返NULL。
- 清洗完成后,立刻查
SELECT COUNT(*) FROM table WHERE cleaned_col IS NULL,并把结果RAISERROR抛出 - 不要依赖
@@ROWCOUNT判断清洗是否成功——它只反映上一条语句影响行数,和数据质量无关 - 留一个
cleaning_log表,每次运行记录proc_name、start_time、affected_rows、null_count,比任何注释都管用
最常被跳过的其实是脏数据隔离:清洗前用SELECT ... INTO #temp_raw备份原始片段,出问题能秒级回退。这步省了,后面所有优化都是徒劳。










