结论:禁止在存储过程中使用 while 循环逐行 insert/update,应优先采用 merge(sql server)或 insert ... on duplicate key update(mysql),否则易引发锁表、数据丢失与性能崩溃。

直接说结论:别在存储过程里用循环 INSERT/UPDATE,优先用 MERGE(SQL Server)或 INSERT ... ON DUPLICATE KEY UPDATE(MySQL),否则容易锁表、丢数据、性能崩。
SQL Server 用 MERGE 替代手写 IF EXISTS + INSERT/UPDATE
手写 IF EXISTS 判断再分别 INSERT/UPDATE 看似清晰,实际并发下极易出错:两次查询之间可能被其他事务插入同主键数据,导致重复键冲突或漏更新。
- 必须用
MERGE语句,它原子性完成“存在则更新、不存在则插入”逻辑 -
MERGE的ON条件必须基于主键或唯一索引列,否则会报错或行为不可控 - 传入参数必须是表值参数(
@inputTable dbo.tblTypeEmployee READONLY),不能是逗号拼接字符串 - 避免在
MERGE中引用外部变量做条件判断,所有逻辑应落在USING子句和WHEN MATCHED/NOT MATCHED分支内
MySQL 用 INSERT ... ON DUPLICATE KEY UPDATE 而非 REPLACE INTO
REPLACE INTO 看似能实现 upsert,但本质是先 DELETE 再 INSERT,会触发自增 ID 跳变、外键级联删除、触发器重复执行,且无法保留原行的未指定字段值。
- 正确做法是确保目标表有主键或唯一索引,然后用
INSERT ... ON DUPLICATE KEY UPDATE - 更新字段必须显式写出,例如
UPDATE name = VALUES(name), status = VALUES(status),VALUES(col)表示本次 INSERT 尝试插入的值 - 如果批量数据量大(>5000 行),建议拆成多个
INSERT语句提交,避免单条 SQL 超过max_allowed_packet - 不要在
ON DUPLICATE KEY UPDATE中调用函数如NOW()多次——它会被执行多次,不是事务开始时的固定时间点
所有数据库都必须规避的硬伤:裸写 WHILE 循环 + 单行语句
用 DECLARE @i INT = 1; WHILE @i 这类写法,在生产环境等于主动制造慢 SQL 和锁竞争。
- 每轮循环都是独立事务(除非显式包裹
BEGIN TRANSACTION),但没错误捕获,中间失败就停在半途 - 单行操作无法利用索引批量定位,IO 和解析开销爆炸,1000 行可能比一条批量
INSERT慢 50 倍以上 - SQL Server 中
SET ROWCOUNT已被弃用,MySQL 中LIMIT在存储过程中不支持直接用于UPDATE语句 - 真正需要分批时,应按主键范围切片(如
WHERE id BETWEEN @start AND @end),而非靠计数器
事务边界与错误处理不是可选项
哪怕用了 MERGE 或 ON DUPLICATE KEY UPDATE,没事务包裹和错误检查,照样算“裸奔”。
- SQL Server 必须用
BEGIN TRY ... BEGIN CATCH,并在CATCH中检查ERROR_NUMBER()和@@ROWCOUNT—— 更新 0 行不等于成功 - MySQL 存储过程中无法直接捕获 SQLSTATE,需依赖应用层重试机制,或用
GET DIAGNOSTICS(5.6+)提取影响行数 - 无论哪种数据库,
COMMIT前必须确认@@ROWCOUNT/ROW_COUNT()符合预期,否则回滚并抛出明确错误 - 批量操作中任何一行失败,默认应整批失败(fail-fast),而不是静默跳过——业务一致性比吞吐量更重要
最常被忽略的点:批量 upsert 的输入数据本身是否干净。比如主键重复、字段类型不匹配、JSON 解析失败等,这些错误发生在存储过程执行前,却常被当成“存储过程写得不对”去调试。











