throw必须前置在insert/update前,否则数据可能已部分写入;sql server用if+throw(错误号50000–59999、状态值1)中止批处理并确保客户端捕获,字符串判空需is null或trim,数值范围直写比较,外键检查用not exists加nolock。

THROW 必须放在写操作之前,否则数据可能已部分写入;错误信息里不带关键上下文,调用方根本没法定位问题。
SQL Server 用 IF + THROW 做前置校验
所有检查必须在 INSERT/UPDATE 语句之前完成,否则事务已污染,回滚代价高、还可能留下脏数据。
-
THROW比RAISERROR更可靠:它中止当前批处理,且能被 .NET/Java 的try-catch直接捕获 - 错误号限定在
50000–59999,状态值固定填1,避免与系统错误冲突 - 字符串判空不能只写
@name = '',得用@name IS NULL OR LTRIM(RTRIM(@name)) = '' - 数值范围直接写
IF @amount 1000000,别套CASE或隐式转换 - 外键存在性查法:用
IF NOT EXISTS (SELECT 1 FROM users WITH (NOLOCK) WHERE id = @user_id),加WITH (NOLOCK)防阻塞
MySQL 存储过程里 SIGNAL 必须配 DECLARE EXIT HANDLER
SIGNAL 本身不终止后续语句——这是和 SQL Server 最大差异。没配 handler,过程照常执行,极易造成数据不一致。
- handler 必须声明在
SIGNAL之前,且作用域仅限当前BEGIN...END块 - 推荐写法:
DECLARE EXIT HANDLER FOR SQLSTATE '45000' BEGIN ROLLBACK; RESIGNAL; END; -
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '库存不足',MESSAGE_TEXT不超过 128 字符,超长会被静默截断 - 别在子查询里调
SIGNAL,MySQL 不允许 - 事务不会自动回滚,
ROLLBACK必须显式写在 handler 里
PostgreSQL 用 RAISE EXCEPTION + EXCEPTION 块控制边界
RAISE NOTICE 不是断言,它只是日志;ASSERT 默认不生效,靠扩展驱动,不可依赖。
-
RAISE EXCEPTION '订单 % 已关闭', @order_id,支持占位符,防 SQL 注入 - 必须包裹在
BEGIN ... EXCEPTION ... END块内,否则异常会向上冒泡到调用层,失去控制 - 捕获唯一冲突要精确:用
WHEN unique_violation THEN ...,别写WHEN OTHERS吞掉磁盘满等真正严重错误 - 并发场景下,仅靠异常捕获不够,必要时配合
SELECT ... FOR UPDATE加锁 - 错误码建议自定义前缀,如
USING ERRCODE = 'P0001',方便应用层区分业务异常
共用校验逻辑别封装成标量函数
SQL Server 里 SELECT dbo.fn_check_status(@id) 这种标量 UDF,在批量操作中每行都调一次,性能崩得很快;MySQL 根本不支持函数内抛异常。
- SQL Server 推荐用内联表值函数(ITVF):
SELECT * FROM dbo.tvf_validate_order(@id),优化器能展开进执行计划 - ITVF 必须是单个
SELECT,不能含变量、IF或循环;返回固定两列:is_valid BIT和error_message NVARCHAR(256) - MySQL 没有 ITVF,校验逻辑只能收束在存储过程体内,靠命名规范和文档约束复用一致性
- 所有跨库共用的“校验模块”本质都是伪概念,真正可复用的是结构清晰、参数明确、行为可测的 SQL 片段
- 简单规则(如长度、非空、范围)优先用
CHECK CONSTRAINT固化在表上,存储过程只管复合逻辑
复杂点在于:不同数据库对“中断执行”的语义完全不同,THROW 是立即终止,SIGNAL 是抛信号但需 handler 拦截,RAISE EXCEPTION 是事务级退出。写错一层,就可能让校验形同虚设。










