最轻量可靠的字段格式校验应在表定义中使用CHECK约束,SQL Server可配合TRY_CAST或PATINDEX增强校验能力,MySQL需用SIGNAL主动中断,PostgreSQL须配BEGIN…EXCEPTION块确保回滚,跨库需统一清洗、校验、转换三步并处理NULL、空格、时区等差异。

SQL Server 用 CHECK 约束 + TRY_CAST 做字段级格式校验
字段格式合法性(如邮箱、手机号、日期字符串)应在写入前就拦住,而不是等进到存储过程里再处理。最轻量、最可靠的方式是直接在表定义上加 CHECK 约束——它由引擎强制执行,不依赖过程逻辑,也不会被绕过。
但 CHECK 对复杂格式支持有限,比如邮箱不能只靠 LIKE '%@%.%',得防 ' @ . ' 这类空格干扰。这时可配合 TRY_CAST 或 PATINDEX:
-
TRY_CAST(email AS UNIQUEIDENTIFIER)可间接验证是否为合法 GUID 格式(若业务允许) -
PATINDEX('%[^a-zA-Z0-9._%+-@]%', email) = 0排除非法字符,再叠加上email LIKE '%_@__%.__%' - 避免在
CHECK中调用 UDF(用户函数),SQL Server 不保证其确定性,可能导致约束失效
MySQL 存储过程中用 SIGNAL 拦截非法输入
MySQL 没有 CHECK 约束的运行时扩展能力,且 INSERT ... SELECT 中无法在子查询里触发 SIGNAL,所以必须把校验逻辑提到语句执行前,并用 SIGNAL 主动中断。
常见错误是只做 SELECT COUNT(*) 判断后就直接 INSERT,中间没锁也没事务保护,高并发下可能校验通过但插入时违反唯一约束。
- 校验邮箱:先
IF email NOT REGEXP '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '邮箱格式不合法'; END IF; - 必须用
BEGIN ... END包裹整个逻辑块,并声明DECLARE EXIT HANDLER FOR SQLEXCEPTION来确保SIGNAL触发时能回滚 - 别在循环里对每一行都
SIGNAL,应先用临时表或变量收集所有待校验值,批量判断后再统一报错
PostgreSQL 中 RAISE EXCEPTION 要配 BEGIN … EXCEPTION 块
PostgreSQL 的函数默认不自动回滚,RAISE EXCEPTION 只是抛异常,若没用 BEGIN ... EXCEPTION 捕获,上层调用可能忽略或部分提交。
尤其当校验逻辑依赖其他表(比如查用户状态),还涉及 SELECT ... FOR UPDATE 加锁时,没包好异常块会导致锁残留或数据不一致。
- 正确写法:
BEGIN ... INSERT ... EXCEPTION WHEN OTHERS THEN RAISE EXCEPTION '校验失败: %', SQLERRM; END; - 用
RAISE EXCEPTION '%', v_msg而不是拼接字符串,防止单引号注入或格式错乱 - 若校验需查外部表,注意默认
READ COMMITTED隔离级别下可能被并发修改,必要时加FOR UPDATE锁定源记录
跨数据库通用陷阱:NULL、空格、时区、精度
看似简单的“校验手机号是否为11位数字”,在不同库中行为差异极大:
- MySQL 的
REGEXP '^[0-9]{11}$'会拒绝' 13812345678 '(含空格),而 PostgreSQL 的~ '^[0-9]{11}$'同样拒绝,但若没TRIM()预处理,应用层传来的空格就直接导致失败 - 日期字符串校验:SQL Server 的
TRY_CAST('2026-02-30' AS DATE)返回NULL;MySQL 在STRICT_TRANS_TABLES模式下才报错,否则静默转成'0000-00-00' - 浮点数校验:SQL Server 的
ISNUMERIC('1e4')返回 1,但TRY_CAST('1e4' AS INT)失败;PostgreSQL 的pg_typeof('1e4'::numeric)直接报错,得先TRIM再试
真正难的从来不是写一条正则,而是让同一套规则在不同环境里表现一致——建议把清洗(去空格、大小写归一)、校验、转换三步拆开,每步都留日志,别堆在一个表达式里。

















