上一篇我们完成了Redis五大核心数据结构的收官学习,借助ZSet实现了排行榜、热度排序等动态排序场景,掌握了List、Hash、Set、ZSet各自的业务适配逻辑。而Redis最核心、最普及的用途,依旧是数据库查询缓存——面对高并发查询、慢SQL场景,缓存能大幅降低数据库压力,把接口响应从百毫秒压缩至毫秒级。
很多新手刚接触Redis缓存,容易陷入“存错数据、清不掉缓存、数据不一致”的误区。本篇聚焦最实用的入门场景:缓存数据库查询结果,从缓存原理、流程设计、代码实战到注意事项,手把手带你落地稳定的缓存逻辑,实现性能与稳定性的双赢。
核心定位:利用Redis内存高速读写的特性,缓存数据库中查询频繁、改动较少的结果,拦截大部分重复查询请求,缓解数据库IO压力,提升系统响应速度。
一、为什么要用Redis缓存数据库查询?
在传统Web应用中,用户查询数据的流程是:请求→应用服务→数据库查询→返回结果。当并发量升高或存在慢SQL时,数据库很容易成为性能瓶颈,而Redis缓存恰好能解决这些痛点:
- 性能差距悬殊:MySQL磁盘查询耗时百毫秒级,Redis内存查询耗时亚毫秒级,速度提升100倍以上
- 降低数据库负载:重复查询直接走缓存,减少数据库连接和查询次数,避免数据库宕机
- 提升用户体验:接口响应更快,减少页面加载等待时间,降低卡顿概率
- 适配高并发场景:支撑秒杀、首页访问、热点数据查询等高并发流量冲击
缓存前提:优先缓存查询频繁、修改较少、实时性要求不高的数据,比如文章详情、用户基础信息、商品基础信息、字典配置等;实时性极强的数据(如库存、余额)不建议盲目缓存。
二、缓存核心模式:旁路缓存(Cache Aside)
企业级开发中,缓存数据库查询99%都采用旁路缓存模式,逻辑简单、稳定性高、易落地,分为读缓存和写缓存两个流程,是新手入门的首选方案。
1. 读数据流程(查询缓存)
- 接收查询请求,先拼接规范的Redis Key
- 查询Redis缓存,若缓存存在,直接返回结果(缓存命中)
- 若缓存不存在(缓存未命中),查询数据库获取结果
- 将数据库结果存入Redis,并设置合理过期时间TTL
- 返回查询结果给用户
2. 写数据流程(更新/删除)
- 先更新/删除数据库中的数据(保证数据库数据最新)
- 立即删除对应的Redis缓存(不建议直接更新缓存,避免数据不一致)
- 下次查询时,重新从数据库加载最新数据并缓存
三、实战步骤:缓存数据库查询结果
第一步:制定缓存Key规范
缓存Key是缓存的唯一标识,必须规范命名,避免冲突、便于维护,严禁用简单数字或无意义字符串作为Key。
通用命名格式:业务模块:数据类型:唯一标识
- 用户信息缓存:
user:info:1001(1001为用户ID) - 文章详情缓存:
article:detail:2024(2024为文章ID) - 商品信息缓存:
goods:info:6688(6688为商品ID)
第二步:读缓存代码实战(SpringBoot+RedisTemplate)
以查询文章详情为例,演示完整的缓存查询逻辑,适配MySQL+Redis架构,新手可直接复用。
/**
* 文章服务:查询文章详情(带Redis缓存)
*/
@Service
public class ArticleService {
@Resource
private ArticleMapper articleMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
// 缓存Key前缀
private static final String ARTICLE_CACHE_KEY = "article:detail:";
// 缓存过期时间:2小时(承接前文TTL知识)
private static final long CACHE_EXPIRE = 7200;
/**
* 根据ID查询文章详情(缓存优先)
* @param articleId 文章ID
* @return 文章详情
*/
public Article getArticleById(Long articleId) {
// 1. 拼接完整缓存Key
String cacheKey = ARTICLE_CACHE_KEY + articleId;
// 2. 先查询Redis缓存
Article cacheArticle = (Article) redisTemplate.opsForValue().get(cacheKey);
// 缓存命中,直接返回
if (cacheArticle != null) {
return cacheArticle;
}
// 3. 缓存未命中,查询数据库
Article dbArticle = articleMapper.selectById(articleId);
// 数据库无数据,直接返回(避免缓存穿透,后文详解)
if (dbArticle == null) {
return null;
}
// 4. 将数据库结果存入Redis,设置过期时间
redisTemplate.opsForValue().set(cacheKey, dbArticle, CACHE_EXPIRE, TimeUnit.SECONDS);
// 5. 返回结果
return dbArticle;
}
}
第三步:写缓存(更新/删除)代码实战
数据更新后,必须清理缓存,否则会出现缓存数据与数据库不一致的问题,严格遵循“先更新库,后删缓存”原则。
/**
* 更新文章信息(清理缓存)
*/
public boolean updateArticle(Article article) {
// 1. 先更新数据库,保证数据最新
int rows = articleMapper.updateById(article);
if (rows <= 0) {
return false;
}
// 2. 立即删除对应的Redis缓存
String cacheKey = ARTICLE_CACHE_KEY + article.getArticleId();
redisTemplate.delete(cacheKey);
return true;
}
/**
* 删除文章(清理缓存)
*/
public boolean deleteArticle(Long articleId) {
// 1. 先删除数据库数据
int rows = articleMapper.deleteById(articleId);
if (rows <= 0) {
return false;
}
// 2. 删除缓存
String cacheKey = ARTICLE_CACHE_KEY + articleId;
redisTemplate.delete(cacheKey);
return true;
}
四、缓存数据库查询的核心注意事项
1. 必须设置缓存过期时间(TTL)
严禁缓存永久有效,否则冷数据会持续占用Redis内存,导致内存溢出。根据数据变动频率设置TTL:
- 静态数据(文章、配置):1~24小时
- 热点数据(首页、热门商品):5~30分钟
- 临时数据:1~5分钟
2. 规避缓存数据不一致
- 严格执行:先更新数据库,再删除缓存,禁止先删缓存再更库(避免并发查询脏数据)
- 不建议直接更新缓存,更新逻辑复杂且容易出错
- 核心数据可加分布式锁,避免并发修改导致缓存错乱
3. 解决缓存穿透问题
缓存穿透指查询数据库不存在的数据,请求会直接打到数据库,恶意请求会压垮数据库。
入门解决方案:缓存空值,即使数据库无数据,也将空结果存入Redis,设置短过期时间。
// 优化后:处理缓存穿透
if (dbArticle == null) {
// 缓存空值,过期时间设为1分钟
redisTemplate.opsForValue().set(cacheKey, null, 60, TimeUnit.SECONDS);
return null;
}
4. 避免缓存雪崩
缓存雪崩指大量缓存同一时间过期,所有请求瞬间打到数据库。
入门解决方案:给过期时间加随机偏移量,打散批量过期时间。
// 基础过期时间+随机300秒内偏移,避免同时过期
long randomExpire = CACHE_EXPIRE + new Random().nextInt(300);
redisTemplate.opsForValue().set(cacheKey, dbArticle, randomExpire, TimeUnit.SECONDS);
5. 慎用缓存的场景
- 实时性要求极高的数据(如库存、账户余额、订单状态)
- 频繁修改、写入远大于读取的数据
- 数据量极小、查询频率极低的数据(缓存收益远低于维护成本)
五、入门实践总结
Redis缓存数据库查询,是后端性能优化的第一步,核心逻辑可浓缩为三句话:
- 读数据:先查缓存,缓存未命中再查数据库,结果回塞缓存并设TTL
- 写数据:先更数据库,再删缓存,保证数据最终一致
- 避坑核心:设TTL、防穿透、打散过期时间
按照本篇流程落地,既能快速实现接口性能提升,又能规避新手常见缓存问题,后续可在此基础上深入学习缓存击穿、分布式缓存等进阶知识。
最终效果:优化后,重复查询接口响应速度提升5~10倍,数据库查询次数减少80%以上,高并发场景稳定性大幅增强。