news 2026/7/28 1:27:12

数据库行锁到底是什么?——从零讲到影院票务里的用法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
数据库行锁到底是什么?——从零讲到影院票务里的用法

🥰个人主页:会编程的土豆(欢迎来访)
💎作者简介:后端学习者
❄️个人专栏:数据结构与算法,数据库,leetcode
那些你一个人走过的夜路,终将化作照亮未来的光

写给基础一般的同学:假设你不懂 InnoDB、隔离级别
读完目标:知道行锁解决什么问题、怎么写、什么时候用/不用,并能对照自己的FOR UPDATE代码讲清楚


〇、先别急着背名词

你先记住一个生活场景:

图书馆里有一排柜子。
行锁≈ 你正在用某一格柜子时,给这一格挂了「使用中」,别人不能同时往这一格塞东西。
其它柜子不受影响。

对应到数据库:

  • 一行数据 = 一格柜子(比如某一个座位那一行)
  • 行锁= 暂时不许别人改这一行
  • 表锁= 整排柜子全锁(粗暴,别人动哪一行都受影响)

抢票/选座场景里,我们通常要的是:只锁住被抢的那几个座位行,而不是锁整张seats表。


一、为什么需要锁?没有锁会怎样?

数据库经常被很多请求同时访问。

想象两个用户几乎同时做这件事:

1. 读一下:座位是不是空闲? 2. 如果是空闲,就改成「我锁定了」

如果中间没有互斥,可能变成:

用户A读到:空闲 用户B读到:空闲 ← A还没改完 用户A改成:A锁定 用户B改成:B锁定 ← 超卖/覆盖

这就是并发下的经典问题:检查的时候成立,真正改的时候已经变了

锁的作用很朴素:

让「读 + 判断 + 改」对同一行变成一个人做完,另一个人再来。


二、行锁是什么?(定义 + 边界)

2.1 一句话定义

行锁(Row Lock):数据库锁住的是表里的某一行(或若干行),而不是整张表。

在 MySQL 的InnoDB引擎里,行锁很常见。
(如果你的表是 MyISAM,行锁支持很弱,课设表一定要用 InnoDB。我们项目 schema 写的就是ENGINE=InnoDB。)

2.2 行锁管多久?

通常管到:

  • 事务提交 Commit,或
  • 事务回滚 Rollback

也就是说:行锁 lifespan ≈事务的寿命

事务很快结束,锁就很快释放;
你在事务里睡觉、调慢接口,锁就会被你拿很久——别人会卡很久

2.3 行锁「禁止」的是什么?

同一行,别人想再:

  • SELECT ... FOR UPDATE(也要独占)
  • UPDATE/DELETE这行

往往会等待,直到你的事务结束。

注意:

  • 别人改别的行,通常还能改
  • 「绝对不能读」不一定:普通SELECT在默认隔离下往往还能读到旧的已提交数据(这点后文会再说,初学先抓住「改同一行会等」)

三、和表锁、业务锁的区别(一定要分清)

3.1 行锁 vs 表锁

行锁表锁
锁的范围一行/几行整张表
并发好(各改各的行)差(大家排队)
典型场景改订单、改座位整表维护、某些引擎

选座只要锁「被点的座位」,用行锁;没必要锁全表。

3.2 行锁 vs 业务锁定(超容易混)

你项目里其实有两层「锁」:

数据库行锁业务锁定字段
怎么体现SELECT ... FOR UPDATEstatus=1,lock_user_id,lock_until
管多久事务期间(很短)支付窗口(如 15 分钟)
目的并发瞬间不打架提交后座位仍显示被占

比喻再强调一次:

  • 行锁:你在柜台办手续时,工作人员按住档案不让别人同时改
  • 业务锁:办完后给你发「临时占座牌」,15 分钟内座位算你的

只有行锁、没有业务字段:事务一提交,别人立刻又能下单抢。
只有业务字段、没有行锁:两人可能同时读到空闲,一起改成锁定。

两样都要,才是完整的「抢座」。


四、怎么用行锁?(写法)

在 MySQL / InnoDB 里,课设最常用、最好讲的就是:

SELECT ... FROM 表 WHERE 条件 FOR UPDATE;

并且它必须放在事务里才有工程意义。

4.1 最小模板(请背这个骨架)

START TRANSACTION; -- 或 Go 里 tx.Begin() SELECT status, lock_user_id FROM seats WHERE id = 101 AND schedule_id = 12 FOR UPDATE; -- 锁住这一行 -- 在这里根据 status 判断 -- 然后 UPDATE / INSERT COMMIT; -- 成功提交,放锁 -- 或 ROLLBACK; -- 失败回滚,放锁

Go 里对应:

