[MySQL] 行级锁SELECT ... LOCK IN SHARE MODE 和 SELECT ... FOR UPDATE

本文详细介绍了MySQL InnoDB存储引擎中的锁机制,包括共享锁和排他锁的应用场景及其对事务的影响,并通过实验验证了不同情况下锁的行为表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、译文

翻译来自官方文档:Locking Reads

If you query data and then insert or update related data within the same transaction, the regular SELECT statement does not give enough protection. Other transactions can update or delete the same rows you just queried. InnoDB supports two types of locking reads that offer extra safety:

如果你在查询数据,然后在同一个事务里插入或者修改相关的数据,常规的 select 语句不会提供足够的保护。其他的事务可以修改或者删除你正在查询的行。InnoDB 支持两种可以提供安全机制的读取锁:

SELECT … LOCK IN SHARE MODE sets a shared mode lock on any rows that are read. Other sessions can read the rows, but cannot modify them until your transaction commits. If any of these rows were changed by another transaction that has not yet committed, your query waits until that transaction ends and then uses the latest values.

SELECT … LOCK IN SHARE MODE 在读取的行上设置一个共享锁,其他的session可以读这些行,但在你的事务提交之前不可以修改它们。如果这些行里有被其他的还没有提交的事务修改,你的查询会等到那个事务结束之后使用最新的值。

For index records the search encounters, SELECT … FOR UPDATE locks the rows and any associated index entries, the same as if you issued an UPDATE statement for those rows. Other transactions are blocked from updating those rows, from doing SELECT … LOCK IN SHARE MODE, or from reading the data in certain transaction isolation levels. Consistent reads ignore any locks set on the records that exist in the read view. (Old versions of a record cannot be locked; they are reconstructed by applying undo logs on an in-memory copy of the record.)

索引搜索遇到的记录,SELECT … FOR UPDATE 会锁住行及任何关联的索引条目,和你对那些行执行 update 语句相同。其他的事务会被阻塞在对这些行执行 update 操作,获取共享锁,或从某些事务隔离级别读取数据等操作。一致性读(Consistent Nonlocking Reads)会忽略在读取视图上的记录的任何锁。(旧版本的记录不能被锁定;它们通过应用撤销日志在记录的内存副本上时被重建。)

All locks set by LOCK IN SHARE MODE and FOR UPDATE queries are released when the transaction is committed or rolled back.

Note
Locking of rows for update using SELECT FOR UPDATE only applies when autocommit is disabled (either by beginning transaction with START TRANSACTION or by setting autocommit to 0. If autocommit is enabled, the rows matching the specification are not locked.

所有被共享锁和排他锁查询所设置的锁都会在事务提交或者回滚之后被释放。

注:
使用 SELECT FOR UPDATE 为 update 操作锁定行,只适用于 autocommit 被禁用(当使用 START TRANSACTION 开始事务或者设置 autocommit 为0时)。如果 autocommit 已启用,符合规范的行不会被锁定。

二、总结

此处参考:MySQL中的共享锁与排他锁

SELECT … LOCK IN SHARE MODE :共享锁(S锁, share locks)。其他事务可以读取数据,但不能对该数据进行修改,直到所有的共享锁被释放。

如果事务对某行数据加上共享锁之后,可进行读写操作;其他事务可以对该数据加共享锁,但不能加排他锁,且只能读数据,不能修改数据。

SELECT … FOR UPDATE:排他锁(X锁, exclusive locks)。如果事务对数据加上排他锁之后,则其他事务不能对该数据加任何的锁。获取排他锁的事务既能读取数据,也能修改数据。

注:普通 select 语句默认不加锁,而CUD操作默认加排他锁。

三、验证

注:使用 mysql 版本为 5.7.9,事务隔离级别为InnoDB默认隔离级别 可重复读(Repeated Read)。

对以下几种情况进行验证:

  1. 当前事务获取共享锁后,可以读写,其他事务是否可以进行读写操作和获取共享锁;
  2. 两个事务同时获取共享锁后,是否可以进行update操作;
  3. 当前事务获取排他锁后,其他事务是否可以进行读写操作和获取共享锁;
  4. 是否可对一条数据加多个排他锁;
  5. 行锁和索引的关系;
  6. 索引数据重复率太高会导致全表扫描;

1、当前事务获取共享锁后,可以读写,其他事务是否可以进行读写操作和获取共享锁:可以读,可以获取共享锁,不可以写

当前事务可以写:
这里写图片描述

事务1获取某行数据共享锁后,事务2更新该行阻塞:
这里写图片描述

事务1提交之后,事务2更新成功:
这里写图片描述

2、两个事务同时获取某行数据共享锁后,是否可以进行update操作:不可以

两个事务同时获取某行数据共享锁,事务1更新该行阻塞:
这里写图片描述

事务2提交之后,事务1更新成功:
这里写图片描述

3、当前事务获取某行数据排他锁后,其他事务是否可以对该行数据进行读写操作和获取共享锁:其他事务可以读,不可以获取共享锁,不可以写

可以读该行数据:
这里写图片描述

不可以获取该行数据共享锁:
这里写图片描述

不可以更新该行数据:
这里写图片描述

4、是否可对一条数据加多个排他锁:不可以
这里写图片描述

5、行锁和索引的关系:查询字段未加索引(主键索引、普通索引等)时,使用表锁

注:InnoDB行级锁基于索引实现。

未加索引时,两种行锁情况为(使用表锁):
- 事务1获取某行数据共享锁,其他事务可以获取不同行数据的共享锁,不可以获取不同行数据的排他锁
- 事务1获取某行数据排他锁,其他事务不可以获取不同行数据的共享锁、排他锁

加索引后,两种行锁为(使用行锁):

  • 事务1获取某行数据共享锁,其他事务可以获取不同行数据的排他锁
  • 事务1获取某行数据排他锁,其他事务可以获取不同行数据的共享锁、排他锁

未加索引表结构:
这里写图片描述

未加索引,事务1获取某行数据共享锁,事务2获取不同行数据共享锁成功:
这里写图片描述

未加索引,事务1获取某行数据共享锁,事务2更新不同行数据阻塞:
这里写图片描述

未加索引,事务1获取某行数据排他锁,事务2获取不同行数据共享锁阻塞:
这里写图片描述

未加索引,事务1获取某行数据排他锁,事务2获取不同行数据排他锁阻塞:
这里写图片描述

加索引后表结构:
这里写图片描述

加索引后,事务1获取某行数据共享锁,事务2更新不同行数据成功:
这里写图片描述

加索引后,事务1获取某行数据排他锁,事务2获取不同行数据共享锁成功:
这里写图片描述

加索引后,事务1获取某行数据排他锁,事务2获取不同行数据排他锁成功:
这里写图片描述

6、索引数据重复率太高会导致全表扫描:当表中索引字段数据重复率太高,则MySQL可能会忽略索引,进行全表扫描,此时使用表锁。可使用 force index 强制使用索引。

表结构:

CREATE TABLE `room` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) NOT NULL,
  `username` varchar(200) NOT NULL DEFAULT '',
  `state` varchar(255) NOT NULL DEFAULT '-1',
  `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `index_uid` (`uid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8

