使用共享锁更新表中数据。 相关知识 在事务的隔离级别内容中,能够了解到两个不同的事务在并发的时候可能会发生数据的影响。细心的话可以发现事务隔离级别章节中,脏读、不可重复读、幻读三个问题都是由事务 A 对数据进行修改、增加,事务 B 总是在做读操作。如果两事务都在对数据进行修改则会导致另外的问题:丢失更新。 这就是本实训所要学习的主题,同时引出并发事务对数据修改的解决方案。 为了完成本关任务,你需要掌握:1.丢失更新的定义及产生原因;2.如何解决丢失更新问题。 丢失更新的定义及产生原因 丢失更新就是两个不同的事务在某一时刻对同一数据进行读取后,先后进行修改,导致第一次操作数据丢失: 该问题和之前事务并发出现的几个问题需要区分开, 因为解决方案不是一类!此类问题,只能用最高隔离级别 Serializable 或者手动使用锁来解决,本关我们就一起学习如何使用锁来解决。 如何解决丢失更新问题 由第一关介绍存储引擎后,我们知道 InnoDB 存储引擎才支持事务,因此我们使用 行锁 来解决,我们还介绍了行锁包括悲观锁和乐观锁,悲观锁包含了读锁和写锁,下面我们就使用悲观锁的两种方式来解决。 使用共享锁 共享锁(S),又称为读锁,获得共享锁之后,针对同一份数据,多个读操作可以同时进行,互不影响,但无法修改和删除数据。 例如:我在客户端 A 给数据 C 添加了共享锁,此时我在客户端 B 只能添加共享锁进行查看,没有修改的权利,如果我想要在客户端 B 进行修改,我只能在 A 处commit才能进行修改。 在查询语句后面增加LOCK IN SHARE MODE,MySQL 会对查询结果中的每行都加共享锁: select ... lock in share mode; 使用排他锁 排他锁(X),又称为写锁、独占锁。获得排他锁后,在当前写操作没有完成前,它会阻断其他写锁和读锁。 排它锁就是我在客户端 A 给数据 C 添加了排它锁,那么我在客户端 B 只能在客户端 A commit之后,才能select数据。 换句话说,只要我在客户端 B 用锁进行了查询,那我我都需要等待 A commit之后,如果此时我客户端 B 不加锁,我是可以查询到的。 添加排它锁的方式: select ... for update; 编程要求 在右侧命令行中编写代码,开启两个命令行窗口连接数据库,体验共享锁的使用,(当一个事务 A 连接在做更新操作的时候,另一个事务 B 连接不能进行更新操作,事务 A 提交后,事务 B 则可进行更新操作并提交,此时更新操作不会被丢失),具体操作如下: 点击+添加一个命令行窗口,在两个命令行中连接数据库(用户名为root,密码为123123); 假设源命令行窗口为 A 连接,新增加的窗口为 B 连接, ,请在 A 中连接数据库后,执行平台 /data/workspace/myshixun/src/step3/table.sql的 sql文件,将会为你创建好mydb数据库和account表,执行命令为: source /data/workspace/myshixun/src/step3/table.sql; 在 A、B 连接中均开启事务,使用共享锁查询account表中原有余额,并在 A 连接中将 id为1的余额改为0,id为2的余额改为200,在 B 连接中将id为2的余额改为0。 此时 B 连接中在等待 A 连接的更新操作完成(提交事务),先后将事务 A 、B 提交即可,平台将输出你更新后的用户余额。
时间: 2025-08-10 17:13:43 浏览: 2
<think>好的,我现在需要帮助用户解决如何在MySQL中使用共享锁(LOCK INSHAREMODE)防止丢失更新,并确保事务并发时的数据一致性的问题。首先,我得回顾一下用户的问题和相关引用,确保自己正确理解需求。
用户提到想了解如何使用共享锁更新数据以及解决丢失更新问题。根据提供的引用,特别是引用3和4,共享锁(LOCKIN SHARE MODE)允许其他事务读取数据,但阻止修改,直到锁释放。而排他锁(FORUPDATE)则更严格,不允许其他事务读写。用户需要的是在并发事务中防止丢失更新,即在多个事务同时读取同一数据后,更新导致其中一个覆盖另一个的情况。首先,我需要明确丢失更新的场景。例如,两个事务同时读取同一行数据,然后各自基于读取的值进行修改,后提交的事务会覆盖前一个的修改,导致前一个的更新丢失。这种情况下,如何用共享锁来避免呢?
根据引用3,使用共享锁可以阻止其他事务修改数据,但允许读取。那如果两个事务都使用LOCKIN SHARE MODE,会发生什么?当一个事务获取共享锁后,另一个事务也可以获取共享锁,但都不能进行修改,直到锁释放。这时候,如果两个事务都试图在读取后更新,可能会出现死锁,因为每个事务都持有共享锁,无法升级为排他锁。这时候可能需要事务在更新时尝试获取排他锁,但如果有其他共享锁存在,就会等待,导致死锁的可能性。
那用户的问题是如何用共享锁防止丢失更新。可能需要结合共享锁和排他锁的使用。例如,在读取时使用共享锁确保数据不被修改,然后在更新时获取排他锁。但这样是否有效?或者是否有其他方法?
根据引用4,共享锁的使用场景包括主从表的一致性,比如在插入从表记录前锁定主表数据,防止主表数据被删除。这可能与用户的问题不太直接相关,但说明共享锁用于需要保持数据不被修改但允许读取的情况。
回到丢失更新问题,通常的解决方案是使用悲观锁或乐观锁。悲观锁如SELECTFOR UPDATE,在读取时锁定行,防止其他事务读取或修改。而共享锁可能无法完全解决,因为多个事务可以同时持有共享锁,然后在更新时尝试获取排他锁,导致死锁。但用户特别询问的是如何使用共享锁来防止丢失更新。可能需要分步骤说明:首先用LOCK INSHAREMODE读取数据,这样其他事务可以读取但不能修改。然后在更新时,事务需要将共享锁升级为排他锁,但这在MySQL中可能无法自动实现,需要显式的更新语句获取排他锁。此时,如果有多个事务都尝试升级,可能会死锁,需要处理这种情况。例如,事务A和B都使用LOCK INSHAREMODE读取同一行,然后都尝试更新。此时,更新操作需要获取排他锁,但事务A和B都持有共享锁,导致互相等待,形成死锁。MySQL会检测到死锁并回滚其中一个事务。因此,使用共享锁可能不是防止丢失更新的最佳方法,而更常用的是SELECTFOR UPDATE。但用户可能希望了解共享锁的应用场景,或者如何在特定情况下使用共享锁来避免丢失更新。可能需要指出,虽然共享锁可以部分防止丢失更新,但在高并发场景下可能不如排他锁有效,并给出示例代码和步骤,同时说明潜在的问题,如死锁,以及如何处理。
另外,根据引用3中的示例,使用LOCKIN SHARE MODE后,执行更新操作,这可能在事务中自动获取排他锁。但需要确保在更新时能够成功,否则会阻塞直到锁释放。因此,正确的步骤可能是在事务中先使用共享锁读取,然后在更新时获取排他锁,这样其他事务无法在读取后同时更新,从而避免丢失更新。但需要验证这种方法的有效性。例如,事务A使用共享锁读取,事务B也可以使用共享锁读取。当A尝试更新时,需要排他锁,此时B仍持有共享锁,导致A等待。如果B在此时也尝试更新,同样需要排他锁,导致死锁。因此,这种方法可能导致死锁,需要事务重试机制或超时设置。
综上,回答应该包括使用共享锁的步骤,示例代码,以及可能的问题和解决方案。同时,比较共享锁和排他锁的适用场景,帮助用户理解何时使用哪种方法更合适。</think>### 如何在MySQL中使用共享锁防止丢失更新
#### 一、丢失更新问题场景
当多个事务同时读取同一数据并基于读取结果进行修改时,后提交的事务可能覆盖先提交事务的修改。例如:
1. 事务A和事务B同时读取账户余额为$1000
2. 事务A存入$200,更新为$1200
3. 事务B取出$100,更新为$900(覆盖了$1200)
#### 二、共享锁的防御机制
使用`SELECT ... LOCK IN SHARE MODE`实现以下效果:
1. **读取阶段**:允许并发读取,但禁止数据修改
2. **更新阶段**:自动升级为排他锁,强制串行化更新
```sql
-- 事务1
START TRANSACTION;
SELECT balance FROM accounts WHERE id=1 LOCK IN SHARE MODE; -- 获取共享锁
UPDATE accounts SET balance = 1200 WHERE id=1; -- 自动升级为排他锁
COMMIT;
-- 事务2
START TRANSACTION;
SELECT balance FROM accounts WHERE id=1 LOCK IN SHARE MODE; -- 等待事务1释放锁
UPDATE accounts SET balance = 900 WHERE id=1;
COMMIT;
```
#### 三、实现步骤详解
1. **事务开启**:显式声明事务边界
2. **共享锁读取**:`SELECT ... LOCK IN SHARE MODE`锁定目标数据
3. **业务处理**:基于读取值进行业务计算
4. **排他锁更新**:UPDATE语句自动申请排他锁
5. **事务提交**:释放所有锁
#### 四、锁机制对比
| 锁类型 | 并发读取 | 并发修改 | 适用场景 |
|-----------------|----------|----------|----------------------------------|
| 共享锁 | ✅允许 | ❌禁止 | 读多写少,需要保持读取一致性 |
| 排他锁(FOR UPDATE)| ❌禁止 | ❌禁止 | 写密集型操作,强数据一致性要求 |
#### 五、潜在风险与解决方案
1. **死锁风险**:
- 当两个事务互相等待锁释放时可能发生
- 解决方案:设置事务超时`SET innodb_lock_wait_timeout = 50;`
2. **性能影响**:
- 长时间持有共享锁会降低并发吞吐量
- 优化建议:缩小事务范围,尽快提交事务
#### 六、生产环境最佳实践
1. 配合索引使用:确保WHERE条件命中索引,避免锁表
2. 重试机制:代码层实现事务失败自动重试
3. 监控工具:使用`SHOW ENGINE INNODB STATUS`监控锁状态
```python
# Python事务重试示例
import MySQLdb
from retrying import retry
@retry(stop_max_attempt_number=3, wait_fixed=2000)
def update_balance():
conn = MySQLdb.connect(...)
try:
cursor = conn.cursor()
cursor.execute("START TRANSACTION")
cursor.execute("SELECT balance FROM accounts WHERE id=1 LOCK IN SHARE MODE")
# 业务逻辑处理
cursor.execute("UPDATE accounts SET balance = ...")
conn.commit()
except Exception as e:
conn.rollback()
raise e
```
#### 七、适用场景验证
1. **订单库存校验**:多个系统同时校验库存时保证基准值一致性
2. **财务余额核对**:审计过程中锁定当前账户状态
3. **主从表关联操作**:插入从表记录时锁定主表数据[^4]
阅读全文