在SQL中怎样防止并发UPDATE同一行导致的阻塞?

雨涛同学_3345

雨涛同学_3345

2026-10-25

379人浏览

原创

单条带条件的UPDATE+影响行数校验是最轻量可靠的防覆盖方案;它将业务校验塞入WHERE子句,避免静默覆盖,且无需加锁、减少阻塞;SELECT FOR UPDATE仅在必须读旧值再计算时才用,但易引发死锁或长等待。

在sql中怎样防止并发update同一行导致的阻塞?

单条带条件的 UPDATE + 影响行数校验,比加锁更轻量、更少阻塞;SELECT FOR UPDATE 反而容易引发死锁或长等待,只在必须读旧值再计算时才考虑。

UPDATE WHERE 条件必须包含业务约束

执行 UPDATE accounts SET balance = 150 WHERE id = 123 这种无判断赋值,会无视当前值直接覆盖。两个并发事务都这么写,后提交者必然覆盖前者——这不是“阻塞”,是静默覆盖。

真正防覆盖+防阻塞的关键,是把校验逻辑塞进 WHERE 子句:

  • UPDATE accounts SET balance = balance + 50 WHERE id = 123 AND balance >= 50:余额不足则整条语句不生效,mysql_affected_rows() 或 @@ROWCOUNT 返回 0
  • 必须确保 id 或 balance 字段有索引,否则可能触发全表扫描+表级锁,反而加剧阻塞
  • 该写法在 MySQL(READ COMMITTED / REPEATABLE READ)、SQL Server、PostgreSQL 下均安全,无需额外事务控制

SELECT FOR UPDATE 不是阻塞“解药”,而是新风险源

很多人以为加了 FOR UPDATE 就能避免阻塞,结果线上频繁卡顿甚至死锁。根本问题在于:它延长了锁持有时间,且极易因使用不当锁错范围。

Gemini
Gemini

Gemini是一款由 Google 提供的个人 AI 助手和多模态生成式 AI 产品。

下载
  • 在事务开头执行 SELECT * FROM orders WHERE user_id = 123 FOR UPDATE,接着调外部 HTTP 接口耗时 2 秒,再 UPDATE——这 2 秒内所有同用户请求全被阻塞
  • WHERE user_id = ? 没走索引 → MySQL 锁整张表,吞吐断崖下跌
  • Oracle 默认 autocommit=ON,SELECT FOR UPDATE 执行完立刻释放锁,后续 UPDATE 是全新事务,完全不受保护
  • SQL Server 写成 SELECT ... WITH (UPDLOCK) 却漏掉 BEGIN TRANSACTION → 锁语句一结束就释放,形同虚设

批量 UPDATE 或高并发场景下,锁顺序比锁本身更关键

死锁不是并发高导致的,而是多个事务对同一组行以不同顺序加锁。比如事务 A 先锁 id=1 再锁 id=5,事务 B 反过来,立刻循环等待。

  • ORDER BY 不等于锁顺序可控——必须用 EXPLAIN FORMAT=TRADITIONAL 验证是否真走索引、Extra 不含 Using filesort
  • 复合索引注意最左前缀:有 (status, id) 索引时,WHERE status = 'pending' ORDER BY id 才有效;仅 WHERE id > 100 则基本无效
  • 避免对非唯一字段(如 name)做 SELECT FOR UPDATE,尤其值重复率高时,间隙锁冲突概率陡增
  • 查不到记录也会死锁:SELECT * FROM orders WHERE order_no = 'ABC' FOR UPDATE 在 RR 隔离级下会对间隙加锁,多个并发请求争抢同一间隙,叠加 INSERT 意向锁即构成死锁链

超时设置必须分层落地,不能只靠数据库默认值

