数据库层校验需分层:身份证须长度判断、收紧正则、日期校验、手算校验码;邮箱只需结构正则、长度与非空检查;触发器禁用return、非确定函数,且须判空防null漏检。

直接在 BEFORE INSERT 或 BEFORE UPDATE 触发器里用 REGEXP + 辅助函数组合校验,能拦住 95% 以上的非法值;但仅靠正则无法验证身份证日期逻辑或邮箱域名有效性,必须分层处理。
身份证格式校验:先过正则,再筛日期和校验码
只写 NEW.id_card REGEXP '^[0-9]{17}[0-9X]$' 是错的——它放行首位为 0 的地址码、非法年份(如 0000)、不存在的月份(13)和日期(20230230)。生产环境至少要:
- 前置长度判断:
IF LENGTH(NEW.id_card) != 18 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '身份证必须为18位'; END IF; - 基础正则收紧:
^[1-9][0-9]{5}(19|20)[0-9]{2}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])[0-9]{3}[0-9X]$(注意\d在 MySQL 5.7 不支持,统一用[0-9]) - 出生日期二次校验:
STR_TO_DATE(SUBSTRING(NEW.id_card, 7, 8), '%Y%m%d') IS NULL—— 若返回NULL,说明是 20991301 这类假日期,直接报错 - 第 18 位校验码必须手算:取前 17 位,按权重
(7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2)加权求和,sum % 11后查表'10X98765432'对应位,且末位X必须大写(UPPER(RIGHT(NEW.id_card, 1)))
邮箱格式校验:正则只能防明显错误,别信“完美正则”
RFC 5322 定义的邮箱正则长达上千字符,MySQL 触发器里根本没法维护。实际只需守住底线:
- 结构合法:
NEW.email REGEXP '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'(允许常见符号,拒绝空格和连续点) - 长度限制:
LENGTH(NEW.email) BETWEEN 5 AND 254(RFC 硬性上限) - 关键字段非空:
IF NEW.email IS NULL OR TRIM(NEW.email) = '' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '邮箱不能为空'; END IF; - 不校验域名是否真实存在——DNS 查询不能进触发器,否则写入阻塞;交由应用层异步验证或定时任务扫活域
触发器写法避坑:信号、事务与兼容性
MySQL 触发器里不能用 RETURN,出错必须用 SIGNAL 抛异常,否则静默失败;且所有校验必须放在 BEFORE 阶段,AFTER 已入库无意义:
-
SIGNAL SQLSTATE '45000'是通用错误码,比自定义状态码更稳妥(避免主从复制中断) - 涉及字符串操作(如
SUBSTRING、LEFT)前,务必加IF NEW.id_card IS NOT NULL判断,否则SUBSTRING(NULL, 7, 8)返回NULL,后续STR_TO_DATE(NULL, ...)仍为NULL,可能漏判 - MySQL 5.7 默认不支持
\d,所有数字匹配统一用[0-9];8.0+ 虽支持,但跨版本迁移时容易翻车 - 触发器内禁止调用非确定性函数(如
RAND()、NOW()),否则主从复制可能失败;SYSDATE()同理,要用就用CURRENT_TIMESTAMP
真正难的不是写对一行正则,而是想清楚:哪些校验必须在数据库层强控(如长度、基本结构、日期逻辑),哪些该交给应用层兜底(如邮箱域名连通性、身份证实时核验)。过度堆砌触发器逻辑,会让写入变慢、调试变难、升级变脆——尤其是校验码计算这种 17 步硬编码,改错一个权重就得重装整个触发器。










