MySQL的悲观锁与乐观锁

本文介绍了数据库并发控制中的悲观锁与乐观锁两种机制。悲观锁通过数据库的select...for update语句实现,在操作数据前锁定资源;乐观锁则通过记录版本号的方式,在更新数据时检查版本一致性来避免冲突。

悲观锁与乐观锁是两种常见的资源并发锁设计思路,也是并发编程中一个非常基础的概念。本文将对这两种常见的锁机制在数据库数据上的实现进行比较系统的介绍。

悲观锁(Pessimistic Lock)

悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常所说的“一锁二查三更新”即指的是使用悲观锁。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update操作来实现悲观锁。当数据库执行select for update时会获取被select中的数据行的行锁,因此其他并发执行的select for update如果试图选中同一行则会发生排斥(需要等待行锁被释放),因此达到锁的效果。select for update获取的行锁会在当前事务结束时自动释放,因此必须在事务中使用。

这里需要注意的一点是不同的数据库对select for update的实现和支持都是有所区别的,例如oracle支持select for update no wait,表示如果拿不到锁立刻报错,而不是等待,mysql就没有no wait这个选项。另外mysql还有个问题是select for update语句执行中所有扫描过的行都会被锁上,这一点很容易造成问题。因此如果在mysql中用悲观锁务必要确定走了索引,而不是全表扫描。

乐观锁(Optimistic Lock)

乐观锁的特点先进行业务操作,不到万不得已不去拿锁。即“乐观”的认为拿锁多半是会成功的,因此在进行完业务操作需要实际更新数据的最后一步再去拿一下锁就好。

乐观锁在数据库上的实现完全是逻辑的,不需要数据库提供特殊的支持。一般的做法是在需要锁的数据上增加一个版本号,或者时间戳,然后按照如下方式实现:

1. SELECT data AS old_data, version AS old_version FROM …;
2. 根据获取的数据进行业务操作,得到new_data和new_version
3. UPDATE SET data = new_data, version = new_version WHERE version = old_version
if (updated row > 0) {
    // 乐观锁获取成功,操作完成
} else {
    // 乐观锁获取失败,回滚并重试
}

乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这之间没有发生并发的修改。如果更新失败即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。

总结
  • 乐观锁在不发生取锁失败的情况下开销比悲观锁小,但是一旦发生失败回滚开销则比较大,因此适合用在取锁失败概率比较小的场景,可以提升系统并发性能

  • 乐观锁还适用于一些比较特殊的场景,例如在业务操作过程中无法和数据库保持连接等悲观锁无法适用的地方


