1 简介
乐观锁允许并发更新,仅在提交时检查冲突,适合冲突不频繁的情景;
悲观锁则在编辑时锁定记录,防止并发更新,适用于高冲突环境。
两种锁各有优劣,选择依赖于并发程度、资源开销和事务重试成本。
- 问题的根本:处理冲突
现在有一座小桥如下,两边都有人需要通过,如何处理他们谁先过的问题?
- 冲突避免
在计算机网络中,有两种处理冲突或碰撞的常用方法:
退避重试:检测并重试,这正是以太网的方式
阻止冲突:通过阻止其他并发数据来避免它们,这是 Wi-Fi 的默认方式。
在使用数据库系统,处理冲突实际上也是类似的。
我们可以允许冲突发生,但是我们需要在提交时检测到它,这正是乐观锁定的工作方式。
如果重试的成本很高,我们可以尝试通过锁定来完全避免冲突,这是悲观锁定工作原理。
在数据库操作中,丢失的数据可能发生在在“已提交读取”隔离级别下运行的任何数据库上。
而数据库有两种方式用于锁定数据,乐观锁和悲观锁:
乐观锁 Optimistic Locking,其中记录仅在更改时锁,如提交到数据库的动作
悲观锁 Pessimistic Locking,即记录在编辑时就被锁,从编辑到提交全过程
在两种数据锁模型中,锁在数据更改后都释放, 如已提交数据到数据库。
1 定义
- 乐观锁
乐观锁模型, 也称为乐观并发控制,是一种并发控制 关系数据库中使用的不使用记录锁的方法。
乐观的 锁允许多个用户尝试更新同一记录,而无需 通知用户其他人也在尝试更新记录。 仅当提交记录时,才会验证记录更改。如果一个 用户成功更新记录,其他用户尝试提交 他们的并发更新会被告知存在冲突。
优势:乐观锁避免了操作持续数据时的锁的开销。如果没有竞争更新, 此模型提供快速更新。
乐观锁使用场景是,当并发记录更新预计不频繁或悲观锁开销很高可以使用。
- 悲观锁
悲观锁模型, 防止同时更新记录。一旦一个用户开始更新一个记录,一个悲观锁被放在上面。
尝试更新此记录的其他用户将被告知其他用户正在进行更新。
其他用户 必须等到第一个用户完成提交其更改,从而 释放记录锁。只有这样,其他用户才能根据 上一个用户的更改。
悲观锁的优势 模式是它通过预防冲突来避免解决冲突的问题。 更新被序列化了,每个后续更新都从提交的开始 记录来自上一个用户的更改。
悲观锁场景是, 后续更新可以延迟到上一个更新 完成。
这通常意味着更新在较短的时间间隔内发生。
2 一个数据冲突的例子
例子:
老王读取帐户余额,值为 100元
紧接着,老王的家人 从帐户取出50,余额从 100更改为 50,并且提交。
此时老王还正在查询,想到账户余额还在,就取出60然后退出了,
老王以为最后的余额会是:100-60 = 40元。
但是,由于其他人已更改,老王的更新将使帐户余额为负值。
在大多数场景下乐观和悲观锁都是并发场景的解决方案。根据关系数据库类型和环境的不同,这可能会降低或提高性能。
通常,使用乐观锁时,您会发现需要在某处对值的行做版本控制。
当锁冲突的可能性较低时,乐观锁效果很好。如果有许多进程的相互作用,也就是锁冲突的可能性很高时,悲观锁效果很好。
3 条件和经典使用场景
这两种形式的锁都会导致进程等待数据的正确副本(如果该副本当前正由另一个进程使用)。
对于悲观锁,锁机制来自数据库本身(本机锁对象),而对于乐观锁,锁机制是某种形式的行版本控制,如时间戳,用于检查记录是否“过时”。
4 使用参考条件
但两者都会导致第二个进程挂起,使用时参考的条件:
-是否有足够的 IO/资源来处理行版本控制的形式?否则,您将增加开销。
如果是这样,比如 经常读取数据,业务经常锁数据以进行写入,使用锁将改进读取和写入之间的并发性 (尽管写入仍会阻止写入,但读取将不再阻止写入,反之亦然)
-代码是否容易受到死锁的影响,或者是否遇到锁?
如果没有遇到长锁或很多死锁,那么乐观锁的额外开销不会更大。
-如果数据库很大(或在非常有限的硬件上),并且数据页接近满,具体取决于关系数据库,则可能会导致需要在主要页面拆分整理和数据碎片,因此请务必打开后考虑重新索引。
5 经典场景:
许多关系数据库系统使用乐观锁定来提供ACID保证。Oracle,PostgreSQL和InnoDB MySQL引擎使用MVCC(多版本并发控制),它们基于乐观锁定。
比如在 MVCC 中使用了乐观锁,因此读取器不会阻止编写器,而编写器也不会阻止读取器,从而允许发生冲突。但是,在提交时,事务引擎会检测到冲突,并回滚冲突的事务。
另外在 SQL Server 中,数据库在读取“可重复读”或“可序列化隔离”级别的记录时会自动获取共享锁,因为 SQL Server 默认使用 2PL(两阶段锁定)算法。
而MySQL在使用可序列化隔离级别时默认使用悲观锁定,而对于其他不太严格的隔离级别,则使用乐观锁定。
6 小结
乐观锁方面,我们通常需要在两个同样令人不满意的选项之间进行选择。
一个是“成本更高的正确性”,这种成本与事件的可能性也许不成比例。
另一种选择是“部分正确”,过度简化的实现。
但是,悲观锁在最基本的形式中,有一个非常简单的实现。执行的成本将恢复为与事件发生的可能性成比例。
悲观和乐观锁都是有用的技术。当重试事务的成本非常高或争用太大以至于如果使用乐观锁,许多事务最终会回滚时,悲观锁定是合适的。
另一方面,乐观锁甚至可以跨多个数据库事务工作,它不依赖于锁定的物理记录。
总的来说,单个任务而言没有锁总是比有锁快,乐观锁的使用率较高。