数据库读写一致性问题

事务并发可能出现的问题

(1)脏读:事务A读取了事务B更新的数据,而后事务B由于某些缘由回滚,那么事务A读取的就是脏数据
(2)不可能重复读:事务A屡次读取同一数据,事务B在事务A屡次读取的过程当中,对数据作了更新并提交,致使事务A屡次读取的结果不一致
(3)幻读:事务A查出一批数据,进行操做,这时事务B新增一条数据,事务A提交以后,发现还有一条数据没有操做,发生幻觉。
备注:不可能重复读偏向修改,幻读偏向新增,解决不可重复读使用行级锁,解决幻读 表锁node

不一样隔离级别在事务并发下致使的结果(mysql为例)

事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

数据库经过并发控制保证读写一致性

并发控制的主要技术锁,时间戳,乐观控制法和多版本控制,商用数据库通常都是经过封锁来实现事务并发控制mysql

1.封锁(加锁)

事务T在对某个数据对象(表,记录)操做以前,先向系统发出请求,对其加锁,加锁后事务T就对该数据对象有了必定事务控制权,在事务T释放它以前,其余事务不能更新此数据对象,属于悲观控制法web

2.锁类型

(1)排他锁(X锁):排它锁又称写锁X。若事务T对数据对象A加上X锁,只有事务T能够读A也能够修改A,其余事务不能再对A加任何锁,直到T释放A上的锁。这保证了其余事务在T释放A上的锁以前不能再读取和修改A。
(2)共享锁(S锁):若事务T对数据对象A加上S锁,则事务T能够读A但不能修改A,其余事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其余事务能够读A,但在T释放A上的S锁以前不能对A作任何修改。redis

3.封锁协议

(1)一级封锁协议(Read uncommited):事务T在修改数据R以前必须先对其加X锁,直到事务结束才释放。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。在一级封锁协议中,若是仅仅是读数据不对其进行修改,是不须要加锁的。因此它不能保证可重复读和不 读"脏"数据。sql

一级封锁协议图
(2)二级封锁协议(Read Committed):
一级封锁协议加上事务T在读取数据R以前必须先对其加S锁,读完后便可释放S锁。二级封锁协议除防止了丢失修改和读"脏"数据,不能保证可重复读数据库

二级封锁协议图(3)三级封锁协议(Repeatable Read):
一级封锁协议加上事务T在读取数据R以前必须先对其加S锁,直到事务结束才释放。三级封锁协议除防止了丢失修改和不读’脏’数据外,还进一步防止了不可重复读。并发

分布式系统中并发控制方案

1.数据库锁

(1)建立一张锁表,须要对某个资源加锁,则往表里添加一条记录,释放锁则删除这条记录
缺点:数据库负载压力增大,一旦数据库挂掉,可能致使整个服务不可用
一旦解锁失败,可能致使死锁
(2)基于数据库的排他锁
for update 对查询数据集加锁
缺点:依赖数据库的事务控制,资源开销大分布式

基于redis的分布式锁

a. 基于 REDIS 的 SETNX()、EXPIRE() 方法作分布式锁
备注:有死锁的可能,setnx 执行成功后,在 expire() 命令执行成功前,发生了宕机的现象,那么就依然会出现死锁的问题
b.基于 REDIS 的 SETNX()、GET()、GETSET()方法作分布式锁
假设key原来是不存在的,那么屡次执行这个命令,会出现下边的效果:
[1]. getset(key, “value1”) 返回nil 此时key的值会被设置为value1
[2]. getset(key, “value2”) 返回value1 此时key的值会被设置为value2
[3]. 依次类推。
c.具体用法
[1]. setnx(lockkey, 当前时间+过时超时时间) ,若是返回1,则获取锁成功;若是返回0则没有获取到锁,转向2。
[2]. get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,若是小于当前系统时间,则认为这个锁已经超时,能够容许别的请求从新获取,转向3。
[3]. 计算newExpireTime=当前时间+过时超时时间,而后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。
[4]. 判断currentExpireTime与oldExpireTime 是否相等,若是相等,说明当前getset设置成功,获取到了锁。若是不相等,说明这个锁又被别的请求获取走了,那么当前请求能够直接返回失败,或者继续重试。
[5]. 在获取到锁以后,当前线程能够开始本身的业务处理,当处理完毕后,比较本身的处理时间和对于锁设置的超时时间,若是小于锁设置的超时时间,则直接执行delete释放锁;若是大于锁设置的超时时间,则不须要再锁进行处理。svg

基于Zookeeper实现分布式锁

在zookeeper指定节点(locks)下建立临时顺序节点node_n
获取locks下全部子节点children
对子节点按节点自增序号从小到大排序
判断本节点是否是第一个子节点,如果,则获取锁;若不是,则监听比该节点小的那个节点的删除事件
若监听事件生效,则回到第二步从新进行判断,直到获取到锁线程