tx, err := db.Begin() defer func() { _ = tx.Rollback() }() err = tx.QueryRow(` SELECT status, lock_user_id FROM seats WHERE id=? AND schedule_id=? FOR UPDATE`, seatID, scheduleID, ).Scan(&status, &lockUID) // 校验... // tx.Exec(`UPDATE ...`) // tx.Exec(`INSERT ...`) tx.Commit()

4.2 几个写法要点

① 一定要用事务对象tx,不要混用

// 正确:查询和更新都走 tx tx.QueryRow(`... FOR UPDATE`) tx.Exec(`UPDATE ...`) // 错误:锁在 tx 上,更新走另一个连接 → 行锁等于白加 tx.QueryRow(`... FOR UPDATE`) db.Exec(`UPDATE ...`) // 危险!

② WHERE 尽量点到具体行,并走索引

WHERE id = ? -- 好:锁目标清晰 WHERE schedule_id = 12 -- 可能锁很多行,甚至触发更粗的锁

你项目用id=? AND schedule_id=?:目标是具体座位,同时防止乱传别的场次 id。

③ 事务要短

事务里不要:

  • time.Sleep
  • 调用微信/支付宝等很久的外部接口
  • 超大循环无关操作

锁拿得越久,别人等得越惨。

④ 失败要回滚

Go 里常用:

defer func() { _ = tx.Rollback() }()

中途return err会自动撤;成功再Commit()


五、什么情况下应该用行锁?

下面这些场景,很适合行锁(或同类互斥):

5.1 稀缺资源「谁拿走算谁的」

  • 选座、选房态、优惠券一券一人
  • 库存扣减(也可用条件更新,但行锁思路同类)
  • 号码池取号

特征:同一行不能被两个事务同时成功占有。

5.2 「先读后改」且中间不能插队

模式是:

读当前状态 → 按状态做分支判断 → 写回新状态

只要两个人可能对同一行做这三步,就要考虑互斥。FOR UPDATE就是把三步包进同一把锁里。

5.3 状态机迁移要串行

例如订单:

  • 用户支付
  • 定时任务超时取消

两边都可能改同一订单。对订单行FOR UPDATE,保证同一时刻只有一个迁移成功。
(你项目支付、取消、超时清理都锁了订单行。)

5.4 一句话判断题

问自己:

如果两个请求同时跑到这段逻辑,会不会把同一行改出矛盾结果?

会 → 考虑行锁(或条件更新等互斥手段)。
不会(只是各写各的日志、各插各的评论)→ 通常不必。


六、什么情况下不要乱用行锁?

6.1 纯展示查询

首页列电影、刷座位图给用户看:

SELECT * FROM seats WHERE schedule_id=?

一般不要FOR UPDATE
原因:展示不需要独占;加了只会让别人更新更慢。

6.2 冲突极少、可以接受重试

例如点赞数 +1,偶尔冲突用乐观锁/重试更轻。
不是所有UPDATE都要先FOR UPDATE

6.3 可以一条条件更新搞定时

UPDATE seats SET status=1, lock_user_id=? WHERE id=? AND status=0;

若影响行数=1 成功,=0 失败——这是另一种互斥(偏乐观/条件写)。
简单扣库存常用这个。
你选座因为要「读很多字段做复杂校验 + 插订单」,用FOR UPDATE更清晰。

6.4 跨很久的用户操作

不要指望:

FOR UPDATE 锁住 等用户掏出手机扫码 5 分钟 再 Commit