不设超时,一个长事务就能让后续所有相关 UPDATE “冻结”。但数据库级超时只能管“等锁”,不能覆盖网络中断、客户端崩溃等场景。

  • MySQL:在 START TRANSACTION 后、UPDATE 前执行 SET SESSION innodb_lock_wait_timeout = 5,超时报 Lock wait timeout exceeded
  • PostgreSQL:必须显式 SET lock_timeout = '3s',单位支持 ms/s/min,超时报 canceling statement due to lock timeout
  • 应用层兜底:用带超时的驱动(如 Python 的 psycopg2.connect(..., options='-c lock_timeout=3s')),避免事务中混入 HTTP 调用、文件读写等外部 I/O
  • UPDATE 前可加轻量预检:SELECT ... FOR UPDATE SKIP LOCKED(MySQL 8.0+/PG)尝试非阻塞获取锁,失败则快速退避

最容易被忽略的一点:所有基于 SQL 的方案都依赖开发者写对语句。只要业务上存在“同一资源只能被一个用户占用”的语义,就必须加唯一索引——这是终极兜底。比如抢座场景,place 字段必须建 UNIQUE 索引,哪怕 UPDATE 漏了 AND user_id IS NULL,后续插入也会被唯一约束拦截。

相关专题

更多
数据分析工具有哪些
数据分析工具有哪些

数据分析工具有Excel、SQL、Python、R、Tableau、Power BI、SAS、SPSS和MATLAB等。详细介绍:1、Excel,具有强大的计算和数据处理功能;2、SQL,可以进行数据查询、过滤、排序、聚合等操作;3、Python,拥有丰富的数据分析库;4、R,拥有丰富的统计分析库和图形库;5、Tableau,提供了直观易用的用户界面等等。

2023.10.12

4523

8

SQL中distinct的用法
SQL中distinct的用法

SQL中distinct的语法是“SELECT DISTINCT column1, column2,...,FROM table_name;”。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

2023.10.27

951

4

SQL中months_between使用方法
SQL中months_between使用方法

在SQL中,MONTHS_BETWEEN 是一个常见的函数,用于计算两个日期之间的月份差。想了解更多SQL的相关内容,可以阅读本专题下面的文章。

2024.02.23

1189

5

SQL出现5120错误解决方法
SQL出现5120错误解决方法

SQL Server错误5120是由于没有足够的权限来访问或操作指定的数据库或文件引起的。想了解更多sql错误的相关内容,可以阅读本专题下面的文章。

2024.03.06

6501

10

sql procedure语法错误解决方法
sql procedure语法错误解决方法

sql procedure语法错误解决办法:1、仔细检查错误消息;2、检查语法规则;3、检查括号和引号;4、检查变量和参数;5、检查关键字和函数;6、逐步调试;7、参考文档和示例。想了解更多语法错误的相关内容,可以阅读本专题下面的文章。

2024.03.06

3263

4

oracle数据库运行sql方法
oracle数据库运行sql方法

运行sql步骤包括:打开sql plus工具并连接到数据库。在提示符下输入sql语句。按enter键运行该语句。查看结果,错误消息或退出sql plus。想了解更多oracle数据库的相关内容,可以阅读本专题下面的文章。

2024.04.07

6460

11

sql中where的含义
sql中where的含义

sql中where子句用于从表中过滤数据,它基于指定条件选择特定的行。想了解更多where的相关内容,可以阅读本专题下面的文章。

2024.04.29

8841

6

sql中删除表的语句是什么
sql中删除表的语句是什么

sql中用于删除表的语句是drop table。语法为drop table table_name;该语句将永久删除指定表的表和数据。想了解更多sql的相关内容,可以阅读本专题下面的文章。

2024.04.29

1210

5

sql中删除一列的命令是什么
sql中删除一列的命令是什么

在sql中,使用alter table语句可以删除一列,语法为:alter table table_name drop column column_name。想了解更多sql的相关内容,可以阅读本专题下面的文章。

2024.04.29

1052

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
热门推荐
/
最新课程
phpStudy极速入门视频教程
phpStudy极速入门视频教程

共6课时 | 54.7万人学习

独孤九贱(4)_PHP视频教程
独孤九贱(4)_PHP视频教程

共89课时 | 134万人学习