背景介绍
对于软件行业来说,后端数据库是最容易成为系统性能瓶颈的地方,特别是很多读请求非常耗时。所以我们可以将一些数据放到缓存中,每次读取的时候先从缓存系统读,如果缓存中存在就直接返回数据,如果缓存中不存在再从数据库中进行读取。这样就不用每次都从数据库中读取数据,从而提升系统性能
应用场景
- 读多写少
- 实时性要求不高
缓存方案对比
上文介绍了缓存可以提升系统读性能,那么什么时候去将数据放入缓存呢?是更新缓存还是删除缓存?什么时候更新/删除缓存?
下面用一个下单业务场景的例子来进行分析。
先更新缓存再更新数据库
存在缓存与数据库不一致问题,导致脏读
| 序号 | 线程1 | 线程2 |
|---|---|---|
| 1 | 读取缓存中库存数量,返回结果为100 | |
| 2 | 扣减缓存中的库存数量为99 | |
| 3 | 更新数据库失败,返回下单失败 | |
| 4 | 从缓存中读取库存数据99 |
先更新数据库再更新缓存
存在缓存与数据库不一致问题,导致脏读
| 序号 | 线程1 | 线程2 |
|---|---|---|
| 1 | 读取缓存中的库存数量,返回结果为100 | |
| 2 | 扣减数据库中的库存数量为99 | |
| 3 | 更新缓存中的库存数量为99 | |
| 4 | 更新数据库失败,事务回滚 | |
| 5 | 从缓存中读取库存数据99 |
先删除缓存再更新数据库
存在缓存与数据库不一致问题,导致脏读。读取的时候先从缓存读取,如果缓存中的数据为空,则从数据库中读取,再将数据放入缓存
| 序号 | 线程1 | 线程2 |
|---|---|---|
| 1 | 读取缓存中的库存数量,返回结果为100 | |
| 2 | 删除缓存 | |
| 3 | 从缓存中读取库存数量,返回结果为空 | |
| 4 | 从数据库中读取库存信息,返回结果100 | |
| 5 | 将库存数量100放入缓存中 | |
| 6 | 修改数据库中的缓存数量为99 |
先更新数据库再删除缓存
存在缓存与数据库不一致问题,导致脏读。读取的时候先从缓存读取,如果缓存中的数据为空,则从数据库中读取,再将数据放入缓存
| 序号 | 线程1 | 线程2 |
|---|---|---|
| 1 | 读取缓存中的库存数量,返回结果为100 | |
| 2 | 修改数据库中的缓存数量为99 | |
| 3 | 删除缓存失败 | |
| 4 | 从缓存中读取库存数量,返回结果为100 |
| 序号 | 线程1 | 线程2 |
|---|---|---|
| 1 | 读取缓存中的库存数量,返回结果为100 | |
| 2 | 修改数据库中的缓存数量为99 | |
| 3 | 删除缓存,但此时事务还没有提交 | |
| 4 | 从缓存中读取数据结果为空 | |
| 5 | 从数据库中读取库存数量为100 | |
| 6 | 提交事务成功 | |
| 7 | 将库存数量100存入缓存 |
Cache Aside Pattern
对比了上述4种缓存方案我们会发现其实任何一种方案都存在缓存数据与数据库不一致的问题。但是对于先更新数据库再删除缓存的方案发生数据不一致问题的概率最小,所以一般通用的缓存方案都会采用这种方式,更新db+delete缓存

