数据写到缓存无非两种情况。
- 应用读缓存没有命中时,会去读db然后再将读取的数据写到缓存。
- 应用更新db中数据后,再更新缓存。
所以更新缓存的方式可以分为以下几种:
1、先删缓存再更新db
对同一个key并发读写缓存可能导致缓存脏数据,一直到下一次数据更新或者缓存失效。
更新 | 查询 |
---|---|
删除缓存 | |
读缓存(空) | |
读db(a=1) | |
更新db(a=100) | |
写缓存 |
如上图,缓存a=1,但是数据库a=100。
2、先更新db再删缓存
查询缓存,没有命中,从db读数据后写入缓存,也可能导致缓存脏数据。
更新 | 查询 |
---|---|
读缓存(空) | |
读db(a=1) | |
更新db(a=100) | |
删缓存 | |
写缓存 |
如上图,缓存a=1,但是db中a=100,这种方式也会出现缓存脏数据,但是被广泛使用,因为这种缓存更新方式出现脏数据的条件为:
- 读缓存没有命中。
- 读缓存时同时更新key。
- 对同一条记录写db比读db先完成。
- 读db比写db先开始,后结束。
一般情况下db的写比读要慢。所以上述四个条件同时发生的概率比较低。
3、先更新db再更新缓存
这种情况也会出现缓存脏数据的情况。
当有两个更新操作并发执行时。下图为可能情况之一。
更新 操作1 | 更新 操作2 |
---|---|
写db(a=1) | |
写db(a=100) | |
更新缓存(a=100) | |
更新db(a=1) |
这种方式的优势在于主动准备好缓存的数据,可以避免缓存穿透。
4、先更新db,需要时才同步更新缓存(read/write though)
和第三种方式的不同之处在于,更新数据时,当数据被写入db后,缓存为空不主动更新缓存。
当对同一个key读/写缓存并发执行时,下图为可能情况之一。
更新 | 查询 |
---|---|
读缓存(空) | |
读db(a=100) | |
写db(a=1) | |
缓存为空不主动更新缓存 | |
更新缓存(a=100) |
此时缓存的数据为脏数据。
5、write back
更新数据时只写入缓存,后台线程负责将数据写入db。类似于linux os的page cache,需要记录那些被更新过,那些需要持久化,实现较为复杂。
小结
类似数据库的事务隔离,缓存的更新和数据库的更新很难用事务的方法将两个操作合并为一个原子操作,所以理论上无法避免在缓存更新时出现缓存脏数据的情况,但是先更新db再删缓存的cache aside模式出现缓存脏数据的概率比较低。
参考
https://coolshell.cn/articles/17416.html