我理解乐观锁和悲观锁的区别,现在请问有人能够解释一下在什么情况下通常使用它们吗?
而且这个问题的答案是否会根据是否使用存储过程来执行查询而改变?
但是只是为了确认,乐观意味着“读取时不锁定表”,而悲观意味着“读取时锁定表”。
我理解乐观锁和悲观锁的区别,现在请问有人能够解释一下在什么情况下通常使用它们吗?
而且这个问题的答案是否会根据是否使用存储过程来执行查询而改变?
但是只是为了确认,乐观意味着“读取时不锁定表”,而悲观意味着“读取时锁定表”。
乐观锁定是一种策略,您可以读取记录并注意版本号(其他方法包括日期、时间戳或校验和/哈希),然后在写回记录之前检查该版本是否已更改。当您写回记录时,您会过滤版本上的更新以确保其是原子性的(即在检查版本和将记录写入磁盘之间未被更新),并一次性更新版本。
如果记录是脏的(即与您的版本不同),则中止事务,用户可以重新启动它。
此策略最适用于高容量系统和三层架构,在这种情况下,客户端不能实际维护数据库锁定,因为连接是从池中获取的,您可能不会在一个访问到另一个访问中使用相同的连接。
悲观锁定是当您锁定记录以供您独占使用直到完成时。它比乐观锁定具有更好的完整性,但需要您小心应用程序设计以避免死锁。要使用悲观锁定,您需要直接连接到数据库(通常在两层客户端服务器应用程序中)或可在连接之外独立使用的事务ID。
在后一种情况下,您将使用TxID打开事务,然后使用该ID重新连接。DBMS维护锁并允许您通过TxID重新获取会话。这就是使用两阶段提交协议(如XA或COM+ Transactions)进行分布式事务的工作方式。在处理冲突时,你有两个选择:
现在,让我们考虑以下的丢失更新异常:
在Read Committed隔离级别下可能会发生丢失更新异常。
在上面的图表中,我们可以看到Alice认为她可以从她的账户
中取出40元,但她没有意识到Bob刚刚改变了账户余额,现在这个账户只剩下20元。
悲观锁定通过对账户进行共享或读取锁定来实现此目标,因此Bob无法更改该账户。
在上面的图表中,Alice和Bob都会在他们都读取的account
表行上获取读锁。当使用可重复读或串行化时,数据库在SQL Server上获取这些锁。account
,所以在其中一个用户释放读锁之前,他们都不能更改它。这是因为写操作需要进行写/排他锁定获取,而共享/读锁定防止写/排他锁定。account
行上的读锁被释放后,Bob的UPDATE
才会继续并应用更改。在Alice释放读锁之前,Bob的UPDATE将被阻塞。version
列。每次执行UPDATE或DELETE时,version
列都会递增,并且在UPDATE和DELETE语句的WHERE子句中也会使用它。为了让它起作用,我们需要在执行UPDATE或DELETE之前发出SELECT并读取当前的version
,否则我们将不知道要传递到WHERE子句或递增的版本值。
乐观锁用于不预期发生许多冲突的情况。进行常规操作的成本较低,但如果确实发生冲突,则需要支付较高的代价来解决它,因为事务会被中止。
悲观锁用于预计会发生冲突的情况。将违反同步的事务简单地阻塞。
要选择适当的锁定机制,必须估计读写的数量并做出相应的计划。
乐观锁假设你读取时数据不会改变。
悲观锁则假设数据将发生改变,因此锁住它。
如果数据完全准确并不是必须的,使用乐观锁。虽然可能会出现偶尔的“脏”读取,但这很少导致死锁等问题。
大多数 Web 应用程序都可以接受脏读取——在极少数情况下,如果数据不能完全匹配,下次重新加载即可。
对于像许多金融交易等需要精确数据操作的场合,请使用悲观锁。重要的是确保精确读取数据,没有未显示更改——额外的锁定开销是值得的。
还有,Microsoft SQL Server 默认为页面锁定——基本上是你正在读取的行和旁边的几行。行锁定更准确但速度较慢。通常值得将事务设置为读取提交或无锁以避免在读取时发生死锁。
我认为还有一种情况,悲观锁定会是更好的选择。
对于乐观锁定,在数据修改时每个参与者都必须同意使用这种类型的锁定。但如果有人在不关心版本列的情况下修改数据,这将破坏乐观锁定的整个理念。
基本上有两个最受欢迎的答案。第一个基本上是这样说的:
乐观锁需要三层架构,您不一定需要为会话维护与数据库的连接,而悲观锁定是当您锁定记录以供您独占使用直到完成时。它比乐观锁定具有更好的完整性,您需要直接连接到数据库。
乐观(版本)由于没有锁定而更快,但在争用高时(悲观)锁定表现更佳,最好预防工作而不是放弃并重新开始。
或者
当您遇到罕见的冲突时,乐观锁定效果最佳
我创建了我的答案来解释“保持连接”与“低冲突”之间的关系。
为了了解哪种策略最适合您,不要考虑DB每秒的事务处理量,而是考虑单个事务的持续时间。通常,您会打开事务,执行操作并关闭事务。这是一个短小精悍的经典事务,符合ANSI的想法,并且可以避免锁定。但是,在实现许多客户同时预订相同房间/座位的票务预订系统时,该如何操作呢?在大多数情况下,乐观锁定更有效并且具有更高的性能。在选择悲观锁定和乐观锁定时,请考虑以下因素:
如果有很多更新操作且用户尝试同时更新数据的可能性相对较高,则悲观锁定非常有用。例如,如果每个操作可以一次更新大量记录(银行可能会在每个月底向每个账户添加利息收入),并且两个应用程序同时运行这些操作,它们将发生冲突。
对于包含经常更新的小表的应用程序而言,悲观锁定也更为适合。在这些所谓的热点情况下,冲突非常可能发生,因此乐观锁定在回滚冲突事务方面浪费了努力。
如果冲突的可能性非常低-存在许多记录但相对较少的用户,或非常少的更新和主要是读取类型的操作,则乐观锁定非常有用。
一种使用乐观锁的情况是使应用程序利用数据库来允许其中一个线程/主机“声明”任务。这是一种我经常使用的技术。
我能想到的最好的例子是使用数据库实现任务队列,多个线程同时声明任务。如果任务的状态为“可用”,“已声明”,“已完成”,则可以通过数据库查询执行类似于“将状态设置为'已声明',其中状态为'可用'”。如果多个线程以这种方式尝试更改状态,则除第一个线程外,所有线程都会因为脏数据而失败。
请注意,这只涉及使用乐观锁的用例。因此,作为“当您不希望发生许多冲突时使用乐观锁”的替代方案,它也可以在您预期发生冲突但希望正好一个事务成功时使用。
关于乐观锁和悲观锁,已经有很多好的评论了。
需要考虑的一个重要点是:
在使用乐观锁时,我们需要注意应用程序如何从这些故障中恢复。
特别是在异步消息驱动架构中,这可能会导致消息处理顺序混乱或更新丢失。
必须仔细考虑故障情况。