数据库、缓存与事务的一些学习
缓存同步的两个主流策略
在不同的请求并行的向缓存请求和更新数据时,因为一些诡异的时序安排,很容易引起某一个请求读到脏数据的情况
这种方法只适合读多写少的情况,保持缓存在大多数时间都是可以取得到值的。其他情况下经常会出现缓存不一致问题,该方法还是需要慎用。
Delete策略分为两种,主要的区别在于淘汰缓存的时机:
查询时,和set策略一致,先查缓存,不命中查数据库,并将值写到缓存
第一种:
更新时,先淘汰缓存,再更新数据库
这种情况下,会有A淘汰缓存后,B缓存不命中,从数据库中将值更新到缓存,A再更新数据库的情况。这种情况下,有一种叫延时淘汰机制,在更新情况下,可以设计一个异步操作,休眠1s再次淘汰缓存,但如果第二次淘汰发生错误了,依旧还是会产生不一致的问题。
第二种:
更新时,先更新数据库,再淘汰缓存
这种策略解决了delete策略第一种提到的问题,但也有新的问题,比如A查询没命中缓存去查数据库,读到一个旧值,此时B将新值写入数据库,然后淘汰缓存,此时A再将读到的值更新到缓存,同样产生了不一致。但此时的情况比上面的情况好很多,因为出现的概率会小很多,A的查询数据库时间是远小于B的写数据库的时间。
但是,如果一定要解决这个问题,同样可以参考delete策略中的第一种里面的延时删除策略,每次发起更新时,更新数据库并且淘汰缓存后,添加一个异步的消息队列,在一定的延时后再次淘汰缓存即可解决问题。
值得一提的是,现在比较公认的更新策略最后一种,来自于Cache-Aside pattern,并且有如下缓存数据库同步策略的定义:
- 失效:从cache读取,没命中,读数据库,成功后将值放入缓存。
- 命中:从cache读取,命中并返回。
- 更新:把要更新的数据先存到数据库,成功后让缓存失效。
缓存的使用事项
一个写多读少的业务流程,需要经常更新数据库并且淘汰缓存,在做查询的时候,大多数时候都是缓存未命中,导致大部分请求全部打到数据库上。
一批缓存使用了相同的过期时间,导致某一个时刻,一大批缓存过期失效,请求全部转发到数据库,导致数据库压力徒增。
MySQL事务
事务
原子性、一致性、隔离性、持久性
数据库并发的问题
丢失更新:
第一类丢失更新:在没有事务隔离的情况下,AB同时操作某个值,同时读取,B做出修改并且提交更改,A发生异常回滚数据,导致B已完成的数据更新丢失。
第二类丢失更新:同上述例子,AB同时更新某值,在B提交更新之后,A也提交更新,B提交的更新就被A提交的覆盖。
脏读:
A对某一数据进行了修改,但是该修改还未提交数据库,这时B也访问该数据,然后使用了这个还没有成功更新的新数据,读到了脏数据。
不可重复读:
AB同时操作一段数据,A在一次操作中需要多次读取该数据,但是在这期间,B修改了该值并且完成了提交,这样A下次读到的数据就不一样。
幻读
同样也是AB同时操作一段数据,但和不可重复读不同的是,B不是对某数据的值进行修改,而是新增或者删除了某数据,A在这前后读取到的数据记录数发生了变化,这要区别于不可重复读。
事务隔离级别
- READ_UNCOMMITTED 未提交读
允许其他事物读到未提交的数据
- READ_COMMITTED 提交读
保证数据得到提交后才能被另一个事务读取到
- REPEATABLE_READ 可重复读
保证一个事务在前后两次获取到的数据都是一样的
- SERIALIZABLE 顺序读
事务的处理串行化
第一类丢失更新 | 脏读 | 不可重复读 | 第二类丢失更新 | 幻读 | |
---|---|---|---|---|---|
未提交读 | √ | ||||
提交读 | √ | √ | |||
可重复读 | √ | √ | √ | √ | |
串行化 | √ | √ | √ | √ | √ |
MySQL采用的默认隔离级别是可重复读级别
REF
- https://www.cnblogs.com/johnsblog/p/6426287.html
- https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=404308725&idx=1&sn=1a25ce76dd1956014ceb8a011855268e&scene=21#wechat_redirect
- https://docs.microsoft.com/en-us/azure/architecture/patterns/cache-aside
- https://blog.csdn.net/d8111/article/details/2595635