** | 做者 肖泽凡,腾讯TEG研发管理部小小后台攻城狮一枚,负责腾讯敏捷产品研发平台TAPD的基础功能的开发和维护,热爱技术,喜欢分享,文章首次发表于SegmentFault,博客名“X先生”,欢迎与我交流**算法
在数据库的实际使用过程当中,咱们经常会遇到不但愿数据被同时写或者读的情景,例如秒杀场景下,两个请求同时读到系统还有库存1个,而后又前后把库存更新为0,这时候就会出现超卖的状况,这时候货物的实际库存和咱们的记录就会对应不上了。数据库
为了解决这种资源竞争致使的数据不一致等问题,咱们须要有一种机制来进行保证数据的正确访问和修改,而在数据库中,这种机制就是数据库的并发控制。其中乐观并发控制,悲观并发控制和多版本并发控制是数据库并发控制主要采用的技术手段。编程
1、本质安全
维基百科:在关系数据库管理系统里,悲观并发控制(又名“悲观锁”,PessimisticConcurrency Control,缩写“PCC”)是一种并发控制的方法。它能够阻止一个事务以影响其余用户的方式来修改数据。若是一个事务执行的操做读某行数据应用了锁,那只有当这个事务把锁释放,其余事务才可以执行与该锁冲突的操做。多线程
事实上咱们常说的悲观锁并非一种实际的锁,而是一种并发控制的思想,悲观并发控制对于数据被修改持悲观的态度,认为数据被外界访问时,必然会产生冲突,因此在数据处理的过程当中都采用加锁的方式来保证对资源的独占。并发
数据库的锁机制其实都是基于悲观并发控制的观点进行实现的,并且按照实际使用状况,数据库的锁又能够分为许多种类,具体能够见我后面的文章。性能
2、实现方式atom
数据库悲观锁的加锁流程大体以下:线程
开始事务后,按照操做类型给须要加锁的数据申请加某一类锁:例如共享行锁等设计
加锁成功则继续后面的操做,若是数据已经被加了其余的锁,并且和如今要加的锁冲突,则会加锁失败(例如已经加了排他锁),此时需等待其余的锁释放(可能出现死锁)
完成事务后释放所加的锁
3、优缺点
优势:
悲观并发控制采起的是保守策略:“先取锁,成功了才访问数据”,这保证了数据获取和修改都是有序进行的,所以适合在写多读少的环境中使用。固然使用悲观锁没法维持很是高的性能,可是在乐观锁也没法提供更好的性能前提下,悲观锁却能够作到保证数据的安全性。
缺点:
因为须要加锁,并且可能面临锁冲突甚至死锁的问题,悲观并发控制增长了系统的额外开销,下降了系统的效率,同时也会下降了系统的并行性。
1、本质
维基百科:在关系数据库管理系统里,乐观并发控制(又名“乐观锁”,OptimisticConcurrency Control,缩写“OCC”)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此互相影响,各事务可以在不产生锁的状况下处理各自影响的那部分数据。
乐观并发控制对数据修改持乐观态度,认为即便在并发环境中,外界对数据的操做通常是不会形成冲突,因此并不会去加锁,而是在提交数据更新以前,每一个事务会先检查在该事务读取数据后,有没有其余事务又修改了该数据。若是其余事务有更新的话,则让返回冲突信息,让用户决定如何去作下一步,好比说重试或者回滚。
能够看出,乐观锁其实也不是实际的锁,甚至没有用到锁来实现并发控制,而是采起其余方式来判断可否修改数据。乐观锁通常是用户本身实现的一种锁机制,虽然没有用到实际的锁,可是能产生加锁的效果。
2、实现方式
CAS(比较与交换,Compare and swap) 是一种有名的无锁算法。无锁编程,即不使用锁的状况下实现多线程之间的变量同步,也就是在没有线程被阻塞的状况下实现变量的同步,因此也叫非阻塞同步(Non-blocking Synchronization)。实现非阻塞同步的方案称为“无锁编程算法”( Non-blocking algorithm)。
乐观锁基本都是基于 CAS(Compare and swap)算法来实现的。咱们先来看下CAS过程,一个CAS操做的过程能够用如下c代码表示:
intcas(long*addr,longold,longnew) { /* Executes atomically. */ if(*addr!= old) return0; *addr= new; return1; }
CAS有3个操做数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改成B,不然什么都不作。整个CAS操做是一个原子操做,是不可分割的。
乐观锁的实现就相似于上面的过程,主要有如下几种方式:
版本号标记:在表中新增一个字段:version,用于保存版本号。获取数据的时候同时获取版本号,而后更新数据的时候用如下命令:updatexxx set version=version+1,… where … version="old version" and ....。这时候经过判断返回结果的影响行数是否为0来判断是否更新成功,更新失败则说明有其余请求已经更新了数据了。
时间戳标记:和版本号同样,只是经过时间戳来判断。通常来讲不少数据表都会有更新时间这一个字段,经过这个字段来判断就不用再新增一个字段了。
待更新字段:若是没有时间戳字段,并且不想新增字段,那能够考虑用待更新字段来判断,由于更新数据通常都会发生变化,那更新前能够拿要更新的字段的旧值和数据库的现值进行比对,没有变化则更新。
全部字段标记:数据表全部字段都用来判断。这种至关于就、不只仅对某几个字段作加锁了,而是对整个数据行加锁,只要本行数据发生变化,就不进行更新。
3、优缺点
优势:
乐观并发控制没有实际加锁,因此没有额外开销,也不错出现死锁问题,适用于读多写少的并发场景,由于没有额外开销,因此能极大提升数据库的性能。
缺点:
乐观并发控制不适合于写多读少的并发场景下,由于会出现不少的写冲突,致使数据写入要屡次等待重试,在这种状况下,其开销其实是比悲观锁更高的。并且乐观锁的业务逻辑比悲观锁要更为复杂,业务逻辑上要考虑到失败,等待重试的状况,并且也没法避免其余第三方系统对数据库的直接修改的状况。
1、本质
维基百科: 多版本并发控制(Multiversion concurrency control, MCC 或 MVCC),是数据库管理系统经常使用的一种并发控制,也用于程序设计语言实现事务内存。
乐观并发控制和悲观并发控制都是经过延迟或者终止相应的事务来解决事务之间的竞争条件来保证事务的可串行化;虽然前面的两种并发控制机制确实可以从根本上解决并发事务的可串行化的问题,可是其实都是在解决写冲突的问题,二者区别在于对写冲突的乐观程度不一样(悲观锁也能解决读写冲突问题,可是性能就通常了)。而在实际使用过程当中,数据库读请求是写请求的不少倍,咱们若是能解决读写并发的问题的话,就能更大地提升数据库的读性能,而这就是多版本并发控制所能作到的事情。
与悲观并发控制和乐观并发控制不一样的是,MVCC是为了解决读写锁形成的多个、长时间的读操做饿死写操做问题,也就是解决读写冲突的问题。MVCC 能够与前二者中的任意一种机制结合使用,以提升数据库的读性能。
数据库的悲观锁基于提高并发性能的考虑,通常都同时实现了多版本并发控制。不只是MySQL,包括Oracle、PostgreSQL等其余数据库系统也都实现了MVCC,但各自的实现机制不尽相同,由于MVCC没有一个统一的实现标准。
总的来讲,MVCC的出现就是数据库不满用悲观锁去解决读-写冲突问题,因性能不高而提出的解决方案。
2、实现方式
MVCC的实现,是经过保存数据在某个时间点的快照来实现的。每一个事务读到的数据项都是一个历史快照,被称为快照读,不一样于当前读的是快照读读到的数据可能不是最新的,可是快照隔离能使得在整个事务看到的数据都是它启动时的数据状态。而写操做不覆盖已有数据项,而是建立一个新的版本,直至所在事务提交时才变为可见。
当前读和快照读
什么是MySQL InnoDB下的当前读和快照读?
当前读
像select lock in share mode(共享锁),select for update ; update, insert ,delete(排他锁)这些操做都是一种当前读,为何叫当前读?就是它读取的是记录的最新版本,读取时还要保证其余并发事务不能修改当前记录,会对读取的记录进行加锁。
快照读
像不加锁的select操做就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是未提交读和串行化级别,由于未提交读老是读取最新的数据行,而不是符合当前事务版本的数据行。而串行化则会对全部读取的行都加锁
3、优缺点
MVCC 使大多数读操做均可以不用加锁,这样设计使得读数据操做很简单,性能很好,而且也能保证只会读取到符合标准的行。不足之处是每行记录都须要额外的存储空间,须要作更多的行检查工做,以及一些额外的维护工做。
1、悲观锁
用来解决读-写冲突和写-写冲突的的加锁并发控制
适用于写多读少,写冲突严重的状况,由于悲观锁是在读取数据的时候就加锁的,读多的场景会须要频繁的加锁和不少的的等待时间,而在写冲突严重的状况下使用悲观锁能够保证数据的一致性
数据一致性要求高
能够解决脏读,幻读,不可重复读,第一类更新丢失,第二类更新丢失的问题
2、乐观锁
解决写-写冲突的无锁并发控制
适用于读多写少,由于若是出现大量的写操做,写冲突的可能性就会增大,业务层须要不断重试,这会大大下降系统性能
数据一致性要求不高,但要求很是高的响应速度
没法解决脏读,幻读,不可重复读,可是能够解决更新丢失问题
3、MVCC
解决读-写冲突的无锁并发控制
与上面二者结合,提高它们的读性能
能够解决脏读,幻读,不可重复读等事务问题,更新丢失问题除外
本文由博客一文多发平台 OpenWrite 发布!