当前位置: 首页 > 科技观察

乐观锁、悲观锁和MVCC,今天让你了解

时间:2023-03-12 10:38:38 科技观察

在数据库的实际使用中,我们经常会遇到不想同时写入或读取数据的情况,比如秒杀中场景,两个请求同时读取系统还有1个库存,然后依次更新库存为0。这时候就会出现超卖的情况。此时,货物的实际库存将与我们的记录不符。为了解决这种资源竞争导致的数据不一致问题,我们需要一种机制来保证数据的正确访问和修改,而在数据库中,这种机制就是数据库的并发控制。其中,乐观并发控制、悲观并发控制和多版本并发控制是数据库并发控制中使用的主要技术手段。悲观并发控制1.精华百科:在关系型数据库管理系统中,悲观并发控制(又称“悲观锁”,PessimisticConcurrencyControl,简称“PCC”)是并发控制的一种方法。它防止事务以影响其他用户的方式修改数据。如果一个事务执行的操作读取了一行数据并应用了锁,那么只有当该事务释放锁时,其他事务才能执行与锁冲突的操作。其实我们常说的悲观锁并不是真正的锁,而是一种并发控制的思想。悲观并发控制对数据修改持悲观态度。它认为当数据被外界访问时,不可避免地会发生冲突,所以在数据处理的过程中,使用锁来保证资源的独占使用。数据库的锁机制其实是基于悲观并发控制的观点来实现的,根据实际使用情况,数据库的锁可以分为很多种。具体可以参考我后面的文章。2.实现方法数据库悲观锁的加锁过程大致如下:启动一个事务后,根据操作类型对需要加锁的数据应用某种类型的锁:比如加锁成功,比如共享行锁,继续后面的操作,如果数据已经加了其他锁,和现在要加的锁冲突,加锁就会失败(比如加了排他锁)。这时需要等待其他锁释放(可能会出现死锁)事务完成后释放所有锁加锁3.优缺点优点:悲观并发控制采用保守策略:“先拿锁,成功后再访问数据”,保证数据的获取和修改有序进行,适合写多读少的环境使用。当然使用悲观锁不能保持很高的性能,但是在乐观锁不能提供更好性能的前提下,悲观锁可以保证数据安全。缺点:由于需要加锁,并且可能面临锁冲突甚至死锁的问题,悲观并发控制增加了系统的额外开销,降低了系统的效率,同时也降低了系统的并行度。乐观并发控制1.精华百科:在关系型数据库管理系统中,乐观并发控制(又称“乐观锁”,OptimisticConcurrencyControl,简称“OCC”)是并发控制的一种方法。它假设多用户并发事务在处理过程中不会相互影响,每个事务都可以在不产生锁的情况下处理自己影响的那部分数据。乐观并发控制是对数据修改持乐观态度,认为即使在并发环境下,外部对数据的操作一般不会造成冲突,所以不加锁,但在提交数据更新之前,每个事务都会先检查没有其他事务有事务读取数据后修改数据。如果有其他事务更新,则返回冲突信息,让用户决定如何进行下一步,如重试或回滚。可以看出,乐观锁其实并不是真正的锁,甚至没有使用锁来实现并发控制,而是通过其他方式来判断数据是否可以修改。乐观锁一般是用户自己实现的锁机制。虽然没有使用实际的锁,但可以产生锁定效果。2.实现CAS(ComparisonandExchange,比较和交换)是一种著名的无锁算法。无锁编程,即在不使用锁的情况下实现多线程之间的变量同步,即实现变量同步而不阻塞线程,所以又称为非阻塞同步(Non-blockingSynchronization)。实现非阻塞同步的方案称为“非阻塞算法”。乐观锁基本上是基于CAS(Compareandswap)算法实现的。我们先来看CAS流程。一个CAS操作过程可以用如下c代码表示:intcas(long*addr,longold,longnew){/*??Executesatomically.*/if(*addr!=old)return0;*addr=new;return1;}复制代码CAS有3个操作数,内存值V,旧期望值A,待修改的新值B。当且仅当期望值A和内存值V相同时,修改内存值V为B,否则什么都不做。整个CAS操作是一个原子操作,不可分割。乐观锁的实现与上述过程类似,主要有以下几种方式:版本号标记:在表中新增一个字段:version,用于保存版本号。获取数据时同时获取版本号,然后更新数据时使用如下命令:updatexxxsetversion=version+1,...where...version="oldversion"and....此时通过判断返回结果中受影响的行数是否为0来判断更新是否成功,如果更新失败,说明其他请求已经更新了该数据。时间戳标记:同版本号,只是通过时间戳来判断。一般来说,很多数据表都会有一个更新时间的字段,不需要再增加一个字段来通过这个字段来判断。待更新字段:如果没有时间戳字段又不想增加新的字段,可以考虑使用待更新字段来判断,因为更新后的数据一般都会发生变化,所以可以获取旧值要更新的字段的数量和更新前数据库的当前值。如果没有变化,则比较并更新该值。所有字段标记:数据表中的所有字段用于判断。这相当于不仅锁定了几个字段,而且锁定了整个数据行。只要行中的数据发生变化,就不会更新。3.优缺点优点:乐观并发控制没有实际加锁,所以没有额外的开销,对于死锁问题也不错。适用于读多写少的并发场景。因为没有额外的开销,所以可以大大提高数据库的性能。缺点:乐观并发控制不适合写多读少的并发场景,因为会出现大量的写冲突,导致多次等待重试数据写入。在这种情况下,它的开销其实比悲观锁要高。更高。而且乐观锁的业务逻辑比悲观锁复杂。在业务逻辑上,必须考虑失败和等待重试,不能避免其他第三方系统直接修??改数据库的情况。Multiversionconcurrencycontrol1.EssenceWikipedia:多版本并发控制(MCC或MVCC)是数据库管理系统中常用的并发控制,在编程语言中也用于实现事务内存。乐观并发控制和悲观并发控制都通过延迟或终止相应的事务来解决事务间的竞争条件,保证事务的串行化;前面两种并发控制机制虽然可以从根本上解决并发事务的序列化问题,但实际上是在解决写冲突的问题。两者的区别在于对写冲突的乐观程度(悲观锁也可以解决读写冲突问题,但性能一般)。在实际使用中,数据库的读请求是写请求的很多倍。如果能解决读写并发的问题,就可以大大提高数据库的读性能,这就是多版本并发控制可以做到的。事情。与悲观并发控制和乐观并发控制不同,MVCC是为了解决读写锁导致的多次、长时间读写操作的问题,也就是解决读写冲突的问题。MVCC可以与前两种机制中的任何一种结合使用,以提高数据库的读取性能。数据库的悲观锁是基于提高并发性能的考虑,一般同时实现多版本并发控制。不仅是MySQL,Oracle、PostgreSQL等其他数据库系统都实现了MVCC,只是各自的实现机制不同,因为MVCC没有统一的实现标准。总的来说,MVCC的出现是数据库不满于使用悲观锁来解决因性能低下导致的读写冲突问题而提出的解决方案。2.实现方法MVCC的实现是通过保存数据在某个时间点的快照来实现的。每个事务读取的数据项是一个历史快照,称为快照读取。与当前读不同的是,快照读读到的数据不一定是最新的,但是快照隔离可以让整个事务中看到的数据保持一致。是数据开始时的状态。写入操作不会覆盖现有数据项,而是创建一个新版本,该版本在事务提交之前不可见。MySQLInnoDB下的currentread和snapshotread什么是currentread和snapshotread?共享模式下的selectlock(共享锁)、selectforupdate等当前读操作;update、insert、delete(排他锁)都是当前读,为什么叫当前读呢?也就是说,它读取最新版本的记录。读取时,必须保证其他并发事务不能修改当前记录,它会锁定读取的记录。像无锁select操作这样的快照读是快照读,即无锁非阻塞读;快照读的前提是隔离级别不是未提交读和序列化级别,因为未提交读总是读取最新的数据行,而不是与当前事务版本匹配的数据行。序列化将锁定所有读取的行。3.优缺点MVCC使得大部分的读操作不需要加锁。这种设计使得读取数据操作非常简单,性能不错,也可以保证只读取符合条件的A行。缺点是每一行都需要额外的存储、更多的行检查和一些额外的维护。适用场景1.悲观锁用于解决读写冲突和写写冲突。加锁并发控制适用于写多读少,写冲突严重的情况,因为悲观锁是在读数据的时候加锁的,读很多的场景会需要频繁加锁,等待时间很多,而悲观锁可以写冲突严重时保证数据一致性重复读,第一种更新丢失,第二种更新丢失。2、乐观锁解决写写冲突。无锁并发控制适用于多读少写,因为如果发生大量的写操作,写冲突的可能性会增加,业务层需要不断重试,会大大降低系统性能。数据一致性要求不高,但是响应速度非常高。不能解决脏读、幻读、不可重复读,但是可以解决更新丢失的问题。3.MVCC解决读写冲突的无锁并发控制结合以上两者,提高他们的读性能可以解决脏读,幻读,不可重复读等事务问题,除了丢失更新的问题