<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Redis on Coding with Denglei</title>
    <link>https://blog.denglei.me/tags/redis/</link>
    <description>Recent content in Redis on Coding with Denglei</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Thu, 25 Jun 2026 23:06:22 +0800</lastBuildDate>
    <atom:link href="https://blog.denglei.me/tags/redis/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Redis：延迟双删的适用边界与落地细节</title>
      <link>https://blog.denglei.me/posts/redis-cache-pattern-delayed-double-delete/</link>
      <pubDate>Tue, 16 Jun 2026 17:04:23 +0800</pubDate>
      <guid>https://blog.denglei.me/posts/redis-cache-pattern-delayed-double-delete/</guid>
      <description>&lt;p&gt;延迟双删不是新概念，但线上一出缓存脏读，我曾经在项目中把它当成标准答案直接套进去。结果通常是代码写了两次删除，问题却没真正收住。&lt;/p&gt;&#xA;&lt;p&gt;这篇就聚焦一个知识点：延迟双删到底解决什么问题，为什么它只能改善最终一致概率，以及在 .NET 服务里怎么把第二次删除做得更稳一点。&lt;/p&gt;&#xA;&lt;h2 id=&#34;1-问题背景数据库已经更新为什么缓存里还是旧值&#34;&gt;1. 问题背景：数据库已经更新，为什么缓存里还是旧值&lt;/h2&gt;&#xA;&lt;p&gt;聊一个高频场景：商品详情页读 Redis，后台商品编辑写数据库。读流量远大于写流量，最常见的缓存策略是 Cache Aside。&lt;/p&gt;&#xA;&lt;p&gt;我的更新代码长这样：先更新数据库，再删除缓存。平时看起来没什么问题，但高并发下还是会偶发脏数据。业务侧看到的现象一般是：管理后台已经改价成功，前台用户短时间内还能查到旧价格。&lt;/p&gt;&#xA;&lt;p&gt;关键不在“删没删缓存”，而在并发时序。&lt;/p&gt;&#xA;&lt;p&gt;一个典型过程是这样的：&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;线程 A 更新数据库中的商品价格&lt;/li&gt;&#xA;&lt;li&gt;线程 A 删除 Redis 中的商品缓存&lt;/li&gt;&#xA;&lt;li&gt;线程 B 正好在这个空档读缓存未命中，开始查数据库&lt;/li&gt;&#xA;&lt;li&gt;线程 B 读到的仍然是旧值，或者读到了事务提交前的旧快照&lt;/li&gt;&#xA;&lt;li&gt;线程 B 把旧值重新写回 Redis&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;这时候数据库是新值，缓存却又变回旧值了。问题根因不是删除动作本身，而是删除之后，旧数据又被别的请求回填进缓存。&lt;/p&gt;&#xA;&lt;p&gt;如果只看文字，这个并发窗口不算直观。把它画成时序图会更清楚：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram&#xA;    autonumber&#xA;    participant A as 写线程A&#xA;    participant R as Redis&#xA;    participant D as Database&#xA;    participant B as 读线程B&#xA;&#xA;    A-&amp;gt;&amp;gt;D: 更新商品价格为新值&#xA;    A-&amp;gt;&amp;gt;R: 删除商品缓存&#xA;    B-&amp;gt;&amp;gt;R: 读取商品缓存&#xA;    R--&amp;gt;&amp;gt;B: 未命中&#xA;    B-&amp;gt;&amp;gt;D: 查询商品数据&#xA;    D--&amp;gt;&amp;gt;B: 返回旧值或旧快照&#xA;    B-&amp;gt;&amp;gt;R: 回填旧值到缓存&#xA;    B--&amp;gt;&amp;gt;B: 后续请求命中旧缓存&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这张图里最关键的不是“删缓存”这一步，而是删完之后到下一次稳定回填新值之前，中间存在一个旧值重新进入 Redis 的窗口。延迟双删补的就是这个窗口。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
