插入前原始值只能在BEFORE INSERT触发器中通过NEW.column_name获取并修改;MySQL和PostgreSQL均支持,SQLite不支持直接修改NEW。

触发器里怎么拿到插入前的原始值?
MySQL 和 PostgreSQL 的触发器机制不同,但核心思路一致:用 NEW 引用即将插入或更新的行。在 BEFORE INSERT 触发器中,NEW.column_name 就是待写入的原始值,你可以直接修改它——这是清洗能生效的前提。
- MySQL 中必须用
BEFORE INSERT(不能用AFTER),否则改NEW无效 - PostgreSQL 同样只允许在
BEFORE触发器中赋值给NEW字段 - SQLite 支持
BEFORE INSERT,但不支持直接修改NEW;得用INSERT ... SELECT+REPLACE绕过,实际中建议避免
比如清洗手机号字段:NEW.phone := REPLACE(REPLACE(NEW.phone, '-', ''), ' ', '')(PostgreSQL)或 SET NEW.phone = REPLACE(REPLACE(NEW.phone, '-', ''), ' ', '')(MySQL)
常见清洗操作该用什么函数?
不同数据库内置字符串函数差异大,别硬套语法:
- 去首尾空格:MySQL/PostgreSQL 都用
TRIM(),但 SQLite 只支持TRIM(col)(无参数形式),不支持TRIM(BOTH 'x' FROM col) - 去所有空白符(含换行、制表符):MySQL 用
REGEXP_REPLACE(col, '[[:space:]]+', ' ')(8.0+),PostgreSQL 用REGEXP_REPLACE(col, '\s+', ' ', 'g'),SQLite 没原生正则替换,得用扩展或应用层处理 - 过滤非法字符(如 SQL 注入敏感符号):优先用白名单式清洗,比如只保留字母、数字、中文、常用标点:
REGEXP_REPLACE(col, '[^a-zA-Z0-9\u4e00-\u9fa5,。!?;:""''()【】、]', '')(PostgreSQL),MySQL 注意开启regexp_engine配置
别依赖 UPPER() 或 LOWER() 做“标准化”——中文、emoji、带重音的西文字母会出问题,且大小写本身可能是业务语义(如用户名区分大小写)
触发器清洗会影响性能和事务行为吗?
会,而且影响是隐性的:
- 每次
INSERT都要执行触发器逻辑,字符串正则替换在大数据量导入时可能明显拖慢速度(尤其 MySQL 8.0 以前的REGEXP_REPLACE性能较差) - 触发器内抛错会中断整个事务:比如清洗时发现手机号长度不对,用
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Invalid phone'(MySQL)会让插入失败并回滚 - PostgreSQL 中若触发器函数返回
NULL,整行会被跳过(不插入),这不是错误,但容易被忽略
测试时务必验证边界情况:NULL 值是否被误清洗成空字符串?空字符串是否该保留?含 NUL 字符(\0)的输入会不会导致截断?
为什么不用应用层清洗而要用触发器?
触发器是兜底手段,不是首选方案:
- 应用层清洗更灵活(可调用完整正则库、调用外部服务校验)、易测试、日志完整
- 触发器只对走 SQL 插入的数据生效;ORM 批量插入、
LOAD DATA INFILE、主从同步、逻辑复制等场景可能绕过触发器 - 多个触发器叠加时顺序难管理(MySQL 不保证同类型触发器执行顺序,PostgreSQL 按创建时间),清洗逻辑容易互相干扰
真正需要触发器的典型场景只有两个:一是遗留系统无法改应用代码,二是必须保证所有接入方式(包括直连 DB 的脚本)都遵守同一套清洗规则。其余情况,优先在 ORM 层或 API 入口做清洗。
触发器清洗最麻烦的不是写法,而是它藏在数据库里,出问题时排查路径长——应用日志看不到清洗过程,DBA 日志又不记录每行清洗细节,线上出脏数据时往往要翻触发器定义+模拟输入反复试。

















