软删除后需同步清理Redis缓存并加拦截机制防止悬挂穿透:先用UNLINK异步删除缓存,再通过数据访问层检查逻辑删除状态,避免缓存缺失时查询已软删数据。

软删除后 Redis 缓存没清,请求直接穿透到 DB
这是典型的“悬挂穿透”:数据库里某条记录被标记为 is_deleted=1 或迁入归档表,但 Redis 里还留着旧缓存,后续查询仍命中缓存并返回已逻辑删除的数据;更糟的是,当缓存过期或被删后,所有请求会同时涌向数据库查这个“已下线”的 key,造成穿透压力。
别只依赖 DEL,先确认缓存是否真该被删
软删除 ≠ 数据彻底消失,它常伴随下游依赖(如关联统计、审计日志、导出快照),所以不能一删了之。关键判断点:
- 该 key 是否在业务语义上已“不可见”?比如用户中心的
user:1001被软删后,前端不再展示、API 不再返回,那就该清 - 是否存在“读已删除数据”的合法场景?比如后台管理页需查看历史记录,此时应保留缓存,但加字段标识(如
"deleted_at": "2026-10-18T15:22:00Z")而非直接删 - 缓存是否带二级索引?比如
user:email:alice@example.com→1001,这个映射也得同步清理,否则仍能通过邮箱反查出软删用户
用 UNLINK 替代 DEL,避免阻塞主线程
软删除往往批量触发(如下线一个服务模块、清退一批账号),若用 DEL 清大量 key,Redis 会同步释放内存,可能卡住几秒甚至更久,导致请求超时。生产环境必须改用:
UNLINK user:1001 user:1002 user:1003
它把键从字典中摘除后,交由后台线程异步回收内存,主进程不阻塞。注意两点:
-
UNLINK是 Redis 4.0+ 才支持,低于此版本只能DEL+ 控制单次数量(建议 ≤ 100 个/批) - 若需清理匹配模式的 key(如所有
user:*:profile),不能直接UNLINK,得先SCAN出来再分批UNLINK,避免KEYS阻塞
加一层“软删拦截”,堵住穿透最后一道口
即使缓存清了,仍有小概率因网络延迟、重试或并发导致请求在清理窗口期内打进来——这时不能让它直连 DB 查“已删数据”。推荐在数据访问层加轻量拦截:
伪代码逻辑:
if (redis.get("user:1001") == null) {
// 缓存未命中,先查数据库逻辑删除状态
dbRow = db.select("SELECT id, deleted_at FROM users WHERE id = 1001");
if (dbRow != null && dbRow.deleted_at != null) {
// 明确已软删,直接返回 404 或空对象,且不写回缓存
return null;
}
// 否则走正常重建流程
redis.set("user:1001", dbRow, 3600);
}
这个检查成本极低(单条主键查询),却能彻底切断穿透链路。比布隆过滤器更准,比互斥锁更轻量。
真正容易被忽略的是:软删除操作本身要和缓存清理形成原子性信号。比如用消息队列通知清理,得确保消息不丢、不重复;若走本地事务,至少保证 DB 更新成功后再发 UNLINK,不能反过来。


