表数据:
这里写图片描述

获取 uid = ‘11’ 的数据的行锁并更新,但更新失败:
这里写图片描述

降低数据重复率,更新成功:
这里写图片描述

这里写图片描述

强制使用索引,更新成功:force index(index_uid)
这里写图片描述

注:此处如果使用表锁,为何其他事务可以获取排他锁?

答:由于使用 force index ,而 InnoDB 行级锁基于索引实现,因此此处使用的是行锁。

四、InnoDB 行锁类型简介

参考:Mysql中那些锁机制之InnoDB

InnoDB行锁的三种类型:

  • Record Lock:对索引项加锁,锁定符合条件的行。其他事务不能修改和删除加锁项;
  • Gap Lock:对索引项之间的“间隙”加锁,锁定记录的范围,不包含索引项本身。其他事务不能再锁范围内插入数据;
  • Next-key Lock: 锁定索引项本身和索引范围,即Record Lock 和 Gap Lock 的结合。可解决幻读问题。
<think>我们正在讨论SQL中的SELECT ...FOR UPDATE语句。根据引用,这个语句用于在事务中锁定选中的行,以防止其他事务修改这些行,直到当前事务结束(提交或回滚)。这主要用于需要保证数据一致性的场景,如银行转账或库存更新。 基本语法: STARTTRANSACTION; SELECT...FROM... WHERE... FORUPDATE;--执行更新操作COMMIT;或 ROLLBACK; 注意事项: 1.锁定范围:满足WHERE条件的所有行都会被锁定。若无WHERE条件,则锁定整个表(在InnoDB中,若没有索引可能导致表锁)。2.隔离级别:不同隔离级别会影响行为,通常建议使用REPEATABLE READ或以上。 3.死锁:多个事务相互等待锁可能导致死锁,需注意操作顺序。4.性能:锁定行会降低并发性能,应谨慎使用。5.存储引擎:仅支持行级锁的引擎(如InnoDB)有效,MyISAM不支持。示例(银行转账):假设有账户表(accounts): id| account_number |balance我们要从账户123向账户456转账100元。步骤:1.开始事务。2.使用SELECT... FORUPDATE锁定两个账户的行(注意:按固定顺序锁定以避免死锁,例如按id升序)。3.检查账户123余额是否足够。 4.更新账户123余额(减去100)。5.更新账户456余额(加上100)。 6.提交事务。 具体SQL:```sqlSTARTTRANSACTION; --锁定两个账户的行,按id顺序锁定以避免死锁SELECT* FROMaccounts WHEREaccount_numberIN (123,456) ORDERBY idFOR UPDATE; --检查账户123的余额SELECT balanceINTO @balanceFROM accountsWHERE account_number =123; --如果余额足够,则进行转账IF @balance >=100THENUPDATEaccounts SETbalance =balance -100WHERE account_number =123;UPDATE accountsSET balance= balance+100 WHEREaccount_number=456;END IF; COMMIT;``` 注意:在MySQL中,IF语句通常用于存储过程,如果不在存储过程中,可以使用程序逻辑判断。另一个示例(库存更新): 假设商品库存表(products): id| product_id| stock扣减商品ID为1001的库存1个: ```sqlSTART TRANSACTION;--锁定商品1001的行SELECT *FROM productsWHERE product_id =1001 FORUPDATE;--检查库存SELECT stockINTO @stockFROM productsWHERE product_id =1001;--如果库存足够,则扣减IF @stock >0THENUPDATE productsSET stock= stock-1 WHEREproduct_id=1001; ENDIF;COMMIT; ```重要提示:使用SELECT ...FOR UPDATE时,应尽量缩小锁定范围(使用精确的WHERE条件)并尽快提交事务,以减少锁竞争。相关问题: 1. SELECT... FORUPDATE在哪些情况下会导致表锁?2.如何避免使用SELECT ...FOR UPDATE时出现的死锁问题? 3. SELECT... FORUPDATE LOCK INSHAREMODE有什么区别?</think>### SELECT ... FOR UPDATE 的用法与示例 `SELECT ... FOR UPDATE` 是 SQL 中用于实现**行级锁**的关键语句,主要在事务中锁定选中的行,防止其他事务修改这些行,确保数据一致性。以下是详细说明: --- #### **核心作用** 1. **独占锁**:锁定查询到的行,禁止其他事务更新或删除(其他事务可读,取决于隔离级别) 2. **事务安全**:保证当前事务完成前,锁定数据不被篡改 3. **适用场景**: - 银行转账(防止并发修改余额) - 库存扣减(避免超卖) - 订单处理(防止重复操作) --- #### **基本语法** ```sql START TRANSACTION; -- 开启事务 SELECT column1, column2 FROM table_name WHERE condition FOR UPDATE; -- 锁定符合条件的行 -- 执行数据更新操作... UPDATE table_name SET column1 = value WHERE condition; COMMIT; -- 提交事务(释放锁) -- 或 ROLLBACK; -- 回滚事务(释放锁) ``` --- #### **关键注意事项** 1. **锁定范围**: - 精确 `WHERE` 条件 → 行级锁 - 无 `WHERE` 条件 → 锁整个表(慎用!) - 无索引字段 → 可能升级为表锁[^3] 2. **存储引擎**: - 仅支持行锁的引擎有效(如 **InnoDB**) - MyISAM 引擎无效(仅支持表锁)[^3] 3. **隔离级别影响**: - `REPEATABLE READ`(默认):锁定查询到的行 - `READ COMMITTED`:锁定实际更新的行[^1] 4. **死锁风险**: - 多事务循环等待锁会触发死锁 - 解决方案:按固定顺序操作资源(如按 ID 排序) --- #### **实际示例** ##### 场景:银行转账(保证余额一致性) ```sql START TRANSACTION; -- 锁定两个账户(按ID顺序避免死锁) SELECT * FROM accounts WHERE account_number IN (123, 456) ORDER BY id FOR UPDATE; -- 锁住账户123456 -- 检查余额是否充足 SELECT balance FROM accounts WHERE account_number = 123; -- 执行转账(假设余额充足) UPDATE accounts SET balance = balance - 100 WHERE account_number = 123; UPDATE accounts SET balance = balance + 100 WHERE account_number = 456; COMMIT; ``` > **关键点**:锁定期间其他事务无法修改这两个账户的余额,防止透支[^4]。 --- ##### 场景:库存扣减(防止超卖) ```sql START TRANSACTION; -- 锁定商品ID=1001的行 SELECT stock FROM products WHERE product_id = 1001 FOR UPDATE; -- 检查库存 IF stock > 0 THEN UPDATE products SET stock = stock - 1 WHERE product_id = 1001; END IF; COMMIT; ``` --- #### **常见问题解决方案** 1. **死锁处理**: - 缩短事务执行时间 - 按固定顺序访问资源(如按主键排序) - 设置锁等待超时:`SET innodb_lock_wait_timeout=10;` 2. **性能优化**: - 避免全表扫描(确保 `WHERE` 条件用索引) - 最小化锁定时间(事务尽快提交) > **注意**:过度使用会导致并发性能下降,仅在必要时使用[^3]。 --- #### **与其他锁对比** | **语句** | **锁类型** | **其他事务能否读** | **其他事务能否写** | |------------------------|------------|-------------------|-------------------| | `SELECT ... FOR UPDATE`| 独占锁 | ✅ (MVCC) | ❌ | | `LOCK IN SHARE MODE` | 共享锁 | ✅ | ❌ | ---
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值