<think>我们正在讨论MySQL中的悲观锁乐观锁。根据引用[1],按照的思想划分,可以分为乐观锁悲观锁。用户希望理解它们的定义、区别以及使用场景。 首先,定义: 1. 悲观锁:总是假设最坏的情况,每次操作数据时都认为别人会修改,所以每次操作时都会上,这样别人想操作这个数据就会阻塞直到它拿到。在MySQL中,典型的实现是通过`SELECT ... FOR UPDATE`语句加排他(X),或者使用`SELECT ... LOCK IN SHARE MODE`加共享(S)[^2]。 2. 乐观锁:相对乐观,认为操作数据时不会发生冲突,所以不上。而是在提交更新时,检查在此期间是否有别人修改过数据。通常使用版本号(version)或时间戳来实现。 区别: - 加方式:悲观锁在操作数据前加数据库层面的);乐观锁则没有实际的加,而是通过版本号控制。 - 使用场景:悲观锁适用于写操作多的场景,保证数据安全;乐观锁适用于读操作多的场景,减少加的开销,提高吞吐量。 - 性能:悲观锁在高并发写时性能较差(因为等待);乐观锁在高并发写时可能冲突较多,导致重试,但读操作性能好。 使用场景: - 悲观锁:当业务逻辑中包含需要严格保证数据一致性的写操作,例如银行转账、库存扣减(避免更新丢失,如引用[3]描述的问题)等。 - 乐观锁:适用于读多写少,冲突较少的场景,例如一些并发量不是很大的更新操作,或者可以接受重试的场景。例如,在更新用户信息时,可以通过版本号控制,避免更新覆盖。 下面我们详细说明: ### 悲观锁MySQL中,悲观锁通常通过事务和行级来实现。例如: ```sql BEGIN; SELECT * FROM accounts WHERE id = 1 FOR UPDATE; -- 加排他 -- 然后进行更新操作 UPDATE accounts SET balance = balance - 100 WHERE id = 1; COMMIT; ``` 这样,在事务提交前,其他事务不能修改id=1的记录,从而防止更新丢失。 ### 乐观锁 乐观锁不在数据库层面加,而是在表中增加一个版本号字段(如`version`)或时间戳。更新时检查版本号是否改变: ```sql -- 假设有一个version字段 UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 1 AND version = old_version; ``` 如果更新行数为0,则表示版本号已被修改,此次更新失败,需要重试或放弃。 ### 总结 - 悲观锁:强一致,牺牲性能;乐观锁:最终一致,性能较好。 - 选择哪种取决于应用场景:对数据冲突概率高的写操作用悲观锁;冲突概率低的用乐观锁。 §§相关问题§§ 1. 在MySQL中如何具体实现悲观锁? 2. 乐观锁的实现中,除了版本号还有哪些方法? 3. 悲观锁乐观锁在高并发场景下各自的优缺点是什么? 4. 如何避免乐观锁的ABA问题?</think>### MySQL悲观锁乐观锁详解 #### 悲观锁定义原理 悲观锁(Pessimistic Locking)基于"先加再访问"的假设,认为并发操作很可能会发生冲突。在MySQL中实现方式包括: 1. **排他(X)**:通过`SELECT ... FOR UPDATE`语句实现,阻止其他事务读写定行[^1] ```sql BEGIN; SELECT * FROM accounts WHERE id=1 FOR UPDATE; -- 加排他 UPDATE accounts SET balance=balance-100 WHERE id=1; COMMIT; ``` 2. **共享(S)**:通过`SELECT ... LOCK IN SHARE MODE`实现,允许其他事务读但禁止写[^2] ```sql SELECT * FROM products WHERE id=5 LOCK IN SHARE MODE; ``` #### 乐观锁定义原理 乐观锁(Optimistic Locking)基于"冲突概率低"的假设,在提交时检测冲突。核心实现方式: 1. **版本号机制**:表中增加`version`字段 ```sql UPDATE orders SET status='paid', version=version+1 WHERE id=100 AND version=5; -- 提交时检查版本 ``` 2. **时间戳机制**:使用`last_modified`时间戳字段 ```sql UPDATE inventory SET quantity=quantity-1, last_modified=NOW() WHERE product_id=10 AND last_modified='2023-06-15 10:00:00'; ``` #### 核心区别对比 | 特性 | 悲观锁 | 乐观锁 | |--------------|---------------------------|---------------------------| | 加时机 | 操作前加 | 提交时检查 | | 实现方式 | 数据库原生机制[^1] | 应用层版本控制 | | 并发性能 | 高并发下性能差 | 高并发下性能好 | | 冲突处理 | 阻塞等待 | 回滚/重试机制 | | 适用场景 | 短事务/强一致性要求 | 长事务/高并发读 | | 典型应用 | 银行转账[^3] | 商品库存更新 | #### 使用场景分析 **悲观锁适用场景**: 1. 资金交易系统(如转账操作,避免丢失更新[^3]) 2. 票务系统的座位定 3. 需要强一致性的库存扣减(如限量商品) **乐观锁适用场景**: 1. 高并发读多写少场景(如新闻点赞) 2. 业务逻辑允许重试的操作 3. 分布式系统跨数据库事务 4. 长事务操作(如复杂审批流程) #### 实战选择建议 - 当**冲突频率 > 20%** 时选择悲观锁 - 当**系统吞吐量要求高**时优先乐观锁 - 对**数据一致性要求极端严格**的用悲观锁 - **分布式环境**倾向乐观锁+重试机制 > 示例场景:电商库存扣减 > - 悲观锁方案:`FOR UPDATE`定商品行,避免超卖 > - 乐观锁方案:版本号更新,失败后前端提示"库存已变化"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值