数据库事务隔离引起的关于锁机制的思考

DB提供两种机制来保证事务的ACID(原子性,一致性,隔离性和持久性)特性,日志预写(write-ahead loging)和锁(lock),前者用于保证原子性、一致性,后者用于保证隔离性。
事务在没有提交前的一系列修改都不能持久化,所以这一系列的操做都是依赖两种log来实现,redo-log和undo-log;修改前的数据由undo-log记录,修改后的数据由redo-log记录,事务提交成功则执行redo-log,失败则执行undo-log。node

DB提供的事务隔离级别以下:
#1 未提交读,READ UNCOMMITTED
执行读操做时不加任何锁,执行写操做时添加行级共享锁,直到事务结束。多个事务能够并行读取某数据,可是一旦某个事务执行了写操做后,其余事务仍旧能够读数据,但须要等待锁释放后才能够执行写操做 。
问题:不能解决脏读,一个事务可能读取到另一个事务最终没有提交的数据。sql

#2 已提交读,READ COMMITTED
执行读操做时添加行级共享锁,读完以后就释放锁;执行写操做时添加行级排他锁,直到事务结束。多个事务能够并行读取某数据,可是一旦某个事务执行了写操做后,其余事务就再也不容许进行任何读写操做,所以能够解决当前事务读取的数据都是最终提交的。
问题:不能解决可重复读,另外一个事务数据更新操做可能让当前事务连续两次读取的数据不一致。数据库

#3 可重复读,REPEATED READ
执行读操做时添加行级共享锁,直到事务结束;执行写操做时添加行级排他锁,直到事务结束。多个事务能够并行读取某数据,但事务仅当数据上没有任何锁的时候才能添加排他锁进行写操做,直到当前事务结束后,其余事务才能尝试添加拍他所进行写操做。所以一旦某数据被读取以后,其余事务不能对其进行修改。
问题:不能解决幻读,另外一个事务的新数据插入操做可能让当前事务连续两次读取的数据不一致。缓存

#4 序列化读,SERIALIZABLE
执行读操做时添加表级共享锁,直到事务结束;执行写操做时添加表级排他锁,直到事务结束。所以涉及到表内某行数据的读写都串行发生。
问题:全部事务都串行发生,访问性能极大下降。session

Racing Process中关于锁的机制架构

就用途而言,锁包含悲观锁、乐观锁、独占锁、共享锁、(非)公平锁、分布式锁和自旋锁。并发

独占锁(Exclusive Lock):
也叫作写锁(X锁,排它锁)资源只容许当前事务读写,其余事务的任何操做都不容许,添加以后一直到事务结束时才释放;当资源上已经有其余锁的时候没法添加独占锁;插入、更新和删除操做自动添加独占锁。mvc

 

共享锁(Shared Lock):分布式

也叫作读锁(S锁)资源容许当前事务读写,容许其余事务的读操做,不容许其余事务的写操做,读操做结束后共享锁就当即释放;查询操做自动添加共享锁。性能

 

悲观锁(Pessimistic Lock):
事务认为数据处理过程当中都须要锁定数据,防止其余racing process的修改,通常都是依靠数据库锁机制实现,包含表锁、行锁等,一旦触发确定会引发线程的阻塞等待

 

乐观锁(Optimistic Lock):
事务认为数据处理过程当中不须要锁定数据;通常经过数据库表的版本列实现,为数据库表添加一列version,读取数据的时候同时读取version数值;以后写入的时候对version + 1并与当前DB中的version数值比较,若是大于数据库表当前的版本号则进行更新,不然认为是过时数据而不进行更新,其实也就是多版本并发控制(Multi-Version Cuoncurrency Controll)。
在Mysql的Innodb引擎中,数据库表会额外添加一列version字段,仅在事务级别为未提交读和已提交读的时候会开启mvcc,在保证数据一致性的前提下提供最大程度的并发。每个racing process在读取数据的时候会在原始数据的基础上生成一条新的临时数据,同时version +1,这条数据在事务提交前对其余事务都是不可见的;一旦事务结束提交的时候会对比临时数据跟原始数据的version版本号,若是临时数据的version小于原始数据的version,则说明临时数据已通过期,放弃或者从新读取数据执行当前的操做。

 

分布式锁(Distributed Lock):
同一个进程内的多个线程之间能够共享内存,所以基于共享内存的synchnorized或者lock机制均可以实现锁,对于进程之间或者多台server之间基于共享内存方式实现的锁就再也不能知足要求。分布式环境的锁须要提供一个全部server均可以访问到的存储介质(DB或者内存),最原始的分布式锁设计源于DB,多个业务系统之间的同步经过向同一张DB table中插入一条具备惟一性键值的数据,成功插入数据的业务系统得到锁,处理完本身的业务数据以后将DB table中的数据删除,其余业务系统就能够继续插入数据从而获取锁;能够经过timer task解决死锁问题,能够在table中增长业务系统标识column解决锁重入的问题;在并发量不是特别大,而且能保证DB系统足够稳定的前提下,DB based的分布式锁能够解决分布式锁的问题。

随着并发业务量的上升和访问延迟要求的提升,缓存锁逐渐替代了数据库锁,以Redis RedLock和ZooKeeper Mutex为表明的基于内存数据操做架构能够保证多业务系统对同一项操做的并发执行,最终只会有一个业务系统操做成功,或者有一个前后顺序(竞争排队);RedLock的核心利用的是Redis的命令set NX EX,NX属性表示仅当某个key不存在的时候才能成功set这个key,这样racing process同时对同一个key执行setnx,最终只有一个process能够设置成功,也表示获取锁成功;EX表示key值的过时时间,防止死锁的状况。

分布式锁主要用于处理在分布式环境中多个racing process对同一个资源进行访问时候的数据一致性问题,所以分布式锁须要知足五个特性:
#1 保证同一个方法在在同一时间点只能被一台机器上的一个线程执行:

#2 保证锁能够正常的释放:避免因加锁的racing process服务下线形成锁不能释放的场景;DB based实现能够经过timer task删除过时的record,Redis based实现能够经过setnx上设置key过时时间自动删除键值,ZooKeeper based实现经过断定session connection断开自动删除建立的节点。

#3 保证锁的实现为阻塞锁:racing process须要等待获取锁以后才继续往下执行业务;DB based实现和Redis based实现均可以经过while(true)循环实现等待(自旋锁),但不够优雅,ZooKeeper based实现经过event-watcher机制自动通知racing process锁已经获取。

#4 保证锁是可重入的:已经得到了锁的racing process能够访问被加了同一把锁的其余资源,避免自身死锁;DB based实现可在record上增长一列lock holder info来辨识当前获取锁的racing process,Redis based实现可将value设置成lock holder info来辨识,ZooKeeper based实现能够在建立的node上记录lock holder info。

#5 保证锁的获取和释放服务具备高可用性能:避免单点故障问题,DB,Redis和ZooKeeper均可以集群的方式提供分布式锁服务。

 

自旋锁(Self-Spin Lock):racing process经过互斥同步实现访问一致性的时候,挂起和恢复线程都须要转到系统内核模式完成,频繁的切换会给并发性能带来压力;同时,大多数共享数据的锁定状态都只须要很短的时间,业务的开销甚至比系统切换开销还低,因此racing process能够暂时不用切换状态,而只须要执行一段时间的忙循环(也就是继续占用CPU时间片),从而等待共享数据的锁被释放,这样的策略就是自旋锁。

相关文章
相关标签/搜索