存储过程不应承担高容错清洗职责,否则易引发并发、锁表及脏数据问题;须拆解容错为可验证动作,用TRIM+IFNULL/COALESCE防御空值与空白字符,并对必填字段做长度校验。

硬要在存储过程中做高容错清洗和导入,本质是用数据库扛本该由ETL或应用层承担的职责——它能跑通,但容易在并发、锁表、脏数据静默入库上翻车。真要这么做,必须把「容错」拆解为可验证的边界动作,而不是指望一个BEGIN...END块自动兜底。
用IFNULL/COALESCE+TRIM组合防御空值和空白字符
参数或源字段为空字符串、全空格、NULL时,直接参与后续逻辑(比如CONVERT转日期)会报错或转成意外值。不能只靠ISNULL(),得先TRIM()再判空。
-
SET p_name = TRIM(IFNULL(p_name, ''));比SET p_name = IFNULL(p_name, 'default');更安全,避免把" "当有效值传下去 - 对电话、邮箱等必填字段,清洗后加长度校验:
IF LENGTH(p_phone) - MySQL 5.7不支持
REGEXP_REPLACE(),别在COALESCE()里嵌套它,否则报错中断整个过程
分批UPDATE+WHERE限定范围,避开锁表和超时
对大表执行UPDATE ... SET phone = REGEXP_REPLACE(phone, '[^0-9]', '')全量扫,轻则锁表几十秒,重则触发max_allowed_packet或事务超时。必须切片。
- 用主键范围分批:
WHERE id BETWEEN @start_id AND @end_id AND status = 'raw',每次处理5000行以内 - 每次
UPDATE后加SELECT ROW_COUNT();判断是否还有数据,避免OFFSET越界失效 - 别用
CURSOR遍历——SQL Server下10万行游标清洗比等价集合语句慢15倍以上
错误捕获只留关键SQLSTATE,别吞掉HY000类报错
用DECLARE CONTINUE HANDLER FOR SQLEXCEPTION包住全部逻辑,看似容错,实则让类型转换失败、正则语法错、字段超长等都静默变成NULL,脏数据直接入库。
- 只捕获明确需降级的错误,例如:
DECLARE CONTINUE HANDLER FOR SQLSTATE '22007' -- 日期格式错,其他抛出 - 必须保留
SQLSTATE 'HY000'(通用错误)和'45000'(自定义异常),否则无法定位CONVERT()失败源头 - 清洗前先用
SELECT COUNT(*) FROM staging WHERE phone NOT REGEXP '^[0-9]{11}$'抽样校验,比事后修更可靠
触发器仅用于单条INSERT的字段级修正,禁用跨表查和状态累积
想靠BEFORE INSERT触发器自动补全地址、推导部门、查用户积分——这些操作在批量导入时会指数级放大性能损耗,且结果不可预测。
- 触发器内只允许
SET NEW.field = TRIM(UPPER(NEW.field))这类无副作用操作 - 禁止
SELECT @var := MAX(id) FROM t——MySQL触发器中用户变量只在首行生效,后续全丢 - 需要防重复插入?用
INSERT IGNORE或ON DUPLICATE KEY UPDATE,别在触发器里写NOT EXISTS子查询
最常被忽略的一点:存储过程里的“容错”不等于“业务逻辑兜底”。它只能拦住格式错误,拦不住语义错误——比如手机号清洗后是11位数字,但其实是错号;日期转成功了,但业务上根本不该是那个时间。这类问题必须靠导入前校验规则和下游业务校验双保险,而不是在BEGIN...END里堆更多IF分支。

















