高并发写入性能瓶颈在于锁策略、事务边界和日志解耦未优化——90%问题源于此三环节;需最小化事务粒度、剥离日志、合理设置隔离级别与锁提示,避免锁升级和阻塞。

高并发写入不是靠“加个存储过程”就能解决的,核心是锁策略、事务边界和日志解耦三件事没做对——90% 的性能问题都卡在这三步。
为什么存储过程里直接 INSERT 就会排队甚至超时
很多人以为存储过程预编译就天然抗并发,其实错在忽略了底层锁行为。哪怕你只 INSERT INTO orders 一行,只要表上有唯一索引冲突检测、触发器、外键或未关闭的 READ_COMMITTED_SNAPSHOT,就会触发行锁/页锁甚至表锁升级。
- 唯一约束冲突检测必须加锁,
INSERT失败前已持锁,后续请求全堵住 - 如果日志表和业务表在同一个事务里,
INSERT INTO audit.LogEntry会把主事务拖长,锁持有时间翻倍 - SQL Server 中没开
READ_COMMITTED_SNAPSHOT时,普通SELECT也会阻塞INSERT(读-写阻塞) - MySQL 下若
INSERT没走主键或唯一索引,可能触发聚簇索引全扫描+行锁,等效锁全表
正确姿势:事务只包真正需要原子性的部分
把“查→算→改”闭环控制在最小粒度,别让无关操作(比如日志、通知、统计)混进来。
- 主逻辑用
BEGIN TRAN→ 关键SELECT ... WITH (UPDLOCK, ROWLOCK)→UPDATE→COMMIT,全程不超过 50ms - 日志写入必须剥离:建独立 schema(如
audit),禁用触发器和外键,INSERT INTO audit.LogEntry WITH (TABLOCK)单独执行 - 异常处理别写在
TRY块里——挪到CATCH末尾,且用IF @@ERROR = 0判断是否成功,失败不中断主流程 - MySQL 存储过程必须显式
COMMIT或ROLLBACK,否则锁一直挂着;建议开头加DECLARE EXIT HANDLER FOR SQLEXCEPTION
隔离级别不是越高越好,READ COMMITTED 是多数场景真实起点
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 在高并发写入中基本不可用——它会对查询范围加间隙锁,哪怕 WHERE id = 123 也可能锁住 id BETWEEN 100 AND 150 的所有空隙,导致批量补单时 20 个线程全卡在 INSERT 阶段。
-
READ COMMITTED能避免脏读、开销低、并发友好,但不是设了就完事:它直接影响锁范围和死锁概率 -
READ COMMITTED SNAPSHOT只解决“读-写阻塞”,对写-写冲突(如热点订单更新)无效,仍要靠行锁排队 - SQL Server 没有
SELECT ... FOR UPDATE,等效写法是SELECT ... WITH (UPDLOCK, ROWLOCK),但必须配BEGIN TRAN,否则锁语句结束就释放 - ROWLOCK 提示不强制生效——如果查询走不了索引,优化器仍会升级为页锁或表锁
别信 GET_LOCK() 或应用层队列能扛高并发写入
GET_LOCK('key', 3) 是会话级函数,不绑定事务,连接池复用时极易锁残留;压测时重复写入频发,根本原因是它不能替代底层行锁。
- 绝不单独依赖
GET_LOCK()做数据一致性控制,它只适合防定时任务重跑这类低频轻量场景 - 真要用必须配超时且检查返回值:
IF GET_LOCK('order_lock', 3) = 1,否则直接跳过 - 写入操作仍需底层行锁兜底:
UPDATE orders WITH (UPDLOCK) WHERE order_no = @no才是可靠防线 - MySQL 9.6.0 虽将外键上移至 SQL 层增强 CDC 一致性,但不改变行锁本质——热点行更新永远要排队
最易被忽略的是:事务长度和锁持有时间呈指数级影响并发吞吐。一个本该 20ms 完成的扣库存操作,因混入日志写入和冗余查询拉长到 300ms,QPS 直接跌掉 90%。这不是数据库不行,是你没把事务切成“刀锋般窄”。










