数据库读写一致性问题

事务并发可能出现的问题

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

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

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

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

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

1.封锁(加锁)

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

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做任何修改。

3.封锁协议

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

一级封锁协议图
(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释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。

基于Zookeeper实现分布式锁

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

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值