【MySQL】悲观锁&乐观锁

本文介绍了数据库并发控制中的悲观锁与乐观锁两种机制。悲观锁通过数据库的select...forupdate指令实现,确保数据行锁定;乐观锁则采用版本号或时间戳方式,在更新时检查版本一致性。两者各有优劣,适用于不同场景。

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

悲观锁(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行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这之间没有发生并发的修改。如果更新失败即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。

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

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


MySQL 中的悲观锁乐观锁是两种处理并发访问和数据一致性的机制,它们在实现方式、适用场景以及性能特点上有显著区别。 ### 悲观锁 悲观锁假设并发冲突经常发生,因此在整个数据处理过程中对数据保持锁定状态,以确保数据的完整性。悲观锁的实现通常依赖于数据库提供的锁机制,例如行锁、读锁和写锁等。这种锁机制在操作开始前就对数据加锁,直到操作完成才会释放锁。悲观锁适用于写入频繁且冲突概率较高的场景,例如金融交易系统,对数据一致性要求极高,无法容忍中间状态的错误。 在实际应用中,悲观锁通过 `SELECT ... FOR UPDATE` 或 `SELECT ... LOCK IN SHARE MODE` 等语句实现,确保数据在事务处理期间不被其他事务修改 [^3]。 ### 乐观锁 乐观锁则假设并发冲突较少,因此在数据操作时不立即加锁,而是在提交更新时检查是否有冲突发生。乐观锁的实现通常依赖于版本号(Version)、时间戳(Timestamp)或比较交换(CAS)机制。如果在提交时检测到冲突,则拒绝操作并提示重试。这种锁机制适用于读取频繁且冲突概率较低的场景,例如统计数据的更新或低并发环境下的数据读取。 乐观锁的一个常见实现方式是通过版本号字段。在更新数据前,会检查当前版本号是否与数据库中的版本号一致,若一致则允许更新并增加版本号;否则,更新失败并提示冲突 [^2]。 ### 适用场景 - **悲观锁适用场景**: - **高冲突场景**:当系统中存在大量并发写入操作,冲突概率较高时,悲观锁可以有效避免数据不一致的问题。 - **数据敏感性高**:如金融系统中的交易处理,对数据一致性要求极高,不能容忍任何中间状态的错误。 - **实时性要求高**:需要立即获取数据并进行修改的场景,悲观锁可以确保数据在操作期间不会被其他事务修改 [^4]。 - **乐观锁适用场景**: - **低冲突场景**:当系统中读取操作远多于写入操作,且并发冲突概率较低时,乐观锁可以减少锁的开销,提高系统吞吐量。 - **允许重试**:在某些场景下,系统可以容忍操作失败并进行重试,此时乐观锁更为合适。 - **数据一致性要求适中**:对于某些非关键数据,如统计数据或日志信息,允许一定程度上的不一致,乐观锁可以提供更好的性能 [^2]。 ### 总结 悲观锁乐观锁各有优劣,选择合适的锁机制应根据具体的业务需求和系统特点来决定。悲观锁适合高冲突、高敏感度的场景,而乐观锁则适合低冲突、高读取频率的场景。理解这两种锁机制的核心思想和适用场景,有助于在实际开发中做出更合理的选择 [^4]。 ```sql -- 悲观锁示例:使用 SELECT ... FOR UPDATE 获取行锁 START TRANSACTION; SELECT * FROM accounts WHERE account_id = 1 FOR UPDATE; -- 执行更新操作 UPDATE accounts SET balance = balance - 100 WHERE account_id = 1; COMMIT; -- 乐观锁示例:使用版本号控制并发更新 UPDATE accounts SET balance = balance - 100, version = version + 1 WHERE account_id = 1 AND version = 5; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值