存储过程压测前必须 mock 异常场景,重点验证出错时是否拖垮连接池,需用 RAISERROR/SIGNAL 主动触发数据库与应用级异常,并确保错误真实抛出;并发压测须多连接独立执行,避免单会话掩盖锁竞争;SQL Server 应防 SP:Recompile,MySQL 需显式事务控制,PostgreSQL 宜设函数为 STABLE 以复用计划;最终需混沌压测覆盖超时、kill、隐式转换等边界组合。

存储过程压测前必须 mock 的异常场景
不模拟异常就压测,等于只测了 happy path,上线后第一个超时或死锁就暴露问题。重点不是“能不能跑通”,而是“出错时会不会拖垮整个连接池”。
-
RAISERROR(SQL Server)或SIGNAL SQLSTATE(MySQL 5.5+)必须在关键分支插入,比如事务开始后、更新主表前、调用外部存储过程后 - 不要只 mock 数据库级错误(如
1205死锁),还要主动触发应用级异常:空输入、非法状态码、超长字符串截断(触发String or binary data would be truncated) - 避免在
TRY……CATCH里吞掉所有错误——压测时要让错误真实抛出,否则你看不到连接堆积和重试风暴
用 SQL Server Profiler + 模拟并发时的陷阱
直接用 SSMS 执行 100 次 EXEC 看不出问题,因为单会话串行执行掩盖了锁竞争和编译争用。
- 必须用
sqlcmd或 .NETSqlConnection启多个线程 / 进程,每个连接独立执行,否则测的是 CPU 编译速度,不是存储过程并发健壮性 - Profiler 抓到大量
SP:Recompile事件?说明参数化不足——检查是否用了拼接 SQL(@sql = 'SELECT * FROM t WHERE id =' + @id),改用sp_executesql带参数 - 如果压测中出现
Timeout expired,先查sys.dm_exec_requests中wait_type是LCK_M_U还是ASYNC_NETWORK_IO:前者是锁冲突,后者大概率是客户端没及时取走结果集,存储过程里别用SELECT返回大结果集
MySQL 存储过程中如何安全模拟高负载下的事务中断
MySQL 的存储过程不支持 TRY……CATCH,靠 DECLARE HANDLER 捕获错误,但 handler 不能中断当前语句执行流——这点极易误判。
- 用
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'simulated failure'主动中断,但注意:handler 只在语句执行完后触发,所以要在关键语句后加IF @error_flag THEN LEAVE proc_label; END IF; - 压测时关掉
autocommit=1,用显式BEGIN/COMMIT包裹逻辑,否则INSERT …… ON DUPLICATE KEY UPDATE类操作可能部分生效,导致数据不一致却无报错 - 避免在循环里频繁
SELECT …… INTO赋值——压测下内存分配慢,容易触发Out of memory;改用临时表 +INSERT …… SELECT批量处理
PostgreSQL 函数压测时最容易被忽略的 planner 行为
PG 的函数默认是 VOLATILE,每次调用都重计划,高并发下 pg_stat_statements 会显示大量重复解析——这不是你的 SQL 写得差,是函数属性没设对。
- 确认函数是否真需要
VOLATILE:如果只读且不依赖CURRENT_TIMESTAMP或random(),改成STABLE,能复用执行计划,降低 CPU 压力 - 压测中发现
EXPLAIN (ANALYZE, BUFFERS)显示大量Shared Hit却响应慢?检查是否启用了jit = on——JIT 编译在简单函数上反而拖慢,压测前设SET jit = off - 不要在函数里写
PERFORM pg_sleep(0.1)模拟延迟——这会让事务长时间持有锁;改用SELECT pg_advisory_lock(123)+pg_sleep+pg_advisory_unlock,更贴近真实阻塞场景
真正难测的不是峰值 QPS,而是当 5% 的请求因网络抖动超时、3% 的事务被 kill、2% 的参数触发隐式转换时,剩下的 90% 请求还能不能稳住连接池和事务隔离性。这些边界组合,没法靠单点脚本覆盖,得靠带错误注入的混沌压测工具打进去看。