这会把行锁拿 5 分钟,系统会卡死。
正确做法:事务快速结束;长时间占用用业务字段(锁定到lock_until


七、结合你的项目:行锁出现在哪?

7.1 下单抢座:锁座位行

tx.QueryRow(` SELECT status,row_no,col_no,lock_user_id FROM seats WHERE id=? AND schedule_id=? FOR UPDATE`, sid, scheduleID)

然后:

  • 已售 → 失败
  • 别人锁定 → 失败
  • 通过 → 插订单、把座位改成锁定

两个用户抢同一sid:后到的在FOR UPDATE等待;你提交后他读到新状态,校验失败

7.2 支付 / 取消 / 超时:锁订单行

SELECT status,user_id,pay_deadline FROM orders WHERE id=? FOR UPDATE

防止:一边支付、一边超时释放,把状态改乱。

7.3 用一张图串起来

用户点下单 → BEGIN → 对每个座位 FOR UPDATE(行锁) → 校验 → INSERT 订单 → UPDATE 座位为业务锁定 → COMMIT(行锁释放,但 status 仍是锁定) → 15 分钟内别人仍不能买(业务锁) → 支付成功:再锁订单行,座位改已售 → 或超时:锁订单行,座位改回空闲

八、慢慢看一段「带行锁」的完整逻辑

用人话对应代码:

① Begin:开启事务(开始办事,还没对外宣布) ② defer Rollback:中途失败就当没发生 ③ FOR UPDATE:按住这个座位档案 ④ 看状态:卖了?别人占了?→ 不行就返回错误(自动回滚) ⑤ 都 OK:创建订单草稿 ⑥ 座位写上「我锁定了、锁到几点、对应哪张单」 ⑦ Commit:对外宣布成功,放开行锁

其中③④是行锁发挥作用的时刻;
是业务锁;
之后行锁没了,业务锁还在。


九、常见误区(基础差最容易踩)

误区 1:以为 FOR UPDATE 可以单独写,不需要事务

工程上请永远:事务包住 FOR UPDATE + 后续写

误区 2:以为加了行锁,整张表别人都不能动

只影响冲突的行(简化理解)。其它座位照样能卖

误区 3:以为行锁能代替支付超时

行锁很短。长时间占座必须靠lock_until+ 定时任务

误区 4:前端把按钮禁用了就不需要锁了

前端防的是手滑,防不了并发和抓包。互斥必须在服务器/数据库

误区 5:所有 SELECT 都加 FOR UPDATE

展示接口不要乱加,否则自己把自己锁慢

误区 6:表不是 InnoDB

没有真正的行级事务锁体验。建表看一眼ENGINE=InnoDB


十、怎么自己验证「行锁生效了」?

方法 A:双浏览器(推荐)

两个账号打开同一场次,同时选同一座位下单:

  • 一人成功
  • 一人提示锁定/已售

方法 B:人为拖慢事务(仅本地调试)

Commit前临时Sleep(5秒),另一个请求会明显卡住。
测完删掉。

方法 C:查库

成功一方:

SELECT id,status,lock_user_id,order_id FROM seats WHERE id=?;

应看到锁定人是成功用户。


十一、行锁相关的兄弟概念(先混个眼熟)

以后学更深会遇到,现在够用知道名字:

概念直觉
共享锁 S / 排他锁 X行锁里 FOR UPDATE 偏排他:我改你别改
死锁A 锁1等2,B 锁2等1,数据库踢掉一个
锁等待超时等太久报错(innodb_lock_wait_timeout
乐观锁不加长锁,靠版本号/条件更新,冲突就重试
悲观锁先锁再干活(FOR UPDATE 属于这类)

你的课设选型是:悲观行锁,适合「抢座位这种冲突可预期」的场景。


十二、什么时候用:速查表

场景建议
两人可能改同一座位/同一订单用行锁(FOR UPDATE)或等价互斥
下单=读状态+分支+写订单+写座位事务 + 行锁
支付与超时可能同时改订单对订单行加锁
首页列表、座位图展示不用 FOR UPDATE
用户扫码等 5 分钟不要持有行锁等;用业务锁定字段
简单 stock-1可考虑UPDATE ... WHERE stock>0

十三、

选座是稀缺资源竞争。我在事务里对座位行使用SELECT FOR UPDATE行锁,保证同一时刻只有一个事务能完成校验和锁定;事务很快提交。支付窗口靠座位状态和lock_until业务锁定维持,超时任务负责释放。这样既防并发超卖,又不会把行锁拿太久。


十四、小结

  1. 行锁锁的是一行,解决多人同时改同一行的冲突。
  2. 常用写法:SELECT ... FOR UPDATE,必须放在事务里,读写都走同一tx
  3. 适合:抢座、库存互斥、状态机并发迁移。
  4. 不适合:纯展示;也不要用行锁代替长时间业务占座。
  5. 你项目里:下单锁座位行,支付/超时锁订单行——这就是标准用法
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/28 1:26:49

WarcraftHelper:魔兽争霸III终极兼容性革命方案

WarcraftHelper:魔兽争霸III终极兼容性革命方案 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 你是否曾因魔兽争霸III在现代系统上卡顿而…

作者头像 李华
网站建设 2026/7/28 1:25:32

收藏!告别模板套用,小白也能逆袭掌握大模型核心技术!

当前自学大模型者易陷入调用接口、套模板误区,难以满足企业对独立开发、解决问题人才的需求。文章强调应从底层逻辑学起,通过完整技术体系与实战项目,培养能解决真实业务问题的开发者。建议学习云和数据AI大模型应用开发课程,掌握…

作者头像 李华
网站建设 2026/7/28 1:22:43

Java增强型for循环原理与应用详解

1. Java增强型for循环的本质与语法规范Java的增强型for循环(Enhanced for loop)是J2SE 5.0引入的语法糖,专业术语称为"for-each循环"。其基本语法结构如下:for (ElementType element : collection) {// 处理element }这…

作者头像 李华
网站建设 2026/7/28 1:18:42

ComfyUI-Manager终极指南:三步打造你的AI绘画扩展中心

ComfyUI-Manager终极指南:三步打造你的AI绘画扩展中心 【免费下载链接】ComfyUI-Manager ComfyUI-Manager is an extension designed to enhance the usability of ComfyUI. It offers management functions to install, remove, disable, and enable various custo…

作者头像 李华