并发控制的乐观和悲观策略

悲观并发控制

一个锁定系统,可以阻止用户以影响其他用户的方式修改数据。如果用户执行的操作导致应用了某个锁,只有这个锁的所有者释放该锁,其他用户才能执行与该锁冲突的操作。这种方法之所以称为悲观并发控制,是因为它主要用于数据争用激烈的环境中,以及发生并发冲突时用锁保护数据的成本低于回滚事务的成本的环境中。

乐观并发控制

在乐观并发控制中,用户读取数据时不锁定数据。当一个用户更新数据时,系统将进行检查,查看该用户读取数据后其他用户是否又更改了该数据。如果其他用户更新了数据,将产生一个错误。一般情况下,收到错误信息的用户将回滚事务并重新开始。这种方法之所以称为乐观并发控制,是由于它主要在以下环境中使用:数据争用不大且偶尔回滚事务的成本低于读取数据时锁定数据的成本。
具体的区别与实例说明如下:

悲观并发控制:

假设A和B需要在SCC(Source Code Control)上修改同一个文件,那么在A锁定这个文件并修改的过程中,B无法修改这个文件,他只能等待A解锁文件后,他才能修改。由此可见,悲观并发控制是强调控制在前,确保整个过程不会出现文件版本的冲突。这样做会使得系统效率损耗在加锁机制上,尤其是加锁机制需要用到低速的外部存储(比如FileLocking)时,然而这样做就降低了事务的并发性,尤其是事务之间本来就不存在冲突的情况下。例如在A修改数据的时候,B只能等待。

由此可见,悲观并发控制通过使用显式的加锁机制或者时间戳,对每一个事务进行增量同步校验。如果加锁机制的成本较高的话,悲观并发控制就会出现一些弊端。首先就是效率问题,尤其是使用低效率的外部存储系统实现加锁机制时,这样的问题会更加突出。其次,在不会出现冲突的事务处理(例如只读型事务)中,使用加锁机制就显得没有必要了,这样做只能增加系统负载。再次,这种方式降低了系统的并发性。

乐观并发控制:

同样假设A和B需要在SCC上修改同一个文件,他们都将这个文件获取到自己的机器上,A修改完以后,就把文件上传到SCC上了,此时B也修改完了,当他也打算将文件上传时,系统会告知B,已经有人上传了,并出现一个错误。剩下的问题只能由B手动解决,例如B可以在SCC上将文件中更改的内容再次复制一遍。乐观并发控制使得系统效率损耗在事务的后期处理中,比如B必须手动的去修改他已经修改过的东西,然而这种控制方式在极少出现冲突的多事务处理中显得十分高效。

乐观并发控制将事务分为三个阶段:读取阶段、校验阶段以及写入阶段。在读取阶段,事务将数据写入本地缓冲(如上所述,A和B将文件都获取到自己的机器上),此时不会有任何校验操作;在校验阶段,系统会对所有的事务进行同步校验(比如在A或者B打算,但还没有,往SCC上写入更改后的文件时);在写入阶段,数据将被最终提交。在完成读取阶段以后,系统会对每个事务分派一个时间戳。

悲观并发控制中一个常见的问题就是死锁。例如A在修改文件T1,B在修改文件T2,他们分别锁定了这两个文件,假设T1和T2内容相关,B在修改T2的时候发现他还需要修改T1,可是T1却被A锁定;与此同时,A在修改T1的时候也发现了他还需要修改T2,可是T2又被B锁定了,这样就出现了死锁。当然,在实际操作中,这种情况可以由A和B协商解决,但是在错综复杂的多事务处理环境中,死锁将使得问题变得非常复杂。