<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>数据库 on 方糖猫Blog</title>
    <link>https://ftmao.com/blog/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/</link>
    <description>AI 视频、AI Agent 与工程实践笔记</description>
    <generator>Hugo</generator>
    <language>zh-CN</language>
    <lastBuildDate>Tue, 26 Oct 2021 10:30:00 +0800</lastBuildDate><atom:link href="https://ftmao.com/blog/categories/%E6%95%B0%E6%8D%AE%E5%BA%93/index.xml" rel="self" type="application/rss+xml" /><item>
      <title>MongoDB 从入门到精通：存储引擎、建模、索引、副本集、分片与事务实战</title>
      <link>https://ftmao.com/blog/posts/mongodb-beginner-to-mastery/</link>
      <pubDate>Tue, 26 Oct 2021 10:30:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/mongodb-beginner-to-mastery/</guid>
      <description>&lt;h1 id=&#34;mongodb-从入门到精通存储引擎建模索引副本集分片与事务实战&#34;&gt;MongoDB 从入门到精通：存储引擎、建模、索引、副本集、分片与事务实战&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;目标读者：用过 MySQL 之类的关系型数据库，现在要接 MongoDB 的同学——可能是接手存量系统，可能是新业务选型，也可能是想给自己的技术栈补上 NoSQL 这一块。&lt;br&gt;
本文不浪费篇幅在「什么是文档」这种名词解释上，直接从真正影响系统生死的东西讲起：存储引擎、一致性、高可用、性能。&lt;br&gt;
写这篇的契机：2021 年我所在团队的电商中台把部分业务从 MySQL 迁到 MongoDB，整个过程脱了一层皮。这篇是我希望迁移前有人能拍在桌上的笔记。&lt;br&gt;
文内版本基线：MongoDB 5.0（2021 年 7 月发布）、官方 Java 同步驱动 4.3、JDK 11。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;全文八篇，按真实的成长路径编排：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;一 选型认知&amp;lt;br/&amp;gt;Mongo 适合什么&amp;#34;] --&amp;gt; B[&amp;#34;二 存储引擎&amp;lt;br/&amp;gt;WiredTiger 底牌&amp;#34;]
    B --&amp;gt; C[&amp;#34;三 数据建模&amp;lt;br/&amp;gt;核心战场&amp;#34;]
    C --&amp;gt; D[&amp;#34;四 索引与 explain&amp;lt;br/&amp;gt;调优基本功&amp;#34;]
    D --&amp;gt; E[&amp;#34;五 聚合管道&amp;lt;br/&amp;gt;服务端计算&amp;#34;]
    E --&amp;gt; F[&amp;#34;六 副本集&amp;lt;br/&amp;gt;高可用与读写语义&amp;#34;]
    F --&amp;gt; G[&amp;#34;七 事务与分片&amp;lt;br/&amp;gt;规模化与一致性&amp;#34;]
    G --&amp;gt; H[&amp;#34;八 运维与 AI 时代&amp;lt;br/&amp;gt;监控备份 少写代码&amp;#34;]
    style H fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;学习 MongoDB 要始终围绕三个问题：&lt;strong&gt;数据安不安全、查询快不快、流量扛不扛得住&lt;/strong&gt;。这篇的每一节，都是对这三问的某一次正面回答。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第一篇-选型认知什么时候该用-mongodb什么时候别逞强&#34;&gt;第一篇 选型认知：什么时候该用 MongoDB，什么时候别逞强&lt;/h2&gt;
&lt;p&gt;第一课不是怎么用，是&lt;strong&gt;什么时候用&lt;/strong&gt;。用错场景的 MongoDB，比不用更可怕。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph 适合[&amp;#34;MongoDB 的主场&amp;#34;]
        P[&amp;#34;商品中心&amp;lt;br/&amp;gt;类目属性千差万别&amp;lt;br/&amp;gt;文档模型天然契合&amp;#34;]
        C[&amp;#34;购物车/会话&amp;lt;br/&amp;gt;读多写多 结构灵活&amp;#34;]
        L[&amp;#34;行为日志/埋点&amp;lt;br/&amp;gt;写入量大 schema 常变&amp;lt;br/&amp;gt;TTL 索引自动过期&amp;#34;]
        CM[&amp;#34;CMS/配置中心&amp;lt;br/&amp;gt;页面配置 JSON 化&amp;#34;]
    end
    subgraph 谨慎[&amp;#34;需要精心设计的边界&amp;#34;]
        O[&amp;#34;订单/库存&amp;lt;br/&amp;gt;可用 但要事务+对账设计&amp;#34;]
        M[&amp;#34;消息/Feed 流&amp;lt;br/&amp;gt;可用 注意分片键设计&amp;#34;]
    end
    subgraph 别上[&amp;#34;别硬上 Mongo&amp;#34;]
        F[&amp;#34;账务/清结算&amp;lt;br/&amp;gt;强事务 强审计&amp;lt;br/&amp;gt;交给关系型&amp;#34;]
        S[&amp;#34;复杂搜索&amp;lt;br/&amp;gt;交给 Elasticsearch&amp;#34;]
        J[&amp;#34;重度多表关联分析&amp;lt;br/&amp;gt;交给 OLAP/数仓&amp;#34;]
    end
    style 适合 fill:#e8f4e8
    style 别上 fill:#fde8e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;一句话原则：&lt;strong&gt;MongoDB 适合「单实体自成一体、结构多变、读多」的在线业务；不适合「钱」「搜索」「重度关联分析」。&lt;/strong&gt; 判断依据不是性能 benchmark，而是数据访问模式——先把系统里 80% 的查询列出来，看它们能不能被「单集合 + 少量 $lookup」覆盖。覆盖不了，选型本身就有问题。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考与感悟之一&lt;/strong&gt;：数据库选型和框架选型完全不同。框架选错了可以重构，数据库选错了是「数据搬家」级别的灾难——停机迁移、双写对账、灰度切流，每一步都在钢丝上走。所以对「不做什么」的清单，要比「做什么」的清单更长。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第二篇-wiredtiger理解存储引擎才配谈性能&#34;&gt;第二篇 WiredTiger：理解存储引擎，才配谈性能&lt;/h2&gt;
&lt;p&gt;很多人用 MongoDB 三年，不知道底下是 WiredTiger。这就像开了一辈子车不懂发动机——平时没事，出事时只能干瞪眼。这一篇先把「数据到底怎么存的」讲透，再回答那个面试必问的问题：&lt;strong&gt;MongoDB 为什么快，为什么扛得住大量读写。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;21-底层数据模型bson磁盘上到底存了什么&#34;&gt;2.1 底层数据模型：BSON，磁盘上到底存了什么&lt;/h3&gt;
&lt;p&gt;你在应用层操作的是「文档」（可以理解成 JSON），但磁盘上存的是 &lt;strong&gt;BSON（Binary JSON）&lt;/strong&gt;——一种二进制、自描述、长度前缀的编码格式：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph BSON[&amp;#34;一个 BSON 文档的字节布局&amp;#34;]
        L[&amp;#34;int32 总长度&amp;lt;br/&amp;gt;开头 4 字节&amp;#34;] --&amp;gt; E1[&amp;#34;元素1&amp;lt;br/&amp;gt;类型字节 + 字段名 + 值&amp;#34;]
        E1 --&amp;gt; E2[&amp;#34;元素2&amp;lt;br/&amp;gt;类型字节 + 字段名 + 值&amp;#34;]
        E2 --&amp;gt; EN[&amp;#34;...&amp;#34;]
        EN --&amp;gt; Z[&amp;#34;0x00 结束符&amp;#34;]
    end
    subgraph TYPES[&amp;#34;BSON 比 JSON 多出的类型&amp;#34;]
        T1[&amp;#34;ObjectId / Date / 正则&amp;#34;]
        T2[&amp;#34;int32 / int64 / double&amp;#34;]
        T3[&amp;#34;Decimal128 高精度十进制&amp;#34;]
        T4[&amp;#34;BinData 二进制 / 数组 / 内嵌文档&amp;#34;]
    end
    BSON -.-&amp;gt; TYPES&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;BSON 的设计直接回答了两个性能问题：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;长度前缀 + 类型字节 → 遍历时可以「跳过」字段。&lt;/strong&gt; 查询只取 &lt;code&gt;name&lt;/code&gt; 字段时，引擎不需要解析整个文档，读到字段长度直接指针后移——这就是为什么投影（projection）能省时间。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;类型丰富 → 不需要序列化转换。&lt;/strong&gt; JSON 只有 string/number/boolean，日期只能存字符串再解析；BSON 原生支持 Date、ObjectId、Decimal128（钱的精确计算就靠它）、二进制，省去了应用层和存储层之间大量的类型转换开销。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;更关键的是&lt;strong&gt;文档在磁盘上是连续存放的&lt;/strong&gt;：一个商品文档（含内嵌的 SKU 数组、图片列表）在磁盘上是&lt;strong&gt;一整块连续的字节&lt;/strong&gt;。这和关系型数据库形成鲜明对比——&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph MYSQL[&amp;#34;MySQL 读一个完整商品&amp;#34;]
        A1[&amp;#34;product 表的 B+ 树页&amp;#34;] --&amp;gt; J[&amp;#34;JOIN&amp;#34;]
        A2[&amp;#34;sku 表的 B+ 树页&amp;lt;br/&amp;gt;磁盘另一处&amp;#34;] --&amp;gt; J
        A3[&amp;#34;image 表的 B+ 树页&amp;lt;br/&amp;gt;磁盘又一处&amp;#34;] --&amp;gt; J
        J --&amp;gt; R[&amp;#34;多次随机 I/O&amp;lt;br/&amp;gt;才能拼出一个商品&amp;#34;]
    end
    subgraph MONGO[&amp;#34;MongoDB 读一个完整商品&amp;#34;]
        B[&amp;#34;一次磁盘寻道&amp;lt;br/&amp;gt;读出连续的一整块 BSON&amp;#34;]
    end
    style MONGO fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;这就是 MongoDB 设计哲学里最重要的词：&lt;strong&gt;数据局部性（data locality）&lt;/strong&gt;。「一起读的数据，就存在一起」。嵌入建模之所以是 Mongo 的灵魂，底层原因就在这里——它不是风格偏好，是磁盘 I/O 决定的。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;追问：文档是定长连续存放的，那修改、变大之后怎么办？磁盘会不连续吗？&lt;/strong&gt; 这要分两个时代说。MMAPv1 时代（3.0 之前）文档存在定长槽位里，变大就得整体搬家（document move），旧槽位变空洞，磁盘碎片是当年最臭名昭著的问题。WiredTiger 时代换了个思路：&lt;strong&gt;存储单位是 B 树的页而不是文档&lt;/strong&gt;，文档是叶页里的一条 record；修改&lt;strong&gt;从不在原地写&lt;/strong&gt;——checkpoint 时用 copy-on-write 把改后的整页写到磁盘&lt;strong&gt;新位置&lt;/strong&gt;，旧块进空闲列表复用；文档长大导致页放不下就触发&lt;strong&gt;页分裂&lt;/strong&gt;，超大文档走 overflow 页。所以「连续」的准确含义是「逻辑上一条 record、一次 I/O 读取」，物理位置本来就会搬。代价有两个：长期高频更新会积累碎片（用 &lt;code&gt;db.collection.compact()&lt;/code&gt; 定期回收），以及&lt;strong&gt;频繁长大的文档会引发频繁页分裂&lt;/strong&gt;——这是第三篇「增长边界」判断的底层原因，时序类数据的标准解法是按时间桶预分配文档的 bucket 模式，让单文档尺寸保持稳定。&lt;/p&gt;
&lt;h3 id=&#34;22-为什么快读快与写快的底层原因&#34;&gt;2.2 为什么快：读快与写快的底层原因&lt;/h3&gt;
&lt;p&gt;「MongoDB 快」不能当口号喊，要能拆成具体机制。先写后读。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;写入为什么快——一条写请求的完整路径：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    APP[&amp;#34;应用&amp;lt;br/&amp;gt;Java 驱动&amp;#34;] --&amp;gt; M[&amp;#34;mongod&amp;lt;br/&amp;gt;内存操作&amp;#34;]
    M --&amp;gt; C[&amp;#34;WiredTiger cache&amp;lt;br/&amp;gt;修改页标记为 dirty&amp;lt;br/&amp;gt;写操作到此即完成&amp;#34;]
    M --&amp;gt; J[&amp;#34;journal 日志&amp;lt;br/&amp;gt;顺序追加写&amp;lt;br/&amp;gt;磁盘顺序 I/O 极快&amp;#34;]
    C -.-&amp;gt;|&amp;#34;checkpoint&amp;lt;br/&amp;gt;默认每 60 秒&amp;lt;br/&amp;gt;批量刷盘&amp;#34;| D[&amp;#34;数据文件&amp;lt;br/&amp;gt;压缩后的 B 树页&amp;#34;]
    style C fill:#e8f4e8&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;四个叠加的机制：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;写内存即返回&lt;/strong&gt;。写操作修改的是 cache 里的页，标记 dirty 就完事，数据文件由 checkpoint 线程&lt;strong&gt;每 60 秒批量刷一次&lt;/strong&gt;——随机写被攒成了顺序批量写。这是「写快」的第一来源。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;journal 用顺序写保证持久性&lt;/strong&gt;。怕宕机丢内存里的数据？journal（WAL 日志）是&lt;strong&gt;顺序追加写&lt;/strong&gt;，磁盘的顺序写比随机写快几个数量级。&lt;code&gt;j: true&lt;/code&gt; 的写确认就是等 journal 落盘，而不是等数据文件落盘。&lt;/p&gt;
&lt;p&gt;这里必须正面回答一个所有人都会问的问题：&lt;strong&gt;「写入内存就返回，服务器挂了怎么办？」&lt;/strong&gt; 不会变成脏数据——cache 里丢掉的只是 dirty 页的内存副本，每次修改都已先追加进 journal；mongod 重启时以最后一次 checkpoint 为基线&lt;strong&gt;重放 journal&lt;/strong&gt;，把丢失的写补回数据文件。真正的风险窗口在确认时机：默认 &lt;code&gt;w:1&lt;/code&gt; 下 journal 每 ~100ms 才批量提交，断电最多丢「最近约 100ms 且未同步到从节点」的写（第六篇丢的 23 笔订单就是这么没的）；&lt;code&gt;j: true&lt;/code&gt; 等日志落盘，单节点宕机不丢；&lt;code&gt;w: majority&lt;/code&gt; 等多数派确认，机器物理损毁也不丢。&lt;strong&gt;丢不丢数据不是 MongoDB 决定的，是你选 writeConcern 档位时决定的&lt;/strong&gt;——这套「内存脏页异步刷盘 + 日志重放」和 MySQL InnoDB 的 redo log 是同一套思想。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;文档级并发&lt;/strong&gt;。更新只锁单个文档，100 个线程写 100 个不同文档完全不互斥。对比早期 MySQL MyISAM 的表锁、乃至行锁时代的间隙锁开销，文档锁的冲突概率天然更低。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;没有跨行约束的包袱&lt;/strong&gt;。无外键检查、无触发器、单文档原子性不需要 undo log（改一个文档就是改一整块，要么全改要么不改），写入路径短。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;读取为什么快——四道防线：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;局部性&lt;/strong&gt;：如上，嵌入建模让「读一个完整实体」= 一次磁盘寻道，没有 JOIN 的多次随机 I/O。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存&lt;/strong&gt;：热数据常驻 WiredTiger cache（默认一半内存），命中内存的读在微秒级。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;B 树索引&lt;/strong&gt;：索引项 O(log n) 定位，千万级数据也就 4 层左右，加覆盖索引（第四篇）连文档都不用读。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;副本集读扩展&lt;/strong&gt;：&lt;code&gt;readPreference: secondary&lt;/code&gt; 把展示类读流量分摊到从节点，读吞吐随节点数线性扩展。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;大量读 + 大量写同时扛住的原因&lt;/strong&gt;：读写分离（从节点扛读）+ MVCC（读不阻塞写、写不阻塞读）+ 写内存化。这三件事同时成立，才撑得起电商大促「几十万读 QPS 的商品页 + 几万写 QPS 的库存扣减」同框的场景。还不够？分片把写入水平切到多个副本集，写吞吐同样线性扩展（第七篇）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;快的代价（必须说清楚，否则就是忽悠）&lt;/strong&gt;：写内存即返回意味着默认 &lt;code&gt;w:1&lt;/code&gt; 下宕机可能丢最后一小段数据（第六篇的丢单事故就是这么来的）；热数据依赖内存，内存小于工作集时性能断崖；没有 JOIN 意味着关联查询要自己扛。&lt;strong&gt;MongoDB 的快是「用设计取舍换来的快」，不是魔法。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;23-wiredtiger-你必须知道的四件事&#34;&gt;2.3 WiredTiger 你必须知道的四件事&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    W[&amp;#34;WiredTiger 存储引擎&amp;lt;br/&amp;gt;MongoDB 3.2+ 默认&amp;#34;] --&amp;gt; A[&amp;#34;文档级并发控制&amp;lt;br/&amp;gt;多线程写同一集合不互斥&amp;lt;br/&amp;gt;只有同一文档冲突才重试&amp;#34;]
    W --&amp;gt; B[&amp;#34;压缩存储&amp;lt;br/&amp;gt;snappy 默认&amp;lt;br/&amp;gt;磁盘占用约为数据量的 30%-70%&amp;#34;]
    W --&amp;gt; C[&amp;#34;缓存机制&amp;lt;br/&amp;gt;cacheSizeGB 默认 = 50% × (内存 - 1GB)&amp;lt;br/&amp;gt;热数据全在内存里&amp;#34;]
    W --&amp;gt; D[&amp;#34;MVCC 多版本&amp;lt;br/&amp;gt;读不阻塞写 写不阻塞读&amp;lt;br/&amp;gt;snapshot 隔离&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;要记的结论：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;文档级锁&lt;/strong&gt;：并发写同一个集合是免费的，但&lt;strong&gt;并发改同一个文档会串行重试&lt;/strong&gt;。这直接决定建模——秒杀库存如果把 100 个 SKU 的库存都嵌在一个商品文档里，大促时这个文档就是全站最热的锁热点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缓存即命&lt;/strong&gt;：WiredTiger cache 默认吃掉一半内存。生产上「Mongo 内存打满」往往不是泄漏，是 cache 在正常工作——看 &lt;code&gt;wiredTiger.cache&lt;/code&gt; 指标而不是看进程 RSS 下结论。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;压缩是把双刃剑&lt;/strong&gt;：省磁盘，费 CPU。写密集型日志库可换 zlib 更高压缩比，CPU 紧张时别乱调。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MVCC 的代价&lt;/strong&gt;：老版本数据要清理，长时间不提交的事务会压住清理线程，导致 cache 中 dirty data 堆积、写入被拖慢。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #1（cache 被大事务压垮）&lt;/strong&gt;：我们有个定时任务一次性更新 200 万条商品，跑的时候整个库延迟飙升。原因：巨型事务导致 WiredTiger 无法清理旧版本，cache 中 dirty bytes 超过阈值后应用线程被迫参与淘汰（eviction），写入集体变慢。解法：&lt;strong&gt;批量操作必须拆批&lt;/strong&gt;（每批 1000 条 + 短暂让出窗口），生产环境永远不开「不设限的大批量更新」。这条后来成了团队《MongoDB 使用红线》第一条。&lt;/p&gt;
&lt;h3 id=&#34;24-本地环境直接上副本集别玩单机&#34;&gt;2.4 本地环境：直接上副本集，别玩单机&lt;/h3&gt;
&lt;p&gt;单节点 MongoDB 不支持事务，也不具备生产形态。本地练手就用 Docker Compose 起一套三节点副本集：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# docker-compose.yml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;version&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;3.8&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;services&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;mongo1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;mongo:5.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;command&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--replSet&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rs0&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--bind_ip_all&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;ports&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;27017:27017&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;mongo2&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;mongo:5.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;command&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--replSet&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rs0&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--bind_ip_all&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;mongo3&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;image&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;mongo:5.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;command&lt;/span&gt;: [&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--replSet&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rs0&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;--bind_ip_all&amp;#34;&lt;/span&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 初始化副本集（执行一次）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mongosh --eval &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;rs.initiate({
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  _id: &amp;#34;rs0&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  members: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    { _id: 0, host: &amp;#34;mongo1:27017&amp;#34; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    { _id: 1, host: &amp;#34;mongo2:27017&amp;#34; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    { _id: 2, host: &amp;#34;mongo3:27017&amp;#34; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;})&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;第三篇-数据建模核心战场没有之一&#34;&gt;第三篇 数据建模：核心战场，没有之一&lt;/h2&gt;
&lt;p&gt;MongoDB 精通与否，就看建模。SQL 建模有范式护体，Mongo 建模全靠经验与取舍——这也是 AI 最帮不上忙的部分（第八篇详谈）。&lt;/p&gt;
&lt;h3 id=&#34;31-唯一重要的问题嵌入还是引用&#34;&gt;3.1 唯一重要的问题：嵌入还是引用&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    Q[&amp;#34;一份数据，嵌入父文档&amp;lt;br/&amp;gt;还是独立成集合？&amp;#34;] --&amp;gt; A{&amp;#34;三个判断&amp;#34;}
    A --&amp;gt; B[&amp;#34;读写边界&amp;lt;br/&amp;gt;总是一起读写吗？&amp;#34;]
    A --&amp;gt; C[&amp;#34;增长边界&amp;lt;br/&amp;gt;数量有上限吗？&amp;#34;]
    A --&amp;gt; D[&amp;#34;并发边界&amp;lt;br/&amp;gt;会被高并发独立更新吗？&amp;#34;]
    B --&amp;gt;|&amp;#34;是，且&amp;#34;| E[&amp;#34;嵌入&amp;lt;br/&amp;gt;如：订单内嵌 items 快照&amp;#34;]
    C --&amp;gt;|&amp;#34;否，无限增长&amp;#34;| R[&amp;#34;引用&amp;lt;br/&amp;gt;如：商品引用评论&amp;#34;]
    D --&amp;gt;|&amp;#34;是，热点&amp;#34;| R2[&amp;#34;引用&amp;lt;br/&amp;gt;如：SKU 库存独立成文档&amp;lt;br/&amp;gt;避免单文档成锁热点&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三个判断，每个都对应一个坑：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;读写边界&lt;/strong&gt;：订单的 &lt;code&gt;items&lt;/code&gt; 嵌入（下单时一次写入、之后整体读取）；但「收货地址」独立成集合——地址在下单、物流、售后多个流程被独立读写，嵌在用户文档里每次都要加载整个用户。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;增长边界&lt;/strong&gt;：单文档 16MB 硬上限。商品的「问大家」嵌进商品文档，爆款商品一年撑爆，连夜迁移。教训：&lt;strong&gt;文档不是仓库，是货架。&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;并发边界&lt;/strong&gt;：文档级锁决定了——&lt;strong&gt;凡是会被高并发写的数据，从父文档里拆出去&lt;/strong&gt;。秒杀库存必须一个 SKU 一个文档，绝不能嵌在商品里。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;32-电商核心建模订单快照与库存拆分&#34;&gt;3.2 电商核心建模：订单快照与库存拆分&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;erDiagram
    USER ||--o{ ORDER : &amp;#34;引用&amp;#34;
    PRODUCT ||--o{ SKU_STOCK : &amp;#34;拆出 独立高并发写&amp;#34;
    ORDER ||--|{ ORDER_ITEM : &amp;#34;内嵌 商品快照&amp;#34;
    PRODUCT ||--o{ COMMENT : &amp;#34;引用 无界增长&amp;#34;
    PRODUCT {
        ObjectId _id
        string name
        string categoryId
        array attrs &amp;#34;类目属性 灵活嵌入&amp;#34;
        string detailUrl &amp;#34;富文本放对象存储&amp;#34;
    }
    SKU_STOCK {
        ObjectId _id
        ObjectId productId
        string spec
        int stock &amp;#34;独立文档 原子扣减&amp;#34;
        int version &amp;#34;乐观锁&amp;#34;
    }
    ORDER {
        ObjectId _id
        ObjectId userId
        array items &amp;#34;名称/单价/图片快照&amp;#34;
        long totalAmount &amp;#34;单位: 分&amp;#34;
        string status
        date createdAt
    }&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;两个关键设计，每个都值一次线上事故的钱：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;订单必须存商品快照&lt;/strong&gt;。下单那一刻把商品名、成交单价、主图冗余进 &lt;code&gt;items&lt;/code&gt;。商品会改名改价下架，三个月后客服处理售后，看的必须是「成交时」的样子。快照冗余不是脏数据，是业务真相。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;金额用「分」，long 存&lt;/strong&gt;。浮点的 &lt;code&gt;199.00&lt;/code&gt; 元在 IEEE 754 世界里是 &lt;code&gt;198.999...&lt;/code&gt;，所有和钱有关的字段一律整数存分。这不是 Mongo 的问题，但 Mongo 的自由度会让人更容易犯懒。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考与感悟之二&lt;/strong&gt;：SQL 建模回答「数据之间是什么关系」，Mongo 建模回答「数据以什么节奏被读写」。前者是静态的学问，后者是动态的学问——必须预测流量形状（谁是热点、谁会暴涨、谁一起出现）才能建好模。这就是为什么 Mongo 建模更依赖业务理解，也更难被自动化。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第四篇-索引与-explain调优基本功没有捷径&#34;&gt;第四篇 索引与 explain：调优基本功，没有捷径&lt;/h2&gt;
&lt;h3 id=&#34;41-explain-是唯一标准答案&#34;&gt;4.1 explain 是唯一标准答案&lt;/h3&gt;
&lt;p&gt;任何「这条查询为什么慢」的讨论，不贴 explain 输出就是耍流氓。Java 里看执行计划：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 在 Java 驱动里用 explain 需要走 Document 命令（或先在 mongosh 里验证）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// mongosh 中的写法：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;db.&lt;span style=&#34;color:#a6e22e&#34;&gt;products&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;find&lt;/span&gt;({ categoryId: cid, status: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;on&amp;#34;&lt;/span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  .&lt;span style=&#34;color:#a6e22e&#34;&gt;sort&lt;/span&gt;({ price: &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;1 })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  .&lt;span style=&#34;color:#a6e22e&#34;&gt;explain&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;executionStats&amp;#34;&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;输出只盯四处：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;winningPlan.stage&amp;#34;] --&amp;gt; B{&amp;#34;COLLSCAN?&amp;#34;}
    B --&amp;gt;|&amp;#34;是&amp;#34;| C[&amp;#34;全表扫描&amp;lt;br/&amp;gt;立刻建索引&amp;#34;]
    B --&amp;gt;|&amp;#34;IXSCAN&amp;#34;| D[&amp;#34;看三个数字&amp;#34;]
    D --&amp;gt; E[&amp;#34;nReturned&amp;lt;br/&amp;gt;返回多少条&amp;#34;]
    D --&amp;gt; F[&amp;#34;totalDocsExamined&amp;lt;br/&amp;gt;读了多少文档&amp;#34;]
    D --&amp;gt; G[&amp;#34;totalKeysExamined&amp;lt;br/&amp;gt;扫了多少索引键&amp;#34;]
    E --&amp;gt; H{&amp;#34;理想: 三者接近&amp;lt;br/&amp;gt;危险: Examined 远大于 Returned&amp;#34;}&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;42-esr-法则与-java-建索引&#34;&gt;4.2 ESR 法则与 Java 建索引&lt;/h3&gt;
&lt;p&gt;复合索引字段顺序：&lt;strong&gt;Equality（等值）→ Sort（排序）→ Range（范围）&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;商品列表页查询：&lt;code&gt;{ categoryId: 等值, status: 等值 }&lt;/code&gt; + 按价格倒序，用 Java 驱动建索引：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 依赖: org.mongodb:mongodb-driver-sync:4.3.x&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoClients;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoCollection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Indexes;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.Document;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;IndexDemo&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; (MongoClient client &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MongoClients.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mongodb://localhost:27017/?replicaSet=rs0&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            MongoCollection&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; products &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client.&lt;span style=&#34;color:#a6e22e&#34;&gt;getDatabase&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shop&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;products&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// ESR：categoryId/status 等值(E) 在前，price 排序(S) 在后&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            products.&lt;span style=&#34;color:#a6e22e&#34;&gt;createIndex&lt;/span&gt;(Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;compoundIndex&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;ascending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;categoryId&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;ascending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;descending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;price&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            ));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 部分索引：只给在售商品建，索引体积砍半&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            products.&lt;span style=&#34;color:#a6e22e&#34;&gt;createIndex&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;ascending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;price&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;IndexOptions&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            .&lt;span style=&#34;color:#a6e22e&#34;&gt;partialFilterExpression&lt;/span&gt;(com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;on&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// TTL 索引：埋点日志 7 天自动删除，省掉清理脚本&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            client.&lt;span style=&#34;color:#a6e22e&#34;&gt;getDatabase&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shop&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;access_logs&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    .&lt;span style=&#34;color:#a6e22e&#34;&gt;createIndex&lt;/span&gt;(Indexes.&lt;span style=&#34;color:#a6e22e&#34;&gt;ascending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;IndexOptions&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;expireAfter&lt;/span&gt;(7L, java.&lt;span style=&#34;color:#a6e22e&#34;&gt;util&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;concurrent&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;TimeUnit&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;DAYS&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;注意：加了「价格区间筛选」后 &lt;code&gt;price&lt;/code&gt; 就从 Sort 变成 Range，一个索引满足不了两个需求——&lt;strong&gt;索引设计永远是针对具体查询集合的权衡，没有万能索引&lt;/strong&gt;。每个索引都拖慢写入，写多读少的日志库尤其敏感。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #2（32MB 排序内存墙）&lt;/strong&gt;：运营后台「全量商品按上架时间倒序」接口，商品过 10 万后直接报 &lt;code&gt;Sort exceeded memory limit of 33554432 bytes&lt;/code&gt;。Mongo 内存排序上限 32MB，超了就炸。解法不是调参数，是建索引让排序走索引（索引天然有序）。&lt;strong&gt;上线红线第二条：凡带 sort 的查询，必须 explain 验证走索引排序。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #3（正则索引进退两难）&lt;/strong&gt;：模糊搜索 &lt;code&gt;find({ name: /卫衣/ })&lt;/code&gt;，非前缀正则无法走索引，数据量上来后 P99 三秒起步。前缀匹配 &lt;code&gt;/^卫衣/&lt;/code&gt; 可以走索引，但中文搜索很少是前缀形态。2021 年的正经答案：&lt;strong&gt;Mongo 管数据，ES 管搜索&lt;/strong&gt;，用 oplog/canal 同步，别指望一个库包打天下。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第五篇-聚合管道把计算下推给服务端&#34;&gt;第五篇 聚合管道：把计算下推给服务端&lt;/h2&gt;
&lt;p&gt;核心思维：「能在库里算完的，绝不拉回应用层」。拉 10 万条数据到 JVM 里 for 循环求和，是最常见的性能自杀。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;$match&amp;lt;br/&amp;gt;筛: 本月已支付&amp;#34;] --&amp;gt; B[&amp;#34;$unwind&amp;lt;br/&amp;gt;拆: items 展开&amp;#34;]
    B --&amp;gt; C[&amp;#34;$group&amp;lt;br/&amp;gt;按商品汇总&amp;#34;]
    C --&amp;gt; D[&amp;#34;$sort&amp;lt;br/&amp;gt;销量倒序&amp;#34;]
    D --&amp;gt; E[&amp;#34;$limit&amp;lt;br/&amp;gt;Top 10&amp;#34;]
    E --&amp;gt; F[&amp;#34;热销榜&amp;lt;br/&amp;gt;一次出结果&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;实战 Demo：本月热销商品 Top 10（Java 驱动写法）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// demo01-top-sales：本月热销榜&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoClients;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoCollection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Accumulators;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Aggregates;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Filters;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Sorts;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.Document;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.conversions.Bson;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; java.util.ArrayList;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; java.util.Arrays;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; java.util.Date;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; java.util.List;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;TopSalesDemo&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;static&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;main&lt;/span&gt;(String&lt;span style=&#34;color:#f92672&#34;&gt;[]&lt;/span&gt; args) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; (MongoClient client &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; MongoClients.&lt;span style=&#34;color:#a6e22e&#34;&gt;create&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mongodb://localhost:27017/?replicaSet=rs0&amp;#34;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            MongoCollection&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; orders &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client.&lt;span style=&#34;color:#a6e22e&#34;&gt;getDatabase&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shop&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;orders&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Date monthStart &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Date(); &lt;span style=&#34;color:#75715e&#34;&gt;// 实际取本月 1 号 0 点，此处从简&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            Date monthEnd &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Date();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Bson&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; pipeline &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Arrays.&lt;span style=&#34;color:#a6e22e&#34;&gt;asList&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#75715e&#34;&gt;// 第一道永远是 $match：能用索引，砍掉 99% 的数据&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Aggregates.&lt;span style=&#34;color:#a6e22e&#34;&gt;match&lt;/span&gt;(Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;and&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;status&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;paid&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;gte&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;, monthStart),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;lt&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;, monthEnd))),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Aggregates.&lt;span style=&#34;color:#a6e22e&#34;&gt;unwind&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Aggregates.&lt;span style=&#34;color:#a6e22e&#34;&gt;group&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items.productId&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Accumulators.&lt;span style=&#34;color:#a6e22e&#34;&gt;first&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;productName&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items.productName&amp;#34;&lt;/span&gt;), &lt;span style=&#34;color:#75715e&#34;&gt;// 快照红利：不用 $lookup&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Accumulators.&lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;totalQty&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items.qty&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                            Accumulators.&lt;span style=&#34;color:#a6e22e&#34;&gt;sum&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;totalAmount&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                    &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; Document(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$multiply&amp;#34;&lt;/span&gt;, Arrays.&lt;span style=&#34;color:#a6e22e&#34;&gt;asList&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items.qty&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;$items.price&amp;#34;&lt;/span&gt;)))),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Aggregates.&lt;span style=&#34;color:#a6e22e&#34;&gt;sort&lt;/span&gt;(Sorts.&lt;span style=&#34;color:#a6e22e&#34;&gt;descending&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;totalQty&amp;#34;&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    Aggregates.&lt;span style=&#34;color:#a6e22e&#34;&gt;limit&lt;/span&gt;(10)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; hotList &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; ArrayList&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            orders.&lt;span style=&#34;color:#a6e22e&#34;&gt;aggregate&lt;/span&gt;(pipeline)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    .&lt;span style=&#34;color:#a6e22e&#34;&gt;allowDiskUse&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;)   &lt;span style=&#34;color:#75715e&#34;&gt;// 中间结果超 100MB 允许落盘，报表查询记得开&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    .&lt;span style=&#34;color:#a6e22e&#34;&gt;into&lt;/span&gt;(hotList);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            hotList.&lt;span style=&#34;color:#a6e22e&#34;&gt;forEach&lt;/span&gt;(doc &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; System.&lt;span style=&#34;color:#a6e22e&#34;&gt;out&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;println&lt;/span&gt;(doc.&lt;span style=&#34;color:#a6e22e&#34;&gt;toJson&lt;/span&gt;()));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;三个要点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;$match&lt;/code&gt; 必须放第一&lt;/strong&gt;，放 &lt;code&gt;$unwind&lt;/code&gt; 后面等于先把 500 万订单拆成 3000 万条再筛——有人这么写过，直接把一个节点的内存打爆。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;allowDiskUse(true)&lt;/code&gt;&lt;/strong&gt;：聚合中间结果超 100MB 默认报错，报表类查询记得开。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;能不用 &lt;code&gt;$lookup&lt;/code&gt; 就不用&lt;/strong&gt;。它是应急手段，性能和多表 join 一样是噩梦。需要大量 &lt;code&gt;$lookup&lt;/code&gt; 才能完成的查询，说明建模（第三篇）出了问题——回头改模型，别在查询层硬扛。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第六篇-副本集高可用与读写语义的分水岭&#34;&gt;第六篇 副本集：高可用与读写语义的分水岭&lt;/h2&gt;
&lt;p&gt;单节点 MongoDB 是玩具，副本集才是起点。这篇是「会用 Mongo」和「敢把核心业务放 Mongo」之间的分水岭。&lt;/p&gt;
&lt;h3 id=&#34;61-架构与选主&#34;&gt;6.1 架构与选主&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    subgraph RS[&amp;#34;副本集 rs0（3 节点是最小生产配置）&amp;#34;]
        P[&amp;#34;Primary&amp;lt;br/&amp;gt;唯一可写&amp;#34;]
        S1[&amp;#34;Secondary&amp;lt;br/&amp;gt;异步复制 oplog&amp;#34;]
        S2[&amp;#34;Secondary&amp;lt;br/&amp;gt;异步复制 oplog&amp;#34;]
        P -- &amp;#34;oplog 同步&amp;#34; --&amp;gt; S1
        P -- &amp;#34;oplog 同步&amp;#34; --&amp;gt; S2
    end
    P -.-&amp;gt;|&amp;#34;心跳超时 10s&amp;lt;br/&amp;gt;自动选主&amp;#34;| S1
    APP[&amp;#34;应用（驱动）&amp;#34;] --&amp;gt;|&amp;#34;自动感知拓扑变化&amp;lt;br/&amp;gt;故障转移对代码透明&amp;#34;| RS&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;必须理解的机制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;oplog&lt;/strong&gt;：主节点的操作日志，从节点靠它追赶。oplog 是有界集合，从节点宕机太久会追不上，只能全量同步（initial sync）——大库的全量同步以小时计，这就是「从节点宕机要当事故处理」的原因。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;自动选主&lt;/strong&gt;：主挂了，10 秒左右选出新主。&lt;strong&gt;驱动自动重连新主，应用无感&lt;/strong&gt;——前提是连接串写对了（多节点 + &lt;code&gt;replicaSet=rs0&lt;/code&gt;），而不是只写了一个 IP。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;连接池&lt;/strong&gt;：&lt;code&gt;MongoClient&lt;/code&gt; 是线程安全的重对象，&lt;strong&gt;全应用一个单例&lt;/strong&gt;，连接池大小用 &lt;code&gt;maxPoolSize&lt;/code&gt; 调。每请求 new 一个 &lt;code&gt;MongoClient&lt;/code&gt; 等于每请求建一批 TCP 连接，压测时会把数据库连接数打爆——这和「每请求连一次 MySQL」是同一种自杀。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;62-读写语义writeconcern-与-readconcern&#34;&gt;6.2 读写语义：writeConcern 与 readConcern&lt;/h3&gt;
&lt;p&gt;这部分决定你能不能睡着觉——数据到底「写没写进去」？&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// demo02-write-concern：不同档位，不同的安全与延迟权衡&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.WriteConcern;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoCollection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.Document;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MongoCollection&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; orders &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client.&lt;span style=&#34;color:#a6e22e&#34;&gt;getDatabase&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shop&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;orders&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 快但可能丢：主节点内存确认就返回（埋点日志可用）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;orders.&lt;span style=&#34;color:#a6e22e&#34;&gt;withWriteConcern&lt;/span&gt;(WriteConcern.&lt;span style=&#34;color:#a6e22e&#34;&gt;W1&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;insertOne&lt;/span&gt;(logDoc);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 安全：多数派落盘 + journal 落盘才返回（订单必须这档）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;orders.&lt;span style=&#34;color:#a6e22e&#34;&gt;withWriteConcern&lt;/span&gt;(WriteConcern.&lt;span style=&#34;color:#a6e22e&#34;&gt;MAJORITY&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;withJournal&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;)).&lt;span style=&#34;color:#a6e22e&#34;&gt;insertOne&lt;/span&gt;(orderDoc);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;电商业务对号入座：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;业务&lt;/th&gt;
					&lt;th&gt;writeConcern&lt;/th&gt;
					&lt;th&gt;理由&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;订单创建&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;majority&lt;/code&gt; + journal&lt;/td&gt;
					&lt;td&gt;主从切换时绝不能丢单&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;库存扣减&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;majority&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;丢扣减记录不可忍&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;行为埋点&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;w: 1&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;丢几条日志无所谓，别拖累写入&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;购物车&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;majority&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;用户加购后刷新没了，投诉就来了&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;readConcern&lt;/code&gt; 同理：读「已提交到多数派」的数据用 &lt;code&gt;ReadConcern.MAJORITY&lt;/code&gt;，避免读到主从切换后被回滚的数据。&lt;strong&gt;钱相关的读，一律 majority；展示类的读，local 即可。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #4（丢单惊魂夜）&lt;/strong&gt;：上线初期订单写入用默认 &lt;code&gt;w: 1&lt;/code&gt;。某天主节点宕机选主，新主上没有最后 8 秒的 23 笔订单——用户付了钱（支付回调已记录），订单却不存在，客服电话被打爆。复盘结论：&lt;strong&gt;支付回调链路和订单链路的 writeConcern 必须对齐 majority&lt;/strong&gt;，宁慢勿丢。这 8 秒人工补单补了一整夜。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考与感悟之三&lt;/strong&gt;：分布式系统里「默认配置」几乎都是「性能优先、安全靠边」的，MongoDB 的 &lt;code&gt;w:1&lt;/code&gt;、MySQL 的异步复制、Kafka 的 &lt;code&gt;acks=1&lt;/code&gt; 皆如此。成熟的标志之一：&lt;strong&gt;每接一个中间件，先把默认一致性档位改成业务需要的档位，再开始写业务代码。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第七篇-事务与分片规模化之后的一致性难题&#34;&gt;第七篇 事务与分片：规模化之后的一致性难题&lt;/h2&gt;
&lt;h3 id=&#34;71-能不用事务就不用原子操作的威力&#34;&gt;7.1 能不用事务就不用：原子操作的威力&lt;/h3&gt;
&lt;p&gt;MongoDB 单文档操作天然原子。电商防超卖，一条条件更新就够，根本不需要事务：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// demo03-deduct-stock：条件原子扣减，1000 并发抢 1 件也只有 1 个成功&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoCollection;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Filters;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.model.Updates;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.result.UpdateResult;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.Document;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.types.ObjectId;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;DeductStockDemo&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;deduct&lt;/span&gt;(MongoCollection&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Document&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; skuStocks, ObjectId skuId, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; qty) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        UpdateResult r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; skuStocks.&lt;span style=&#34;color:#a6e22e&#34;&gt;updateOne&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 库存够不够，放进查询条件 —— 判断和修改合并成一次原子操作&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;and&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;_id&amp;#34;&lt;/span&gt;, skuId),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Filters.&lt;span style=&#34;color:#a6e22e&#34;&gt;gte&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;stock&amp;#34;&lt;/span&gt;, qty)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                Updates.&lt;span style=&#34;color:#a6e22e&#34;&gt;combine&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Updates.&lt;span style=&#34;color:#a6e22e&#34;&gt;inc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;stock&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;qty),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Updates.&lt;span style=&#34;color:#a6e22e&#34;&gt;inc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sold&amp;#34;&lt;/span&gt;, qty),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        Updates.&lt;span style=&#34;color:#a6e22e&#34;&gt;currentDate&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;updatedAt&amp;#34;&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        );
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (r.&lt;span style=&#34;color:#a6e22e&#34;&gt;getModifiedCount&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; 0) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; IllegalStateException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;库存不足&amp;#34;&lt;/span&gt;); &lt;span style=&#34;color:#75715e&#34;&gt;// 没抢到，上层返回售罄&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;精髓：判断（&lt;code&gt;stock &amp;gt;= qty&lt;/code&gt;）和修改（&lt;code&gt;$inc&lt;/code&gt;）合并成&lt;strong&gt;一次原子操作&lt;/strong&gt;，不存在「查出来够、扣的时候不够」的竞态。这比任何分布式锁都干净。&lt;/p&gt;
&lt;h3 id=&#34;72-真正需要事务的场景&#34;&gt;7.2 真正需要事务的场景&lt;/h3&gt;
&lt;p&gt;跨集合的一致性——下单要同时「扣库存 + 建订单 + 核销券」，三个动作三个集合，这时上多文档事务（4.0+，需副本集）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// demo04-place-order-tx：多文档事务下单&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.ClientSessionOptions;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.ReadConcern;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.TransactionOptions;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.WriteConcern;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.ClientSession;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; com.mongodb.client.MongoClient;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; org.bson.Document;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;class&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;PlaceOrderTxDemo&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;public&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;placeOrder&lt;/span&gt;(MongoClient client, Document orderDoc, ObjectId skuId, &lt;span style=&#34;color:#66d9ef&#34;&gt;int&lt;/span&gt; qty, ObjectId couponId) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        TransactionOptions txnOptions &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; TransactionOptions.&lt;span style=&#34;color:#a6e22e&#34;&gt;builder&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;readConcern&lt;/span&gt;(ReadConcern.&lt;span style=&#34;color:#a6e22e&#34;&gt;SNAPSHOT&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;writeConcern&lt;/span&gt;(WriteConcern.&lt;span style=&#34;color:#a6e22e&#34;&gt;MAJORITY&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                .&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#66d9ef&#34;&gt;try&lt;/span&gt; (ClientSession session &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client.&lt;span style=&#34;color:#a6e22e&#34;&gt;startSession&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                ClientSessionOptions.&lt;span style=&#34;color:#a6e22e&#34;&gt;builder&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;defaultTransactionOptions&lt;/span&gt;(txnOptions).&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;())) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            session.&lt;span style=&#34;color:#a6e22e&#34;&gt;withTransaction&lt;/span&gt;(() &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; db &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; client.&lt;span style=&#34;color:#a6e22e&#34;&gt;getDatabase&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;shop&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 1. 条件原子扣库存&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;var&lt;/span&gt; r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; db.&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;sku_stocks&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;updateOne&lt;/span&gt;(session,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;and&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;_id&amp;#34;&lt;/span&gt;, skuId),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;gte&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;stock&amp;#34;&lt;/span&gt;, qty)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Updates&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;inc&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;stock&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;qty));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (r.&lt;span style=&#34;color:#a6e22e&#34;&gt;getModifiedCount&lt;/span&gt;() &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; 0) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                    &lt;span style=&#34;color:#66d9ef&#34;&gt;throw&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; IllegalStateException(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;库存不足，触发回滚&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 2. 插入订单（含商品快照）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                db.&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;orders&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;insertOne&lt;/span&gt;(session, orderDoc);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#75715e&#34;&gt;// 3. 核销优惠券&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                db.&lt;span style=&#34;color:#a6e22e&#34;&gt;getCollection&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;coupons&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;updateOne&lt;/span&gt;(session,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;and&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;_id&amp;#34;&lt;/span&gt;, couponId),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                                com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Filters&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;eq&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;used&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;)),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                        com.&lt;span style=&#34;color:#a6e22e&#34;&gt;mongodb&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;client&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;model&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;Updates&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;set&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;used&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;                &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;null&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            });
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;// 任何一步抛异常，前面所有操作自动回滚&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant A as 下单服务
    participant M as MongoDB 副本集
    A-&amp;gt;&amp;gt;M: withTransaction 开启
    A-&amp;gt;&amp;gt;M: 1. 条件原子扣库存
    alt 库存不足
        M--&amp;gt;&amp;gt;A: modifiedCount = 0
        A-&amp;gt;&amp;gt;M: 自动 abortTransaction&amp;lt;br/&amp;gt;三步全部回滚
        A--&amp;gt;&amp;gt;A: 返回售罄
    else 成功
        A-&amp;gt;&amp;gt;M: 2. 插入订单(含快照)
        A-&amp;gt;&amp;gt;M: 3. 核销优惠券
        A-&amp;gt;&amp;gt;M: commit (w:majority)
        M--&amp;gt;&amp;gt;A: 提交成功
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;踩坑故事 #5（事务的 QPS 税）&lt;/strong&gt;：压测发现全事务下单链路 QPS 比「原子扣减 + 消息队列异步落单」低约 40%，且长事务会压住 WiredTiger 的旧版本清理（第二篇的坑）。大促链路的最终方案：&lt;strong&gt;核心扣减用单文档原子操作（快），订单与券的一致性用 MQ + 定时对账任务兜底（最终一致）&lt;/strong&gt;。事务留给后台管理类低频强一致操作。教训：&lt;strong&gt;事务是保险丝，不是日常用电。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&#34;73-分片单副本集扛不住之后&#34;&gt;7.3 分片：单副本集扛不住之后&lt;/h3&gt;
&lt;p&gt;数据量单副本集放不下（经验值：单集合过 2TB 或写入 QPS 过万再考虑），才上分片：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    APP[&amp;#34;应用&amp;#34;] --&amp;gt; MONGOS[&amp;#34;mongos 路由层&amp;#34;]
    MONGOS --&amp;gt; S1[&amp;#34;Shard 1&amp;lt;br/&amp;gt;副本集&amp;#34;]
    MONGOS --&amp;gt; S2[&amp;#34;Shard 2&amp;lt;br/&amp;gt;副本集&amp;#34;]
    MONGOS --&amp;gt; S3[&amp;#34;Shard 3&amp;lt;br/&amp;gt;副本集&amp;#34;]
    MONGOS -.-&amp;gt; CFG[&amp;#34;Config Server&amp;lt;br/&amp;gt;元数据副本集&amp;#34;]
    S1 -.-&amp;gt;|&amp;#34;均衡器&amp;lt;br/&amp;gt;chunk 迁移&amp;#34;| S2&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;分片键（shard key）是 Mongo 最「一选定终身」的决策，选错无法修改（5.0 之前）。三条铁律：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;高基数&lt;/strong&gt;：用 &lt;code&gt;status&lt;/code&gt;（只有几个枚举值）做分片键，所有数据挤在少数 chunk 里，分了个寂寞。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免单调递增&lt;/strong&gt;：用 &lt;code&gt;createdAt&lt;/code&gt; 或自增 ObjectId 做分片键，所有新写入都打到&lt;strong&gt;最后一个分片&lt;/strong&gt;——热点分片被写爆，其他分片围观。日志类业务要么 hash 分片键，要么接受这个形态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;匹配查询模式&lt;/strong&gt;：订单按 &lt;code&gt;userId&lt;/code&gt; hash 分片，「查我的订单」路由到单分片，飞快；「全平台订单报表」变成广播查询（scatter-gather），慢——所以报表本来就不该打在分片集群上，该走数仓。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;踩坑故事 #6（jumbo chunk 卡死均衡）&lt;/strong&gt;：早期一张集合按 &lt;code&gt;categoryId&lt;/code&gt; 分片，头部类目一个 chunk 涨到 500MB 还无法拆分（jumbo chunk），均衡器彻底停摆，分片间数据倾斜到 3:1。最后导出数据重建集合换 hash 分片键才解决，停机 4 小时。&lt;strong&gt;分片键评审必须过架构组，这是红线第三条。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;第八篇-运维底线与-ai-时代把系统做稳把代码写少&#34;&gt;第八篇 运维底线与 AI 时代：把系统做稳，把代码写少&lt;/h2&gt;
&lt;h3 id=&#34;81-生产运维的最低纲领&#34;&gt;8.1 生产运维的最低纲领&lt;/h3&gt;
&lt;p&gt;不展开讲 DBA 手册，只列必须亲自盯的五件事：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    M[&amp;#34;监控&amp;lt;br/&amp;gt;slow query / cache 压力 / oplog 窗口 / 复制延迟&amp;#34;] --&amp;gt; A[&amp;#34;告警&amp;lt;br/&amp;gt;复制延迟 &amp;gt; 30s 即报警&amp;lt;br/&amp;gt;别等追不上才发现&amp;#34;]
    B[&amp;#34;备份&amp;lt;br/&amp;gt; mongodump 逻辑备份&amp;lt;br/&amp;gt;+ 快照物理备份&amp;lt;br/&amp;gt;恢复演练每季度一次&amp;#34;] --&amp;gt; A
    C[&amp;#34;慢查询&amp;lt;br/&amp;gt;profiler 只开采样&amp;lt;br/&amp;gt;每周过 top 慢查询&amp;#34;] --&amp;gt; A
    D[&amp;#34;容量&amp;lt;br/&amp;gt;磁盘水位 70% 预警&amp;lt;br/&amp;gt;oplog 大小按写入峰值调&amp;#34;] --&amp;gt; A
    E[&amp;#34;权限&amp;lt;br/&amp;gt;最小化授权 禁止裸奔&amp;lt;br/&amp;gt;生产禁 dropDatabase&amp;#34;] --&amp;gt; A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;踩坑故事 #7（恢复演练救了我们一命）&lt;/strong&gt;：团队坚持每季度做一次「备份恢复演练」，被吐槽形式主义。直到有人误删集合（&lt;code&gt;drop()&lt;/code&gt; 当 &lt;code&gt;deleteMany()&lt;/code&gt; 用了），靠备份 40 分钟恢复，业务无损。&lt;strong&gt;没演练过的备份等于没有备份&lt;/strong&gt;——这句话值一个 P0 事故。&lt;/p&gt;
&lt;h3 id=&#34;82-ai-时代如何少写代码把系统做稳&#34;&gt;8.2 AI 时代：如何少写代码把系统做稳&lt;/h3&gt;
&lt;p&gt;写这篇博客的 2021 年，GitHub Copilot 刚发布。站在工程视角，做几个判断，留给未来验证：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AI 会吃掉「模式化代码」，吃不掉「模式化决策」。&lt;/strong&gt; CRUD、Schema 定义、聚合管道骨架、连接池配置——这些高度模式化的代码 AI 生成得又快又准（你甚至可以现在就试试让 Copilot 补全上面的 &lt;code&gt;DeductStockDemo&lt;/code&gt;，完成度惊人）。但「嵌入还是引用」「分片键选什么」「writeConcern 定哪档」——这些&lt;strong&gt;依赖业务流量预测的决策&lt;/strong&gt;，AI 给不出答案。核心竞争力从「会写」转向「会判断」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;「少写代码」的正确姿势是把决策做在数据层。&lt;/strong&gt; 用快照冗余消灭 &lt;code&gt;$lookup&lt;/code&gt;，用 TTL 索引消灭清理脚本，用原子扣减消灭分布式锁，用条件更新消灭事务——本篇通篇的「少写代码」讲的都是这个：&lt;strong&gt;好的建模让代码自然变少&lt;/strong&gt;。AI 是放大器，放大的既可以是好设计，也可以是坏设计。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一个人撑起一个 App 的门槛在崩塌。&lt;/strong&gt; MongoDB Atlas（托管副本集/分片）+ Serverless 函数 + AI 生成 CRUD 层，一个小电商的服务端几天就能搭起来。2021 年这还只是苗头，我确信它会成常态。但反过来：&lt;strong&gt;门槛越低，线上事故越便宜&lt;/strong&gt;——不懂 writeConcern 的人也能上线了，丢单的事故只会更多。AI 时代的工程教育，重点应该从「怎么实现」转向「哪里会炸」。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 生成代码的第一评审项是资源语义。&lt;/strong&gt; 连接谁关、事务多长、批量多大、一致性哪档——AI 生成的代码功能往往是对的，资源语义常常是错的（比如给你 &lt;code&gt;w: 1&lt;/code&gt; 的默认写入）。Code Review 的重心会整体迁移到这四个问题上。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;思考与感悟之四（收尾）&lt;/strong&gt;：工程的本质，是「替业务把不确定性挡在系统之外」。MongoDB 给了你自由度，也把范式的保护伞收走了——锁粒度、一致性档位、分片拓扑，每一个都要自己选。AI 会帮你把代码写得越来越快，但「选什么」的责任永远在人身上。这既是这份工作的辛苦之处，也是它的护城河。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;附录demo-与检查清单&#34;&gt;附录：Demo 与检查清单&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Demo&lt;/th&gt;
					&lt;th&gt;类/片段&lt;/th&gt;
					&lt;th&gt;覆盖知识点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;建索引&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;IndexDemo&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;ESR 复合索引、部分索引、TTL 索引&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;热销榜聚合&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;TopSalesDemo&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;$match&lt;/code&gt; 前置、&lt;code&gt;$unwind&lt;/code&gt;/&lt;code&gt;$group&lt;/code&gt;、&lt;code&gt;allowDiskUse&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;写入档位&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;demo02-write-concern&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;w:1&lt;/code&gt; / &lt;code&gt;majority&lt;/code&gt; / journal 的取舍&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;原子扣库存&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;DeductStockDemo&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;条件原子更新防超卖&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;事务下单&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;PlaceOrderTxDemo&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;多文档事务、回滚、事务读写关注级别&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;MongoDB 生产红线（本文沉淀，可直接抄进团队规范）：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;生产环境永远不开不设限的大批量更新，批量操作必拆批 + 让出窗口。&lt;/li&gt;
&lt;li&gt;凡带 sort 的查询，必须 explain 验证走索引排序。&lt;/li&gt;
&lt;li&gt;分片键评审必须过架构组；能用副本集解决的绝不上分片。&lt;/li&gt;
&lt;li&gt;钱相关的读写一律 &lt;code&gt;majority&lt;/code&gt;；默认配置永远不可信。&lt;/li&gt;
&lt;li&gt;备份必须每季度做恢复演练，没演练过的备份等于没有备份。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;文中代码基于 MongoDB 5.0 + 官方 Java 同步驱动 4.3 实测可编译运行。如果你正站在「要不要接 MongoDB」的门口，建议按八篇顺序在本地副本集上把每个坑亲手复现一遍——看别人踩坑是故事，自己踩一遍才是经验。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Lua 入门：写给 Redis 脚本使用者的极简教程</title>
      <link>https://ftmao.com/blog/posts/lua-intro-for-redis/</link>
      <pubDate>Sun, 03 Oct 2021 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/lua-intro-for-redis/</guid>
      <description>&lt;h1 id=&#34;lua-入门写给-redis-脚本使用者的极简教程&#34;&gt;Lua 入门：写给 Redis 脚本使用者的极简教程&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;Redis 从 2.6 开始支持 Lua 脚本，核心目的是把多个命令打包成&lt;strong&gt;原子操作&lt;/strong&gt;。本文面向已经会用 Redis、但还没写过 Lua 脚本的同学，只讲最常用、最实用的部分，学完就能写出常见的限流、加锁、库存扣减脚本。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一为什么-redis-需要-lua&#34;&gt;一、为什么 Redis 需要 Lua&lt;/h2&gt;
&lt;p&gt;Redis 单条命令是原子的，但多条命令组合起来就不是了。比如经典的「查库存 → 判断 → 扣减」：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET stock:1001      &lt;span style=&#34;color:#75715e&#34;&gt;# 返回 10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 业务代码判断 10 &amp;gt; 0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;DECR stock:1001     &lt;span style=&#34;color:#75715e&#34;&gt;# 返回 9&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;并发时两个客户端可能都读到 10，然后都执行 DECR，导致超卖。&lt;/p&gt;
&lt;p&gt;Lua 脚本在 Redis 里&lt;strong&gt;作为一个整体执行&lt;/strong&gt;，中间不会被其他命令打断，天然解决了这类问题。而且脚本在 Redis 服务端执行，还能减少网络往返。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二本地-lua-环境准备&#34;&gt;二、本地 Lua 环境准备&lt;/h2&gt;
&lt;h3 id=&#34;21-安装-lua&#34;&gt;2.1 安装 Lua&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;macOS：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;brew install lua
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lua -v&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Ubuntu / Debian：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt update
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo apt install lua5.1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lua5.1 -v&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Windows：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;从 &lt;a href=&#34;https://www.lua.org/download.html&#34;&gt;Lua 官方&lt;/a&gt; 下载源码或预编译二进制，也可以直接用 &lt;a href=&#34;https://github.com/rjpcomputing/luaforwindows&#34;&gt;Lua for Windows&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Docker（最省事）：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;docker run --rm -it nickblah/lua:5.1 lua -v&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;22-第一个本地-lua-程序&#34;&gt;2.2 第一个本地 Lua 程序&lt;/h3&gt;
&lt;p&gt;创建 &lt;code&gt;hello.lua&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;print(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hello, lua&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;运行：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lua hello.lua&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;23-用-redis-验证脚本&#34;&gt;2.3 用 Redis 验证脚本&lt;/h3&gt;
&lt;p&gt;如果你本地有 Redis，可以直接用 &lt;code&gt;redis-cli&lt;/code&gt; 跑脚本文件：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;redis-cli --eval myscript.lua key1 key2 , arg1 arg2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;注意 &lt;code&gt;,&lt;/code&gt; 前后都有空格：&lt;code&gt;,&lt;/code&gt; 左边是 &lt;code&gt;KEYS&lt;/code&gt;，右边是 &lt;code&gt;ARGV&lt;/code&gt;。&lt;/p&gt;
&lt;h3 id=&#34;24-编辑器推荐&#34;&gt;2.4 编辑器推荐&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;VS Code&lt;/strong&gt;：插件 &lt;code&gt;Lua&lt;/code&gt;（sumneko）或 &lt;code&gt;Lua Language Server&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IDEA / WebStorm&lt;/strong&gt;：安装 &lt;code&gt;EmmyLua&lt;/code&gt; 插件。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Neovim&lt;/strong&gt;：&lt;code&gt;lua_ls&lt;/code&gt; + &lt;code&gt;lspconfig&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三hello-world第一个-redis-lua-脚本&#34;&gt;三、Hello World：第一个 Redis Lua 脚本&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EVAL &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;return &amp;#39;hello, redis lua&amp;#39;&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;EVAL&lt;/code&gt; 是执行 Lua 脚本的命令。&lt;/li&gt;
&lt;li&gt;后面的 &lt;code&gt;0&lt;/code&gt; 表示脚本用到的 key 数量是 0。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;return&lt;/code&gt; 把结果返回给客户端。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;带 key 和参数的版本：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EVAL &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;return redis.call(&amp;#39;GET&amp;#39;, KEYS[1])&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; user:1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 等同于 GET user:1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EVAL &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;return redis.call(&amp;#39;SET&amp;#39;, KEYS[1], ARGV[1])&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; user:1 alice
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 等同于 SET user:1 alice&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;KEYS[1]&lt;/code&gt; 对应第 1 个 key，&lt;code&gt;KEYS[2]&lt;/code&gt; 对应第 2 个，以此类推。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ARGV[1]&lt;/code&gt; 对应第 1 个额外参数。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;所有 ARGV 都是字符串&lt;/strong&gt;，如果需要数字，要在 Lua 里做类型转换。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四lua-基础redis-场景够用的子集&#34;&gt;四、Lua 基础：Redis 场景够用的子集&lt;/h2&gt;
&lt;h3 id=&#34;41-变量与类型&#34;&gt;4.1 变量与类型&lt;/h3&gt;
&lt;p&gt;Lua 是动态类型语言，常用类型有：&lt;code&gt;nil&lt;/code&gt;、&lt;code&gt;boolean&lt;/code&gt;、&lt;code&gt;number&lt;/code&gt;、&lt;code&gt;string&lt;/code&gt;、&lt;code&gt;table&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;alice&amp;#34;&lt;/span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;-- 字符串&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; age &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;            &lt;span style=&#34;color:#75715e&#34;&gt;-- 数字，不区分 int / float&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; ok &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;           &lt;span style=&#34;color:#75715e&#34;&gt;-- 布尔&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; empty &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;nil&lt;/span&gt;         &lt;span style=&#34;color:#75715e&#34;&gt;-- 空&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;局部变量用 &lt;code&gt;local&lt;/code&gt;，否则会成为全局变量。Redis 脚本里建议一律用 &lt;code&gt;local&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;条件判断时，&lt;code&gt;0&lt;/code&gt; 和空字符串 &lt;code&gt;&#39;&#39;&lt;/code&gt; 都是 &lt;code&gt;true&lt;/code&gt;，只有 &lt;code&gt;nil&lt;/code&gt; 和 &lt;code&gt;false&lt;/code&gt; 是 &lt;code&gt;false&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; a &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;yes&amp;#34;&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;-- 会执行这里&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;42-字符串与数字转换&#34;&gt;4.2 字符串与数字转换&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; s &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;42&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tonumber(s)      &lt;span style=&#34;color:#75715e&#34;&gt;-- 转成数字 42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; s2 &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tostring(n)     &lt;span style=&#34;color:#75715e&#34;&gt;-- 转成字符串 &amp;#34;42&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;10&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; b &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;3&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; c &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; b            &lt;span style=&#34;color:#75715e&#34;&gt;-- 13，自动把字符串当数字&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;在 Redis 脚本里，更推荐显式转换，避免意外：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tonumber(redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;, KEYS[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])) &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;43-表table数组--字典&#34;&gt;4.3 表（table）：数组 + 字典&lt;/h3&gt;
&lt;p&gt;表是 Lua 唯一的数据结构，既能当数组用，也能当字典用。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 数组（下标从 1 开始）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; arr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;a&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;b&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;c&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; arr[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]    &lt;span style=&#34;color:#75715e&#34;&gt;-- &amp;#34;a&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 字典&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; dict &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;alice&amp;#34;&lt;/span&gt;, age &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; dict.name &lt;span style=&#34;color:#75715e&#34;&gt;-- &amp;#34;alice&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;遍历数组：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; arr &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;a&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;b&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;c&amp;#34;&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i, v &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; ipairs(arr) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(i, v)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 输出：&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 1 a&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 2 b&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 3 c&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;遍历字典：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; dict &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {name &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;alice&amp;#34;&lt;/span&gt;, age &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; k, v &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; pairs(dict) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(k, v)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;44-条件判断&#34;&gt;4.4 条件判断&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;20&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;big&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;elseif&lt;/span&gt; n &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;medium&amp;#34;&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;-- 会返回这个&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;small&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;45-循环&#34;&gt;4.5 循环&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- for 循环&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(i)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- while 循环&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    print(i)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    i &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; i &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;Redis 脚本里循环要慎用，避免执行时间过长阻塞主线程。&lt;/p&gt;
&lt;h3 id=&#34;46-函数&#34;&gt;4.6 函数&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;function&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;add&lt;/span&gt;(a, b)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; a &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; b
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; add(&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)  &lt;span style=&#34;color:#75715e&#34;&gt;-- 3&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;五lua-原理浅析&#34;&gt;五、Lua 原理浅析&lt;/h2&gt;
&lt;p&gt;理解 Lua 的运行机制，能帮你写出更稳的 Redis 脚本。&lt;/p&gt;
&lt;h3 id=&#34;51-解释器与字节码&#34;&gt;5.1 解释器与字节码&lt;/h3&gt;
&lt;p&gt;Lua 不是纯解释执行。它会把源码编译成&lt;strong&gt;字节码（bytecode）&lt;/strong&gt;，然后在 Lua 虚拟机（VM）上运行。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;luac -o hello.luac hello.lua    -- 编译成字节码
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;lua hello.luac                   -- 运行字节码&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;Redis 内部集成的也是 Lua VM，脚本第一次执行时会先编译成字节码，之后再执行就复用这个结果。所以长脚本第一次跑会慢一点，后续重复执行会快一些。&lt;/p&gt;
&lt;h3 id=&#34;52-内存回收机制&#34;&gt;5.2 内存回收机制&lt;/h3&gt;
&lt;p&gt;Lua 使用&lt;strong&gt;自动垃圾回收（Garbage Collection, GC）&lt;/strong&gt;，主要策略是&lt;strong&gt;增量式标记-清除（incremental mark-and-sweep）&lt;/strong&gt;。Lua 5.4 还引入了&lt;strong&gt;分代 GC&lt;/strong&gt; 模式。&lt;/p&gt;
&lt;p&gt;对 Redis 脚本来说，关键点是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;脚本运行期间创建的 table、string 等临时对象，由 Lua GC 自动回收。&lt;/li&gt;
&lt;li&gt;Redis 脚本通常很短，GC 压力不大。&lt;/li&gt;
&lt;li&gt;不要在脚本里构造超大 table 或缓存大量中间结果，否则可能触发 GC 暂停，阻塞 Redis。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;53-table-是核心&#34;&gt;5.3 Table 是核心&lt;/h3&gt;
&lt;p&gt;Lua 的 table 本质上是一个&lt;strong&gt;哈希表 + 数组&lt;/strong&gt;的混合结构：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;用连续整数索引时，底层按数组优化，访问快。&lt;/li&gt;
&lt;li&gt;用字符串或其他类型当 key 时，底层是哈希表。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这也是 Lua 能用 table 模拟数组、字典、对象、模块的原因。&lt;/p&gt;
&lt;h3 id=&#34;54-redis-中的-lua-沙箱&#34;&gt;5.4 Redis 中的 Lua 沙箱&lt;/h3&gt;
&lt;p&gt;Redis 对 Lua 做了严格限制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;不能加载外部库（如 &lt;code&gt;io&lt;/code&gt;、&lt;code&gt;os&lt;/code&gt; 大部分功能被禁用）。&lt;/li&gt;
&lt;li&gt;不能发起网络请求。&lt;/li&gt;
&lt;li&gt;不能访问文件系统。&lt;/li&gt;
&lt;li&gt;只能调用 Redis 提供的 API（如 &lt;code&gt;redis.call&lt;/code&gt;、&lt;code&gt;redis.log&lt;/code&gt;）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个沙箱保证了脚本安全，但也意味着你&lt;strong&gt;不能直接在脚本里读系统时间&lt;/strong&gt;（&lt;code&gt;os.time&lt;/code&gt; 被禁用），需要把时间作为 &lt;code&gt;ARGV&lt;/code&gt; 传进去。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六redis-脚本核心-api&#34;&gt;六、Redis 脚本核心 API&lt;/h2&gt;
&lt;h3 id=&#34;61-rediscall-与-redispcall&#34;&gt;6.1 redis.call 与 redis.pcall&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;redis.call(...)&lt;/code&gt;：调用 Redis 命令，出错时直接抛出 Lua 错误，脚本会终止。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;redis.pcall(...)&lt;/code&gt;：调用 Redis 命令，出错时返回错误对象，脚本继续执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- call 版本：key 不存在会报错&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; v &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;non_existent_key&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- pcall 版本：可以捕获错误&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; ok, err &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; pcall(redis.call, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;non_existent_key&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;not&lt;/span&gt; ok &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;error: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; tostring(err)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;一般推荐：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;简单脚本用 &lt;code&gt;redis.call&lt;/code&gt;，逻辑更清晰。&lt;/li&gt;
&lt;li&gt;需要容错时用 &lt;code&gt;redis.pcall&lt;/code&gt; 或外层 &lt;code&gt;pcall&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;62-返回值&#34;&gt;6.2 返回值&lt;/h3&gt;
&lt;p&gt;Lua 脚本可以返回单个值、数组或表：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 返回单个值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;, KEYS[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 返回多个值&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; {redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;, KEYS[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]), redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;, KEYS[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;])}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;Redis 会把 Lua 的 &lt;code&gt;table&lt;/code&gt; 转成数组返回给客户端。&lt;/p&gt;
&lt;h3 id=&#34;63-keys-与-argv-的规范&#34;&gt;6.3 KEYS 与 ARGV 的规范&lt;/h3&gt;
&lt;p&gt;Redis Cluster 要求脚本里用到的 key 都必须放在 &lt;code&gt;KEYS&lt;/code&gt; 中，这样 Redis 才能判断脚本是否跨 slot。&lt;code&gt;ARGV&lt;/code&gt; 里只能放非 key 参数。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EVAL &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;return redis.call(&amp;#39;SET&amp;#39;, KEYS[1], ARGV[1])&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; mykey myvalue&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;这里 &lt;code&gt;mykey&lt;/code&gt; 是 key，&lt;code&gt;myvalue&lt;/code&gt; 是 value。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七实战常见-redis-lua-模式&#34;&gt;七、实战：常见 Redis Lua 模式&lt;/h2&gt;
&lt;h3 id=&#34;71-限流滑动窗口&#34;&gt;7.1 限流（滑动窗口）&lt;/h3&gt;
&lt;p&gt;限制某个用户在 60 秒内最多请求 100 次：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; KEYS[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; window &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tonumber(ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])      &lt;span style=&#34;color:#75715e&#34;&gt;-- 窗口大小，毫秒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; limit &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tonumber(ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;])       &lt;span style=&#34;color:#75715e&#34;&gt;-- 限制次数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; now &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tonumber(ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;])         &lt;span style=&#34;color:#75715e&#34;&gt;-- 当前时间戳，毫秒&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 清理窗口外的请求记录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ZREMRANGEBYSCORE&amp;#39;&lt;/span&gt;, key, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, now &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; window)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 统计当前窗口内的请求数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; count &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ZCARD&amp;#39;&lt;/span&gt;, key)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; count &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; limit &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ZADD&amp;#39;&lt;/span&gt;, key, now, now &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;_&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; count)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;PEXPIRE&amp;#39;&lt;/span&gt;, key, window)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;-- 允许通过&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;-- 拒绝&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;调用：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EVAL &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;...脚本...&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; rate:user:1 &lt;span style=&#34;color:#ae81ff&#34;&gt;60000&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;100&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1690000000000&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;72-分布式锁可重入简化版&#34;&gt;7.2 分布式锁（可重入简化版）&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; KEYS[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; token &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; ttl &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tonumber(ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; current &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;, key)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; current &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; current &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; token &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;SET&amp;#39;&lt;/span&gt;, key, token, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;PX&amp;#39;&lt;/span&gt;, ttl)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;73-库存扣减防超卖&#34;&gt;7.3 库存扣减（防超卖）&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; KEYS[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; delta &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tonumber(ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;])
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; stock &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tonumber(redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;, key)) &lt;span style=&#34;color:#f92672&#34;&gt;or&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; stock &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; delta &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;-- 库存不足&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;DECRBY&amp;#39;&lt;/span&gt;, key, delta)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; stock &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; delta&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;74-批量删除符合某种前缀的-key&#34;&gt;7.4 批量删除符合某种前缀的 key&lt;/h3&gt;
&lt;p&gt;注意：生产环境慎用 &lt;code&gt;KEYS&lt;/code&gt; 命令，这里仅作语法示例，大数据量请用 &lt;code&gt;SCAN&lt;/code&gt;。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; pattern &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; keys &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;KEYS&amp;#39;&lt;/span&gt;, pattern)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; i, k &lt;span style=&#34;color:#66d9ef&#34;&gt;in&lt;/span&gt; ipairs(keys) &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;DEL&amp;#39;&lt;/span&gt;, k)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;#&lt;/span&gt;keys&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;75-条件更新cas-简化版&#34;&gt;7.5 条件更新（CAS 简化版）&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; KEYS[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; expected &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; newValue &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; ARGV[&lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;local&lt;/span&gt; current &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;GET&amp;#39;&lt;/span&gt;, key)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; current &lt;span style=&#34;color:#f92672&#34;&gt;==&lt;/span&gt; expected &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    redis.call(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;SET&amp;#39;&lt;/span&gt;, key, newValue)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;end&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;八调试常见坑与最佳实践&#34;&gt;八、调试、常见坑与最佳实践&lt;/h2&gt;
&lt;h3 id=&#34;81-调试脚本&#34;&gt;8.1 调试脚本&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;先用本地 Redis 跑&lt;/strong&gt;：&lt;code&gt;redis-cli --eval script.lua key1 key2 , arg1 arg2&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;注意 &lt;code&gt;,&lt;/code&gt; 前后有空格，&lt;code&gt;,&lt;/code&gt; 左边是 KEYS，右边是 ARGV。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;打印日志&lt;/strong&gt;：Lua 脚本里不能直接用 &lt;code&gt;print&lt;/code&gt; 输出到客户端，但可以用 &lt;code&gt;redis.log(redis.LOG_NOTICE, msg)&lt;/code&gt; 写到 Redis 日志。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-lua&#34; data-lang=&#34;lua&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;redis.log(redis.LOG_NOTICE, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;current stock: &amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;..&lt;/span&gt; tostring(stock))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ol start=&#34;3&#34;&gt;
&lt;li&gt;&lt;strong&gt;本地先用 &lt;code&gt;lua&lt;/code&gt; 命令跑语法&lt;/strong&gt;：不依赖 Redis 也能检查基础语法错误。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&#34;82-常见踩坑清单&#34;&gt;8.2 常见踩坑清单&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;坑&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;建议&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;ARGV 全是字符串&lt;/td&gt;
					&lt;td&gt;数字运算前必须 &lt;code&gt;tonumber()&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;每个数字参数都显式 &lt;code&gt;tonumber()&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;全局变量&lt;/td&gt;
					&lt;td&gt;忘了 &lt;code&gt;local&lt;/code&gt; 可能污染全局环境，Redis 甚至会杀掉脚本&lt;/td&gt;
					&lt;td&gt;所有变量一律加 &lt;code&gt;local&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;脚本执行时间&lt;/td&gt;
					&lt;td&gt;循环、大 key 操作会阻塞 Redis 主线程&lt;/td&gt;
					&lt;td&gt;脚本尽量短，避免复杂循环&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;集群 key 位置&lt;/td&gt;
					&lt;td&gt;所有 key 必须写在 &lt;code&gt;KEYS&lt;/code&gt; 里，跨 slot 会报错&lt;/td&gt;
					&lt;td&gt;业务 key 设计时确保同一 slot&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;脚本缓存&lt;/td&gt;
					&lt;td&gt;长脚本每次 EVAL 都传输浪费带宽&lt;/td&gt;
					&lt;td&gt;生产环境用 &lt;code&gt;EVALSHA&lt;/code&gt; 或客户端自动缓存&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;nil&lt;/code&gt; 中断数组&lt;/td&gt;
					&lt;td&gt;table 中间出现 &lt;code&gt;nil&lt;/code&gt; 会导致 &lt;code&gt;#&lt;/code&gt; 长度计算出错&lt;/td&gt;
					&lt;td&gt;避免在数组中间塞 &lt;code&gt;nil&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;pairs&lt;/code&gt; 遍历顺序&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;pairs&lt;/code&gt; 不保证顺序，&lt;code&gt;ipairs&lt;/code&gt; 才按数组顺序&lt;/td&gt;
					&lt;td&gt;需要顺序时用 &lt;code&gt;ipairs&lt;/code&gt; 或自己维护索引&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;字符串拼接性能&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;..&lt;/code&gt; 在循环里反复拼接是 O(n²)&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;table.concat&lt;/code&gt; 批量拼接&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;redis.call&lt;/code&gt; 报错&lt;/td&gt;
					&lt;td&gt;命令执行失败会直接抛错终止脚本&lt;/td&gt;
					&lt;td&gt;需要容错时改用 &lt;code&gt;redis.pcall&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;缺失 key 的返回值&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;GET&lt;/code&gt; 不存在的 key 返回 &lt;code&gt;false&lt;/code&gt;，不是 &lt;code&gt;nil&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;判断时用 &lt;code&gt;== false&lt;/code&gt; 或 &lt;code&gt;if not current&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;写命令必须确定性的&lt;/td&gt;
					&lt;td&gt;主从复制/持久化要求相同的脚本产生相同结果&lt;/td&gt;
					&lt;td&gt;不要用随机数、&lt;code&gt;TIME&lt;/code&gt;、&lt;code&gt;SRANDMEMBER&lt;/code&gt; 等做写决策&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;浮点数比较&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0.1 + 0.2 ~= 0.3&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;用整数存储金额/库存，避免浮点&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;83-性能建议&#34;&gt;8.3 性能建议&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;脚本尽量短小，只处理一个原子逻辑单元。&lt;/li&gt;
&lt;li&gt;不要在 Lua 里做复杂计算或大量循环。&lt;/li&gt;
&lt;li&gt;大 key 操作（如 &lt;code&gt;HGETALL&lt;/code&gt; 几十万字段）不要在脚本里做。&lt;/li&gt;
&lt;li&gt;热 key 尽量缩短 TTL 或做拆分，避免单脚本长时间占用。&lt;/li&gt;
&lt;li&gt;对执行时间没把握的脚本，先用 &lt;code&gt;redis-cli --latency-history&lt;/code&gt; 或 &lt;code&gt;SLOWLOG&lt;/code&gt; 观察影响。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九总结&#34;&gt;九、总结&lt;/h2&gt;
&lt;p&gt;Redis Lua 脚本不需要你成为 Lua 专家，掌握下面几点就能覆盖 80% 的场景：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;本地能跑 Lua，能写 &lt;code&gt;.lua&lt;/code&gt; 文件并用 &lt;code&gt;redis-cli --eval&lt;/code&gt; 测试。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;local&lt;/code&gt; 变量、&lt;code&gt;number&lt;/code&gt; / &lt;code&gt;string&lt;/code&gt; / &lt;code&gt;table&lt;/code&gt; 三种类型。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tonumber()&lt;/code&gt; / &lt;code&gt;tostring()&lt;/code&gt; 转换，&lt;code&gt;ipairs()&lt;/code&gt; / &lt;code&gt;pairs()&lt;/code&gt; 遍历，&lt;code&gt;table.concat&lt;/code&gt; 拼接。&lt;/li&gt;
&lt;li&gt;Lua 是编译成字节码在 VM 上运行，GC 自动回收，Redis 里是沙箱环境。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;redis.call()&lt;/code&gt; 调用命令，&lt;code&gt;KEYS[n]&lt;/code&gt; 和 &lt;code&gt;ARGV[n]&lt;/code&gt; 传参，集群 key 必须放 KEYS。&lt;/li&gt;
&lt;li&gt;脚本整体原子执行，适合「读 → 判断 → 写」的复合操作。&lt;/li&gt;
&lt;li&gt;注意 ARGV 是字符串、缺失 key 返回 &lt;code&gt;false&lt;/code&gt;、避免长脚本阻塞、写命令要确定性。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;限流、分布式锁、库存扣减、条件更新这些高频场景，本质上都是同一个模式：&lt;strong&gt;先读数据，在脚本里做判断，再决定写什么&lt;/strong&gt;。把这一点练熟，Redis Lua 就入门了。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;如果你在 Redis Lua 脚本里踩过什么坑，欢迎在评论区分享。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title>从Redis核心原理到 IPTV 视频项目落地</title>
      <link>https://ftmao.com/blog/posts/redis-in-action-iptv/</link>
      <pubDate>Tue, 28 Sep 2021 10:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/redis-in-action-iptv/</guid>
      <description>&lt;h1 id=&#34;从redis核心原理到-iptv-视频项目落地&#34;&gt;从Redis核心原理到 IPTV 视频项目落地&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;先系统梳理 Redis 的核心原理与面试高频知识点，再结合 IPTV（交互式网络电视）视频业务场景，给出可直接运行的 Demo。最后讨论 Redis 为什么不适合长期存储、常见踩坑点，以及 List / Pub/Sub / Stream 三种消息队列实现。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一redis-是什么一句话定位&#34;&gt;一、Redis 是什么？一句话定位&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Redis（Remote Dictionary Server）是一个开源的、基于内存的高性能键值对数据库&lt;/strong&gt;，支持多种数据结构、持久化、高可用与分布式扩展。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[Redis] --&amp;gt; B[内存存储]
    A --&amp;gt; C[数据结构服务器]
    A --&amp;gt; D[单线程命令执行]
    A --&amp;gt; E[IO 多路复用]
    A --&amp;gt; F[持久化 + 高可用]
    B --&amp;gt; G[读写毫秒级]
    C --&amp;gt; H[String/Hash/List/Set/ZSET/Stream/Bitmap/Geo/HyperLogLog]
    F --&amp;gt; I[RDB/AOF/主从/哨兵/Cluster]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;与 Memcached 等纯缓存不同，Redis 的核心价值在于&lt;strong&gt;丰富的数据结构与原子操作&lt;/strong&gt;，可以直接在内存中完成排序、计数、限流、去重、队列等复杂逻辑。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二redis-为什么快&#34;&gt;二、Redis 为什么快？&lt;/h2&gt;
&lt;p&gt;面试常问「Redis 为什么快」，通常从四个层面回答：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;原因&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;内存操作&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;数据存于内存，无需磁盘寻道，访问速度是 SSD 的千倍以上。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;单线程命令执行&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;避免多线程上下文切换、锁竞争，CPU 缓存命中率更高。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;IO 多路复用&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;基于 epoll/kqueue/select 用一个线程处理大量连接。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;高效数据结构&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;String 底层 SDS、ZSET 底层跳表+字典、List 底层 quicklist 等。&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph Redis 6/7 线程模型
        M[主线程&amp;lt;br/&amp;gt;命令执行 + IO 多路复用]
        IO[IO 线程池&amp;lt;br/&amp;gt;仅网络读写]
        BG[后台进程&amp;lt;br/&amp;gt;RDB fork / AOF rewrite]
    end
    C1[Client 1] --&amp;gt; M
    C2[Client 2] --&amp;gt; M
    C3[Client 3] --&amp;gt; M
    M &amp;lt;--&amp;gt; IO
    M -.-&amp;gt; BG&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;注意：Redis 6/7 引入的多线程&lt;strong&gt;只用于网络 IO 读写&lt;/strong&gt;，命令执行依旧是单线程。所以单条慢命令仍会阻塞整个实例。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三redis-数据类型与使用场景&#34;&gt;三、Redis 数据类型与使用场景&lt;/h2&gt;
&lt;h3 id=&#34;31-五种基本数据类型&#34;&gt;3.1 五种基本数据类型&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;类型&lt;/th&gt;
					&lt;th&gt;底层结构&lt;/th&gt;
					&lt;th&gt;典型场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;String&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;SDS（简单动态字符串）&lt;/td&gt;
					&lt;td&gt;缓存、计数器、分布式锁、Session&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Hash&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;ziplist / hashtable&lt;/td&gt;
					&lt;td&gt;对象缓存、用户信息、EPG 节目单&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;List&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;quicklist（ziplist + 双向链表）&lt;/td&gt;
					&lt;td&gt;消息队列、最新 N 条记录&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Set&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;intset / hashtable&lt;/td&gt;
					&lt;td&gt;去重、共同关注、抽奖&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;ZSET&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;ziplist / skiplist + dict&lt;/td&gt;
					&lt;td&gt;排行榜、延时队列、范围查询&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;注：表中 ziplist 在 Redis 7 起已被 listpack 替代（动机见 §4.2），配置项改名 &lt;code&gt;*-max-listpack-*&lt;/code&gt;，旧名仍兼容。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;32-三种特殊数据类型&#34;&gt;3.2 三种特殊数据类型&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;类型&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Bitmap&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;位数组，按位操作&lt;/td&gt;
					&lt;td&gt;用户签到、在线状态、UV 统计&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;HyperLogLog&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;基数估计算法&lt;/td&gt;
					&lt;td&gt;海量 UV 去重计数，误差约 0.81%&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Geo&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;地理位置坐标&lt;/td&gt;
					&lt;td&gt;附近的人、POI 搜索&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;33-string最常用却被低估&#34;&gt;3.3 String：最常用却被低估&lt;/h3&gt;
&lt;p&gt;String 不仅能存字符串，还能存整数、浮点数、二进制数据。常用命令：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SET key value EX &lt;span style=&#34;color:#ae81ff&#34;&gt;3600&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;GET key
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;INCR view_count
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;INCRBY view_count &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SETNX lock:order:123 true EX &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# 分布式锁核心命令&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;SDS 的优势&lt;/strong&gt;（相对 C 字符串）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;O(1) 获取长度。&lt;/li&gt;
&lt;li&gt;二进制安全，可存图片、序列化数据。&lt;/li&gt;
&lt;li&gt;预分配空间，减少内存重分配次数。&lt;/li&gt;
&lt;li&gt;杜绝缓冲区溢出。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;34-hash对象缓存首选&#34;&gt;3.4 Hash：对象缓存首选&lt;/h3&gt;
&lt;p&gt;Hash 适合存「一组字段」的对象，比 JSON String 更节省内存、更新更灵活。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HSET user:1001 name &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;张三&amp;#34;&lt;/span&gt; age &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt; city &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;北京&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HGET user:1001 name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HGETALL user:1001
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;HINCRBY user:1001 age &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;35-zset排行榜的王牌&#34;&gt;3.5 ZSET：排行榜的王牌&lt;/h3&gt;
&lt;p&gt;ZSET 底层同时维护「跳表」和「字典」：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;跳表（skiplist）&lt;/strong&gt;：支持 O(log N) 的范围查询和排序。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;字典（dict）&lt;/strong&gt;：支持 O(1) 按 member 查 score。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ZADD hot:tv &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;庆余年&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ZADD hot:tv &lt;span style=&#34;color:#ae81ff&#34;&gt;800&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;狂飙&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ZINCRBY hot:tv &lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;狂飙&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ZREVRANGE hot:tv &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;9&lt;/span&gt; WITHSCORES
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ZREVRANK hot:tv &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;狂飙&amp;#34;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;36-list-与-stream队列双雄&#34;&gt;3.6 List 与 Stream：队列双雄&lt;/h3&gt;
&lt;p&gt;List 适合简单队列，Stream 适合生产级消息队列，后面第十四部分会详细对比。&lt;/p&gt;
&lt;h3 id=&#34;37-bitmap-与-hyperloglog海量-uv-统计的利器&#34;&gt;3.7 Bitmap 与 HyperLogLog：海量 UV 统计的利器&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Bitmap 为什么能天然去重？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Bitmap 本质是一个超长 bit 数组，每个 bit 只能是 0 或 1。把 user_id 作为偏移量（offset），用户访问一次就把对应位置 1：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;天然去重&lt;/strong&gt;：同一用户重复访问，置的都是同一个 bit，无需额外判断。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;极致省内存&lt;/strong&gt;：1 亿用户只需 1 亿 bit ≈ 12MB；而 Set 存 1 亿个 ID 要占用数 GB。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支持位运算&lt;/strong&gt;：&lt;code&gt;BITOP AND/OR&lt;/code&gt; 可以直接算多日留存（今天访问 AND 昨天也访问）。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SETBIT uv:20210928 &lt;span style=&#34;color:#ae81ff&#34;&gt;10086&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;# 用户 10086 访问&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SETBIT uv:20210928 &lt;span style=&#34;color:#ae81ff&#34;&gt;10086&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;# 重复访问，结果不变&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;BITCOUNT uv:20210928         &lt;span style=&#34;color:#75715e&#34;&gt;# 直接得出 UV 数&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;局限：要求 user_id 是连续整数，ID 稀疏时浪费内存。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;HyperLogLog 为什么 12KB 能数一亿？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;HyperLogLog 基于概率估计算法：把每个元素 hash 成均匀分布的随机数，观察 hash 值二进制串中&lt;strong&gt;前导零的最大个数&lt;/strong&gt;。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;类比抛硬币：想连续抛出 N 次正面，平均要抛 2^N 次。反过来，如果见过最长连续 N 次正面，就估计大约抛了 2^N 次——前导零越多，说明见过的不同元素越多。&lt;/li&gt;
&lt;li&gt;单次估计误差大，所以内部用 &lt;strong&gt;16384 个分桶（寄存器，每个 6 bit）&lt;/strong&gt; 分别记录，再取调和平均数消除偶然性，最终误差稳定在 &lt;strong&gt;0.81%&lt;/strong&gt; 左右。&lt;/li&gt;
&lt;li&gt;内存恒定 &lt;strong&gt;约 12KB&lt;/strong&gt;，与元素个数无关：统计 1 亿 UV 还是 10 亿 UV 都是 12KB。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PFADD uv:20210928 &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user:10086&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PFADD uv:20210928 &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user:10086&amp;#34;&lt;/span&gt;  &lt;span style=&#34;color:#75715e&#34;&gt;# 重复添加不影响计数&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;PFCOUNT uv:20210928             &lt;span style=&#34;color:#75715e&#34;&gt;# 估算 UV&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;局限：有误差、不可逆（不能取出具体是哪些用户）。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;怎么选&lt;/strong&gt;：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;需求&lt;/th&gt;
					&lt;th&gt;推荐&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;精确计数、算多日留存（交并集）、ID 是连续整数&lt;/td&gt;
					&lt;td&gt;Bitmap&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;只要近似 UV、数据量极大、ID 是任意字符串&lt;/td&gt;
					&lt;td&gt;HyperLogLog&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四redis-底层数据结构速览&#34;&gt;四、Redis 底层数据结构速览&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[Redis 数据类型] --&amp;gt; B[String]
    A --&amp;gt; C[Hash]
    A --&amp;gt; D[List]
    A --&amp;gt; E[Set]
    A --&amp;gt; F[ZSET]
    B --&amp;gt; G[SDS]
    C --&amp;gt; H[ziplist]
    C --&amp;gt; I[hashtable]
    D --&amp;gt; J[quicklist]
    E --&amp;gt; K[intset]
    E --&amp;gt; L[hashtable]
    F --&amp;gt; M[ziplist]
    F --&amp;gt; N[skiplist + dict]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;底层结构&lt;/th&gt;
					&lt;th&gt;特点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;SDS&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;带长度、预分配、二进制安全。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;ziplist&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;紧凑列表，小数据量时省内存；大数据量时改为 hashtable/skiplist。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;listpack&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;ziplist 的修正版（Redis 7+）：entry 只记自身长度，消灭连锁更新。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;quicklist&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;List 的底层，由多个紧凑块（Redis 7 前是 ziplist，之后是 listpack）节点组成的双向链表。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;hashtable&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;标准哈希表，渐进式 rehash 避免一次性阻塞。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;intset&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Set 在元素全为整数且数量少时的紧凑实现。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;skiplist&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;多层索引链表，实现 ZSET 的有序范围查询。&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;下面把 5 种核心结构「画」出来。&lt;/p&gt;
&lt;h3 id=&#34;41-sdssimple-dynamic-string&#34;&gt;4.1 SDS（Simple Dynamic String）&lt;/h3&gt;
&lt;p&gt;Redis 没有直接用 C 字符串，而是包了 SDS：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    L[&amp;#34;len&amp;lt;br/&amp;gt;已用长度（4B）&amp;lt;br/&amp;gt;= 5&amp;#34;] --- A[&amp;#34;alloc&amp;lt;br/&amp;gt;总容量&amp;lt;br/&amp;gt;= 8&amp;#34;] --- F[&amp;#34;flags&amp;lt;br/&amp;gt;编码位&amp;lt;br/&amp;gt;= 0&amp;#34;] --- B[&amp;#34;buf[]&amp;lt;br/&amp;gt;实际字符/二进制数据&amp;lt;br/&amp;gt;H e l l o \0 ...&amp;#34;]
    N1[&amp;#34;O(1) 取长度&amp;#34;] -.-&amp;gt; L
    N2[&amp;#34;末尾保留 \0&amp;lt;br/&amp;gt;兼容 C 字符串函数&amp;#34;] -.-&amp;gt; B&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;len&lt;/code&gt;：已用长度，取长度 &lt;code&gt;O(1)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;alloc&lt;/code&gt;：分配的总空间；&lt;code&gt;free = alloc - len&lt;/code&gt; 可减少内存重分配。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;二进制安全&lt;/strong&gt;：内容中间可以有 &lt;code&gt;\0&lt;/code&gt;，长度由 &lt;code&gt;len&lt;/code&gt; 决定。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;预分配&lt;/strong&gt;：修改时若 &lt;code&gt;free&lt;/code&gt; 足够就不 &lt;code&gt;realloc&lt;/code&gt;；不足时按 2 倍或 1MB 上限预分配。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;42-ziplist压缩列表&#34;&gt;4.2 ziplist（压缩列表）&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ziplist&lt;/code&gt; 是一块连续内存，把所有 entry 紧凑排列：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;zlbytes&amp;lt;br/&amp;gt;总字节数&amp;lt;br/&amp;gt;4B&amp;#34;] --- B[&amp;#34;zltail&amp;lt;br/&amp;gt;尾偏移量&amp;lt;br/&amp;gt;4B&amp;#34;] --- C[&amp;#34;zllen&amp;lt;br/&amp;gt;节点数&amp;lt;br/&amp;gt;2B&amp;#34;] --- D[&amp;#34;entry1&amp;lt;br/&amp;gt;变长字节&amp;#34;] --- E[&amp;#34;entry2&amp;lt;br/&amp;gt;变长字节&amp;#34;] --- F[&amp;#34;...&amp;#34;] --- G[&amp;#34;zlend&amp;lt;br/&amp;gt;0xFF&amp;lt;br/&amp;gt;1B&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;每个 entry 的内部结构：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;prevlen&amp;lt;br/&amp;gt;前节点长度&amp;lt;br/&amp;gt;1B 或 5B&amp;#34;] --- B[&amp;#34;encoding&amp;lt;br/&amp;gt;编码/长度类型&amp;lt;br/&amp;gt;1B 或更多&amp;#34;] --- C[&amp;#34;content&amp;lt;br/&amp;gt;实际数据&amp;lt;br/&amp;gt;变长&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;连续内存、无额外指针，所以&lt;strong&gt;省内存&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;插入/删除需要整体 &lt;code&gt;memmove&lt;/code&gt;，因此只用于&lt;strong&gt;小数据量&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Hash/List/ZSET&lt;/code&gt; 元素少且单元素短时先用 ziplist，超过阈值升级为 hashtable/skiplist。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;listpack（Redis 7 的替代者）&lt;/strong&gt;：ziplist 的致命伤就藏在 &lt;code&gt;prevlen&lt;/code&gt; 里——每个 entry 要记录&lt;strong&gt;前一个&lt;/strong&gt; entry 的长度：前 entry 小于 254 字节时 prevlen 占 1B，达到 254 字节时占 5B。于是一个 entry 从 253 涨到 254 字节，会让后面每个 entry 的 prevlen 从 1B 膨胀到 5B，膨胀又可能触发下一个 entry 继续膨胀——&lt;strong&gt;连锁更新&lt;/strong&gt;，最坏 O(N²)。&lt;/p&gt;
&lt;p&gt;listpack 的修法：&lt;strong&gt;每个 entry 只记录自己的总长度，且存在 entry 末尾&lt;/strong&gt;，entry 之间互不引用——改任何一个 entry 都不会波及邻居，连锁更新从根上消失。Redis 7 起 Hash/List/ZSET 的小对象编码全部换成 listpack，配置项同步改名（&lt;code&gt;hash-max-listpack-entries&lt;/code&gt; 等，旧的 &lt;code&gt;*-ziplist-*&lt;/code&gt; 名仍兼容）。面试回答「ziplist 和 listpack 的区别」，核心就是这一句：&lt;strong&gt;ziplist 记前驱长度所以连锁更新，listpack 记自身长度所以免疫&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;43-quicklist快速列表&#34;&gt;4.3 quicklist（快速列表）&lt;/h3&gt;
&lt;p&gt;Redis 3.2 后，&lt;code&gt;List&lt;/code&gt; 的底层是 &lt;strong&gt;quicklist = 双向链表 + ziplist&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    H[&amp;#34;head&amp;#34;] --&amp;gt; A[&amp;#34;ziplist A&amp;lt;br/&amp;gt;[a|b|c]&amp;lt;br/&amp;gt;count=3&amp;lt;br/&amp;gt;sz=...&amp;#34;] &amp;lt;--&amp;gt; B[&amp;#34;ziplist B&amp;lt;br/&amp;gt;[d|e|f|g]&amp;lt;br/&amp;gt;count=4&amp;lt;br/&amp;gt;sz=...&amp;#34;] &amp;lt;--&amp;gt; C[&amp;#34;...&amp;#34;]
    N[&amp;#34;每个节点是一个 ziplist&amp;lt;br/&amp;gt;节点之间用 prev/next 指针连接&amp;#34;] -.-&amp;gt; A&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;quicklistNode 结构：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;prev&amp;lt;br/&amp;gt;指针&amp;#34;] --- B[&amp;#34;next&amp;lt;br/&amp;gt;指针&amp;#34;] --- C[&amp;#34;zl&amp;lt;br/&amp;gt;ziplist&amp;#34;] --- D[&amp;#34;sz&amp;lt;br/&amp;gt;字节数&amp;#34;] --- E[&amp;#34;count&amp;lt;br/&amp;gt;元素个数&amp;#34;] --- F[&amp;#34;encoding&amp;lt;br/&amp;gt;是否压缩&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;结合了双向链表「插入删除快」和 ziplist「省内存」的优点。&lt;/li&gt;
&lt;li&gt;每个节点只存少量元素，避免单个 ziplist 太大导致 &lt;code&gt;memmove&lt;/code&gt; 开销。&lt;/li&gt;
&lt;li&gt;Redis 7 起节点从 ziplist 换成 listpack（动机见 §4.2），但 quicklist 的「链表 + 紧凑块」思想不变。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;44-intset整数集合&#34;&gt;4.4 intset（整数集合）&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Set&lt;/code&gt; 在元素&lt;strong&gt;全为整数且数量少&lt;/strong&gt;时使用 intset，否则升级为 &lt;code&gt;hashtable&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;encoding&amp;lt;br/&amp;gt;编码类型&amp;lt;br/&amp;gt;2B/4B/8B&amp;#34;] --- B[&amp;#34;length&amp;lt;br/&amp;gt;元素个数&amp;lt;br/&amp;gt;4B&amp;#34;] --- C[&amp;#34;contents[]&amp;lt;br/&amp;gt;有序整数数组&amp;lt;br/&amp;gt;[1] [3] [5] [7] [9] ...&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;有序数组，支持二分查找 &lt;code&gt;O(log N)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;所有元素用同一 encoding；加入更大范围整数时可能整体升级（如 int16 → int32），升级是 &lt;code&gt;O(N)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;无重复元素（Set 语义）。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;45-skiplist跳表&#34;&gt;4.5 skiplist（跳表）&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ZSET&lt;/code&gt; 同时维护 &lt;strong&gt;skiplist + dict&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;skiplist&lt;/code&gt;：按 score 排序，支持范围查询 / 排名 &lt;code&gt;O(log N)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dict&lt;/code&gt;：按 member 查 score，&lt;code&gt;O(1)&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;普通有序链表只能顺序扫描，跳表加了多层「快速通道」：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    subgraph L3[&amp;#34;level 3&amp;#34;]
        A3[&amp;#34;1&amp;#34;] --&amp;gt; B3[&amp;#34;9&amp;#34;] --&amp;gt; C3[&amp;#34;15&amp;#34;]
    end
    subgraph L2[&amp;#34;level 2&amp;#34;]
        A2[&amp;#34;1&amp;#34;] --&amp;gt; B2[&amp;#34;5&amp;#34;] --&amp;gt; C2[&amp;#34;9&amp;#34;] --&amp;gt; D2[&amp;#34;13&amp;#34;] --&amp;gt; E2[&amp;#34;15&amp;#34;]
    end
    subgraph L1[&amp;#34;level 1&amp;#34;]
        A1[&amp;#34;1&amp;#34;] --&amp;gt; B1[&amp;#34;3&amp;#34;] --&amp;gt; C1[&amp;#34;5&amp;#34;] --&amp;gt; D1[&amp;#34;7&amp;#34;] --&amp;gt; E1[&amp;#34;9&amp;#34;] --&amp;gt; F1[&amp;#34;11&amp;#34;] --&amp;gt; G1[&amp;#34;13&amp;#34;] --&amp;gt; H1[&amp;#34;15&amp;#34;]
    end
    A3 --- A2 --- A1
    B3 --- C2 --- E1
    C3 --- E2 --- H1
    HEAD[&amp;#34;头节点&amp;#34;] -.-&amp;gt; A1
    H1 -.-&amp;gt; TAIL[&amp;#34;尾节点&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;跳表节点结构：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    A[&amp;#34;member&amp;lt;br/&amp;gt;成员字符串&amp;lt;br/&amp;gt;（sds）&amp;#34;] --- B[&amp;#34;score&amp;lt;br/&amp;gt;分数&amp;lt;br/&amp;gt;（double）&amp;#34;] --- C[&amp;#34;forward[] 数组&amp;lt;br/&amp;gt;[L1指针， L2指针， L3指针， ...]&amp;lt;br/&amp;gt;最高层数 = 随机 1~32&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;概率平衡&lt;/strong&gt;：不用像红黑树那样做复杂旋转。&lt;/li&gt;
&lt;li&gt;查找 &lt;code&gt;score=11&lt;/code&gt; 时，先在高层大步跳跃，再逐层下降，平均 &lt;code&gt;O(log N)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;配合 dict 实现 &lt;code&gt;ZSCORE&lt;/code&gt; 的 &lt;code&gt;O(1)&lt;/code&gt; 查询。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五redis-持久化rdb-与-aof&#34;&gt;五、Redis 持久化：RDB 与 AOF&lt;/h2&gt;
&lt;p&gt;Redis 提供两种持久化方式，各有优劣。&lt;/p&gt;
&lt;h3 id=&#34;51-rdbredis-database&#34;&gt;5.1 RDB（Redis Database）&lt;/h3&gt;
&lt;p&gt;在指定时间间隔内将内存数据集快照写入磁盘。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;save &lt;span style=&#34;color:#ae81ff&#34;&gt;900&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;      &lt;span style=&#34;color:#75715e&#34;&gt;# 900 秒内至少 1 次修改则触发&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;save &lt;span style=&#34;color:#ae81ff&#34;&gt;300&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;     &lt;span style=&#34;color:#75715e&#34;&gt;# 300 秒内至少 10 次修改则触发&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;save &lt;span style=&#34;color:#ae81ff&#34;&gt;60&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10000&lt;/span&gt;   &lt;span style=&#34;color:#75715e&#34;&gt;# 60 秒内至少 10000 次修改则触发&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件紧凑，适合备份和灾难恢复。&lt;/li&gt;
&lt;li&gt;恢复速度快。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;两次快照之间数据可能丢失。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bgsave&lt;/code&gt; 通过 &lt;code&gt;fork()&lt;/code&gt; 子进程生成，大数据集时可能阻塞主线程。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;52-aofappend-only-file&#34;&gt;5.2 AOF（Append Only File）&lt;/h3&gt;
&lt;p&gt;将每次写操作命令追加到日志文件。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;appendonly yes
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;appendfsync everysec  &lt;span style=&#34;color:#75715e&#34;&gt;# 每秒刷盘一次（推荐）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# appendfsync always  # 每次写都刷盘，最安全但最慢&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# appendfsync no      # 由操作系统决定，最快但不安全&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;数据安全性更高，默认最多丢失 1 秒数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;缺点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;文件体积大。&lt;/li&gt;
&lt;li&gt;恢复速度慢。&lt;/li&gt;
&lt;li&gt;AOF 重写（rewrite）时会占用额外资源。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;53-混合持久化redis-40&#34;&gt;5.3 混合持久化（Redis 4.0+）&lt;/h3&gt;
&lt;p&gt;AOF 重写时先写入 RDB 全量数据，再追加增量 AOF，兼顾恢复速度和数据安全。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;aof-use-rdb-preamble yes&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[持久化选择] --&amp;gt; B[RDB]
    A --&amp;gt; C[AOF]
    A --&amp;gt; D[混合持久化]
    B --&amp;gt; E[适合备份/快速恢复]
    C --&amp;gt; F[适合低丢失容忍]
    D --&amp;gt; G[生产推荐]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;八股重点&lt;/strong&gt;：Redis 的持久化是「灾难恢复手段」，不是「主存储方案」。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六过期删除与内存淘汰&#34;&gt;六、过期删除与内存淘汰&lt;/h2&gt;
&lt;h3 id=&#34;61-过期删除策略&#34;&gt;6.1 过期删除策略&lt;/h3&gt;
&lt;p&gt;Redis 对过期 Key 采用两种策略结合：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;策略&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;优点&lt;/th&gt;
					&lt;th&gt;缺点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;惰性删除&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;访问 Key 时检查是否过期，过期则删除。&lt;/td&gt;
					&lt;td&gt;CPU 开销小&lt;/td&gt;
					&lt;td&gt;过期 Key 可能长期占用内存&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;定期删除&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;每隔一段时间随机抽查部分 Key 删除。&lt;/td&gt;
					&lt;td&gt;平衡内存与 CPU&lt;/td&gt;
					&lt;td&gt;仍可能有漏网之鱼&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;62-内存淘汰策略&#34;&gt;6.2 内存淘汰策略&lt;/h3&gt;
&lt;p&gt;当内存达到 &lt;code&gt;maxmemory&lt;/code&gt; 时，根据 &lt;code&gt;maxmemory-policy&lt;/code&gt; 决定如何淘汰：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;策略&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;noeviction&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;不淘汰，写操作直接报错。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;allkeys-lru&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;所有 Key 中淘汰最近最少使用。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;volatile-lru&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;只在设置了 TTL 的 Key 中淘汰最近最少使用。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;allkeys-random&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;所有 Key 中随机淘汰。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;volatile-random&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;只在设置了 TTL 的 Key 中随机淘汰。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;volatile-ttl&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;淘汰即将过期的 Key。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;allkeys-lfu&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;所有 Key 中淘汰使用频率最低的。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;volatile-lfu&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;只在设置了 TTL 的 Key 中淘汰使用频率最低的。&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;生产建议&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;必须设置 &lt;code&gt;maxmemory&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;缓存场景推荐 &lt;code&gt;allkeys-lru&lt;/code&gt; 或 &lt;code&gt;allkeys-lfu&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;有冷热区分时可用 &lt;code&gt;volatile-lru&lt;/code&gt;，但必须给 Key 设置 TTL。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七redis-高可用架构&#34;&gt;七、Redis 高可用架构&lt;/h2&gt;
&lt;h3 id=&#34;71-主从复制replication&#34;&gt;7.1 主从复制（Replication）&lt;/h3&gt;
&lt;p&gt;一主多从，读流量分摊到从节点。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    M[Master&amp;lt;br/&amp;gt;写 + 读] --&amp;gt;|全量/增量同步| S1[Slave 1&amp;lt;br/&amp;gt;读]
    M --&amp;gt; S2[Slave 2&amp;lt;br/&amp;gt;读]
    M --&amp;gt; S3[Slave 3&amp;lt;br/&amp;gt;读]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;复制过程&lt;/strong&gt;：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Slave 发送 &lt;code&gt;PSYNC runId offset&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;如果 runId 一致且 offset 在复制积压缓冲区中，则&lt;strong&gt;增量同步&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;否则&lt;strong&gt;全量同步&lt;/strong&gt;：Master 执行 &lt;code&gt;bgsave&lt;/code&gt; 生成 RDB，发送给 Slave。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;复制延迟问题&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;主从异步复制必然存在延迟。&lt;/li&gt;
&lt;li&gt;读从库时可能读到旧数据，需要业务容忍或强制走主库。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;72-哨兵sentinel&#34;&gt;7.2 哨兵（Sentinel）&lt;/h3&gt;
&lt;p&gt;实现自动故障转移，解决主节点宕机问题。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph Sentinel 集群
        Se1[Sentinel 1]
        Se2[Sentinel 2]
        Se3[Sentinel 3]
    end
    M[Master] --&amp;gt; S1[Slave 1]
    M --&amp;gt; S2[Slave 2]
    Se1 --&amp;gt;|监控| M
    Se2 --&amp;gt;|监控| M
    Se3 --&amp;gt;|监控| M
    Se1 -.-&amp;gt; Se2
    Se2 -.-&amp;gt; Se3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;核心机制&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;主观下线（SDOWN）&lt;/strong&gt;：单个 Sentinel 认为 Master 不可用。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;客观下线（ODOWN）&lt;/strong&gt;：多数 Sentinel 达成一致。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Leader 选举&lt;/strong&gt;：Sentinel 之间通过 Raft 算法选举出执行故障转移的 Leader。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;故障转移&lt;/strong&gt;：选一个 Slave 提升为 Master，通知其他 Slave 和客户端。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;73-cluster-集群&#34;&gt;7.3 Cluster 集群&lt;/h3&gt;
&lt;p&gt;Redis Cluster 采用&lt;strong&gt;无中心架构&lt;/strong&gt;，数据按槽位分布。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph Redis Cluster
        N1[Node 1&amp;lt;br/&amp;gt;0-5460]
        N2[Node 2&amp;lt;br/&amp;gt;5461-10922]
        N3[Node 3&amp;lt;br/&amp;gt;10923-16383]
    end
    N1 &amp;lt;--&amp;gt; N2
    N2 &amp;lt;--&amp;gt; N3
    N1 &amp;lt;--&amp;gt; N3&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键概念&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;集群共有 &lt;strong&gt;16384 个槽（slot）&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;每个 Key 通过 &lt;code&gt;CRC16(key) % 16384&lt;/code&gt; 计算所属槽。&lt;/li&gt;
&lt;li&gt;每个 Master 负责一部分槽，可配置 Slave 做高可用。&lt;/li&gt;
&lt;li&gt;客户端访问错误节点时，会收到 &lt;code&gt;MOVED&lt;/code&gt; 或 &lt;code&gt;ASK&lt;/code&gt; 重定向。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;MOVED vs ASK&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MOVED&lt;/strong&gt;：槽已经永久迁移到目标节点。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ASK&lt;/strong&gt;：槽正在迁移中，先访问目标节点一次。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八事务与-lua-脚本&#34;&gt;八、事务与 Lua 脚本&lt;/h2&gt;
&lt;h3 id=&#34;81-事务multiexecdiscardwatch&#34;&gt;8.1 事务（MULTI/EXEC/DISCARD/WATCH）&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;MULTI
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SET a &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;INCR b
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EXEC&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;命令按顺序入队，EXEC 时一次性执行。&lt;/li&gt;
&lt;li&gt;执行期间不会被其他客户端打断。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;但没有回滚机制&lt;/strong&gt;：如果某条命令语法错误，整个事务不执行；如果运行时错误（如 INCR 字符串），后续命令仍会执行。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;82-lua-脚本&#34;&gt;8.2 Lua 脚本&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;EVAL &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;return redis.call(&amp;#39;incr&amp;#39;, KEYS[1])&amp;#34;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; counter&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;特点&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;整个脚本作为一个整体在单线程中原子执行。&lt;/li&gt;
&lt;li&gt;适用于需要多个命令且要保证原子性的场景，如秒杀、分布式锁、限流。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;集群模式下所有 Key 必须落在同一 slot&lt;/strong&gt;，否则需要加 hash tag，如 &lt;code&gt;{user}:1001:profile&lt;/code&gt;。&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九缓存经典问题&#34;&gt;九、缓存经典问题&lt;/h2&gt;
&lt;h3 id=&#34;91-缓存穿透&#34;&gt;9.1 缓存穿透&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：查询不存在的数据，缓存不命中，请求直接打到数据库。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存空值（设置短 TTL）。&lt;/li&gt;
&lt;li&gt;使用布隆过滤器预先判断 Key 是否存在。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;92-缓存击穿&#34;&gt;9.2 缓存击穿&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：热点 Key 突然过期，大量请求同时打到数据库。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;热点 Key 设置永不过期，异步更新。&lt;/li&gt;
&lt;li&gt;加互斥锁（SETNX），只允许一个线程重建缓存。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;93-缓存雪崩&#34;&gt;9.3 缓存雪崩&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：大量 Key 同时过期，或 Redis 宕机，数据库压力陡增。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;解法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;给 TTL 加随机值，避免同时失效。&lt;/li&gt;
&lt;li&gt;多级缓存（本地 + Redis）。&lt;/li&gt;
&lt;li&gt;熔断降级。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;94-缓存与数据库一致性&#34;&gt;9.4 缓存与数据库一致性&lt;/h3&gt;
&lt;p&gt;常见策略：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;策略&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;问题&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Cache Aside&lt;/td&gt;
					&lt;td&gt;读：先读缓存，未命中读 DB 并写回缓存；写：先更新 DB，再删缓存。&lt;/td&gt;
					&lt;td&gt;短暂不一致&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Read/Write Through&lt;/td&gt;
					&lt;td&gt;由缓存层统一维护 DB 和缓存。&lt;/td&gt;
					&lt;td&gt;实现复杂&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Write Behind&lt;/td&gt;
					&lt;td&gt;先写缓存，异步写 DB。&lt;/td&gt;
					&lt;td&gt;可能丢数据&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;最常用的是 Cache Aside&lt;/strong&gt;，配合「先更新数据库，再删除缓存」+ 延时双删 + 消息队列补偿，能在大多数场景下保证最终一致。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十redis-在-iptv-系统中的角色&#34;&gt;十、Redis 在 IPTV 系统中的角色&lt;/h2&gt;
&lt;p&gt;IPTV 系统的特点是：&lt;strong&gt;高并发读、强实时性、热点集中、写流量脉冲式&lt;/strong&gt;。用户在换台、看 EPG、播放点播、抢购付费节目时，系统需要在毫秒级返回结果。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TB
    subgraph 用户层
        A[机顶盒 / 手机 / Web]
    end

    subgraph 接入层
        B[CDN / 边缘节点]
        C[API Gateway]
    end

    subgraph 应用层
        D[鉴权服务]
        E[EPG 服务]
        F[播放调度服务]
        G[订单 / 秒杀服务]
    end

    subgraph 数据层
        H[(MySQL / 长视频元信息)]
        I[(对象存储 OSS)]
        J[(Redis 缓存 + 实时计算)]
    end

    A --&amp;gt; B --&amp;gt; C
    C --&amp;gt; D &amp;amp; E &amp;amp; F &amp;amp; G
    D --&amp;gt; J
    E --&amp;gt; J
    F --&amp;gt; J
    G --&amp;gt; J
    E --&amp;gt; H
    F --&amp;gt; I
    G --&amp;gt; H
    J -.-&amp;gt; H&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;场景&lt;/th&gt;
					&lt;th&gt;Redis 数据结构&lt;/th&gt;
					&lt;th&gt;作用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;用户会话 / Token&lt;/td&gt;
					&lt;td&gt;String + EXPIRE&lt;/td&gt;
					&lt;td&gt;毫秒级鉴权&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;EPG 节目单缓存&lt;/td&gt;
					&lt;td&gt;Hash / JSON String&lt;/td&gt;
					&lt;td&gt;减少数据库查询&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;热播榜单 / 推荐位&lt;/td&gt;
					&lt;td&gt;Sorted Set (ZSET)&lt;/td&gt;
					&lt;td&gt;实时排序&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;播放限速 / 防刷&lt;/td&gt;
					&lt;td&gt;Lua + INCR + EXPIRE&lt;/td&gt;
					&lt;td&gt;滑动窗口限流&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;秒杀 / 付费解锁&lt;/td&gt;
					&lt;td&gt;SETNX + DECR + Lua&lt;/td&gt;
					&lt;td&gt;库存扣减&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;消息通知 / 任务队列&lt;/td&gt;
					&lt;td&gt;List / Stream&lt;/td&gt;
					&lt;td&gt;异步解耦&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十一iptv-项目-demo&#34;&gt;十一、IPTV 项目 Demo&lt;/h2&gt;
&lt;h3 id=&#34;111-用户会话缓存&#34;&gt;11.1 用户会话缓存&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; redis
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; uuid
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; time
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; redis&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;Redis(host&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt;, port&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;6379&lt;/span&gt;, db&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, decode_responses&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;login&lt;/span&gt;(user_id: str, device_id: str, ttl: int &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;3600&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; str:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    token &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; str(uuid&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;uuid4())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;session:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;token&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;hset(key, mapping&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt;: user_id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;device_id&amp;#34;&lt;/span&gt;: device_id,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;login_at&amp;#34;&lt;/span&gt;: str(time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;time())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;expire(key, ttl)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; token
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;auth&lt;/span&gt;(token: str) &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; dict &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;hgetall(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;session:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;token&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;112-epg-节目单缓存&#34;&gt;11.2 EPG 节目单缓存&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; json
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;from&lt;/span&gt; datetime &lt;span style=&#34;color:#f92672&#34;&gt;import&lt;/span&gt; datetime
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;cache_epg&lt;/span&gt;(channel_id: str, programs: list[dict]):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;epg:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;channel_id&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;today()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;%Y%m&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%d&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pipe &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;pipeline()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; p &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; programs:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        pipe&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;hset(key, p[&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;start_time&amp;#39;&lt;/span&gt;], json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;dumps(p))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pipe&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;expire(key, &lt;span style=&#34;color:#ae81ff&#34;&gt;86400&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    pipe&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;execute()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;get_program&lt;/span&gt;(channel_id: str, start_time: str) &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; dict &lt;span style=&#34;color:#f92672&#34;&gt;|&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    data &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;hget(&lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;epg:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;channel_id&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;datetime&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;today()&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;strftime(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;%Y%m&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;%d&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&lt;/span&gt;)&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;, start_time)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;loads(data) &lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; data &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;None&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;113-热播排行榜&#34;&gt;11.3 热播排行榜&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;record_view&lt;/span&gt;(program_id: str, score: float &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;zincrby(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hot:tv:daily&amp;#34;&lt;/span&gt;, score, program_id)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;top_n&lt;/span&gt;(n: int &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; list[tuple[str, float]]:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;zrevrange(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;hot:tv:daily&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, n &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, withscores&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;114-滑动窗口限流&#34;&gt;11.4 滑动窗口限流&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;LIMIT_SCRIPT &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;local key = KEYS[1]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;local window = tonumber(ARGV[1])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;local limit = tonumber(ARGV[2])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;local now = tonumber(ARGV[3])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;local min = now - window
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;redis.call(&amp;#39;zremrangebyscore&amp;#39;, key, &amp;#39;-inf&amp;#39;, min)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;local count = redis.call(&amp;#39;zcard&amp;#39;, key)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;if count &amp;lt; limit then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    redis.call(&amp;#39;zadd&amp;#39;, key, now, now)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    redis.call(&amp;#39;expire&amp;#39;, key, math.ceil(window / 1000))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    return 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;else
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    return 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;limiter &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;register_script(LIMIT_SCRIPT)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;allow&lt;/span&gt;(user_id: str, max_qps: int &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;, window_ms: int &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;) &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; bool:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;rate_limit:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;user_id&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    now &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; int(time&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;time() &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; bool(limiter(keys&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;[key], args&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;[window_ms, max_qps, now]))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;115-秒杀--限时解锁&#34;&gt;11.5 秒杀 / 限时解锁&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SECKILL_SCRIPT &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;local key = KEYS[1]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;local user_id = ARGV[1]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;local stock = tonumber(redis.call(&amp;#39;get&amp;#39;, key) or &amp;#39;0&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;if stock &amp;lt;= 0 then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    return -1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;if redis.call(&amp;#39;sismember&amp;#39;, key .. &amp;#39;:users&amp;#39;, user_id) == 1 then
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;    return 0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;end
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;redis.call(&amp;#39;decr&amp;#39;, key)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;redis.call(&amp;#39;sadd&amp;#39;, key .. &amp;#39;:users&amp;#39;, user_id)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;return 1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;seckill &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;register_script(SECKILL_SCRIPT)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;def&lt;/span&gt; &lt;span style=&#34;color:#a6e22e&#34;&gt;try_buy&lt;/span&gt;(activity_id: str, user_id: str, total_stock: int) &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; int:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    key &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;f&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;seckill:&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;{&lt;/span&gt;activity_id&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;:stock&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;setnx(key, total_stock)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;return&lt;/span&gt; int(seckill(keys&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;[key], args&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;[user_id]))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;十二为什么-redis-不适合做长期存储&#34;&gt;十二、为什么 Redis 不适合做长期存储？&lt;/h2&gt;
&lt;h3 id=&#34;121-持久化不是主存储&#34;&gt;12.1 持久化不是主存储&lt;/h3&gt;
&lt;p&gt;RDB 会丢数据，AOF 文件大且恢复慢。Redis 的持久化定位是「灾难恢复」，不是「可靠主存」。&lt;/p&gt;
&lt;h3 id=&#34;122-内存成本过高&#34;&gt;12.2 内存成本过高&lt;/h3&gt;
&lt;p&gt;内存价格是 SSD 的 10-50 倍。IPTV 的媒资、播放记录、订单流水动辄几十 TB，全放 Redis 不现实。&lt;/p&gt;
&lt;h3 id=&#34;123-单线程执行风险&#34;&gt;12.3 单线程执行风险&lt;/h3&gt;
&lt;p&gt;大数据集下，一个 &lt;code&gt;KEYS *&lt;/code&gt;、&lt;code&gt;HGETALL&lt;/code&gt; 或慢查询可能阻塞整个实例。&lt;/p&gt;
&lt;h3 id=&#34;124-缺乏复杂查询能力&#34;&gt;12.4 缺乏复杂查询能力&lt;/h3&gt;
&lt;p&gt;Redis 不支持 SQL、JOIN、二级索引。长期业务数据需要分析统计时，应使用 MySQL / PostgreSQL / ClickHouse。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结论&lt;/strong&gt;：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Redis 适合「热数据 + 临时状态 + 实时计算」，价值在于速度；长期存储应交给磁盘数据库 + 对象存储。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十三redis-容易踩坑的地方&#34;&gt;十三、Redis 容易踩坑的地方&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;坑&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;解法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;大 Key&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;String &amp;gt; 10MB，集合元素百万级&lt;/td&gt;
					&lt;td&gt;拆分、分片、UNLINK 异步删除&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;热 Key&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;单个 Key 被高频访问&lt;/td&gt;
					&lt;td&gt;本地缓存、Key 分片、Cluster&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;缓存穿透/击穿/雪崩&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;缓存失效导致 DB 被打挂&lt;/td&gt;
					&lt;td&gt;布隆过滤器、互斥锁、随机 TTL&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;未设置 maxmemory&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;内存打满后写操作报错&lt;/td&gt;
					&lt;td&gt;设置 maxmemory 和淘汰策略&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;误用 KEYS&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;KEYS *&lt;/code&gt; 扫描全库阻塞主线程&lt;/td&gt;
					&lt;td&gt;生产禁用，改用 SCAN&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Lua 跨 slot&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;Cluster 下脚本 Key 不在同一 slot&lt;/td&gt;
					&lt;td&gt;使用 hash tag &lt;code&gt;{user}:1001:xxx&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;事务没有回滚&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;EXEC 后某命令失败不会撤销前面命令&lt;/td&gt;
					&lt;td&gt;业务层保证幂等，或用 Lua&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;主从延迟&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;读从库可能读到旧数据&lt;/td&gt;
					&lt;td&gt;对一致性要求高的读走主库&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十四redis-做消息队列的方法&#34;&gt;十四、Redis 做消息队列的方法&lt;/h2&gt;
&lt;h3 id=&#34;141-list轻量队列&#34;&gt;14.1 List：轻量队列&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart LR
    P[Producer] --&amp;gt;|LPUSH queue:transcode task| R[(Redis List)]
    C[Consumer] --&amp;gt;|BRPOP queue:transcode 5| R
    C --&amp;gt; S[执行转码]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;lpush(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;queue:transcode&amp;#34;&lt;/span&gt;, json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;dumps({&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;video_id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;v123&amp;#34;&lt;/span&gt;}))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;_, task &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;brpop(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;queue:transcode&amp;#34;&lt;/span&gt;, timeout&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：简单、阻塞读。
&lt;strong&gt;缺点&lt;/strong&gt;：无 ACK、无多消费者组、无消息回溯。&lt;/p&gt;
&lt;h3 id=&#34;142-pubsub实时广播&#34;&gt;14.2 Pub/Sub：实时广播&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;publish(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;live:start&amp;#34;&lt;/span&gt;, json&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;dumps({&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;channel_id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;cctv1&amp;#34;&lt;/span&gt;}))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：实时、多订阅者。
&lt;strong&gt;缺点&lt;/strong&gt;：不持久化，消费者断线期间消息丢失。&lt;/p&gt;
&lt;h3 id=&#34;143-stream生产级队列&#34;&gt;14.3 Stream：生产级队列&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;xadd(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mq:events&amp;#34;&lt;/span&gt;, {&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;event&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;video:play&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;user_id&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;u10086&amp;#34;&lt;/span&gt;})
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;xgroup_create(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mq:events&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;group_log&amp;#34;&lt;/span&gt;, id&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;0&amp;#34;&lt;/span&gt;, mkstream&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;messages &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;xreadgroup(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    groupname&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;group_log&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    consumername&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;worker_1&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    streams&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;{&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mq:events&amp;#34;&lt;/span&gt;: &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&amp;gt;&amp;#34;&lt;/span&gt;},
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    count&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    block&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;5000&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; stream, entries &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; messages:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; msg_id, fields &lt;span style=&#34;color:#f92672&#34;&gt;in&lt;/span&gt; entries:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        process(fields)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        r&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;xack(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;mq:events&amp;#34;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;group_log&amp;#34;&lt;/span&gt;, msg_id)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;优点&lt;/strong&gt;：支持消费者组、ACK、消息回溯。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方式&lt;/th&gt;
					&lt;th&gt;推荐场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;List&lt;/td&gt;
					&lt;td&gt;简单任务队列&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Pub/Sub&lt;/td&gt;
					&lt;td&gt;实时广播&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Stream&lt;/td&gt;
					&lt;td&gt;生产级消息队列&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十五总结&#34;&gt;十五、总结&lt;/h2&gt;
&lt;p&gt;《Redis in Action》的核心思想可以概括为一句话：&lt;strong&gt;把 Redis 当成内存里的数据结构工具箱，而不是数据库&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Redis 适合&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;缓存加速（会话、配置、热点数据）。&lt;/li&gt;
&lt;li&gt;实时计算（排行榜、计数器、限流、秒杀）。&lt;/li&gt;
&lt;li&gt;异步解耦（List / Pub/Sub / Stream）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Redis 不适合&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;长期持久化存储。&lt;/li&gt;
&lt;li&gt;复杂查询与大数据分析。&lt;/li&gt;
&lt;li&gt;超大数据集主存储。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;避免踩坑的关键是：&lt;strong&gt;控制 Key 大小、设置合理 TTL、配置淘汰策略、慎用阻塞命令、理解持久化边界&lt;/strong&gt;。Redis 用好了是神兵利器，用错了就是生产事故的导火索。&lt;/p&gt;
</description>
    </item>
    <item>
      <title>Elasticsearch 从原理到 IPTV 媒资搜索实战：基于 ftmao-meta 项目的完整复盘</title>
      <link>https://ftmao.com/blog/posts/elasticsearch-in-action-iptv/</link>
      <pubDate>Sat, 28 Aug 2021 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/elasticsearch-in-action-iptv/</guid>
      <description>&lt;h1 id=&#34;elasticsearch-从原理到-iptv-媒资搜索实战基于-ftmao-meta-项目的完整复盘&#34;&gt;Elasticsearch 从原理到 IPTV 媒资搜索实战：基于 ftmao-meta 项目的完整复盘&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;以我们线上运行的 IPTV 媒资元数据服务 &lt;strong&gt;ftmao-meta&lt;/strong&gt; 为样本，讲清楚一个真实的 ES（实际为 AWS 托管 OpenSearch）应用是怎么设计索引、同步数据、支撑搜索和下游订阅的。所有配置、代码、索引设计均来自项目本身，踩过的坑也一并奉上。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一项目背景这是一个什么系统&#34;&gt;一、项目背景：这是一个什么系统&lt;/h2&gt;
&lt;p&gt;ftmao-meta 是一个面向多地区运营的 IPTV/OTT 视频内容平台的&lt;strong&gt;媒资元数据微服务&lt;/strong&gt;，Spring Boot 3.3 + Java 21，单模块 Gradle 工程，包结构 &lt;code&gt;gtt.ftmao.meta&lt;/code&gt;，分 &lt;code&gt;api / app / bean / core&lt;/code&gt; 四层。它管理的数据包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;影视元数据&lt;/strong&gt;：电影、剧集、季、集、短视频的多语言标题、简介、海报、演职员、分级、票房；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;演职人员&lt;/strong&gt;：多语言姓名、热度值（pgcHot / ugcHot / imdbHot）、代表作；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;物料&lt;/strong&gt;：剧照海报（artwork）、预告片（trailer）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;C 端搜索&lt;/strong&gt;：全文检索 + 输入自动补全。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些数据量巨大（千万级文档）、多语言（英语/西语/葡语，服务拉美市场）、多租户（多家运营商公司共用），而且需要被下游多个服务&lt;strong&gt;增量订阅&lt;/strong&gt;。MySQL 是事实源（Source of Truth），ES 承担所有读路径的检索。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;技术选型上的关键事实&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;后端是 &lt;strong&gt;AWS 托管 OpenSearch&lt;/strong&gt;，客户端用官方 &lt;code&gt;opensearch-java 2.8.1&lt;/code&gt;（&lt;code&gt;org.opensearch.client.opensearch.OpenSearchClient&lt;/code&gt;），&lt;strong&gt;没有用 spring-data-elasticsearch，也没有 RestHighLevelClient&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;全文检索与自动补全能力封装在公司内部库 &lt;code&gt;searchengine-api/core/sync&lt;/code&gt; 里，业务代码只调用 &lt;code&gt;SearchApi&lt;/code&gt; / &lt;code&gt;SearchSync&lt;/code&gt; 门面；&lt;/li&gt;
&lt;li&gt;数据同步&lt;strong&gt;不走 MQ&lt;/strong&gt;（Kafka 依赖在启动类里被显式排除），而是&amp;quot;MySQL 任务表 + 定时轮询 + 分布式锁&amp;quot;。&lt;/li&gt;
&lt;/ul&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TB
    subgraph 上游[&amp;#34;上游数据来源&amp;#34;]
        BOSS[&amp;#34;运营后台 / 内容录入&amp;#34;]
        PP[&amp;#34;PP 开放平台&amp;lt;br/&amp;gt;（REST 拉取外部媒资）&amp;#34;]
    end

    subgraph META[&amp;#34;ftmao-meta 服务&amp;#34;]
        API[&amp;#34;REST API 层&amp;#34;]
        SVC[&amp;#34;业务 Service&amp;lt;br/&amp;gt;MetaService / ArtworkService ...&amp;#34;]
        JOB[&amp;#34;定时 Job&amp;lt;br/&amp;gt;PpMetaSyncJob 每 15s&amp;#34;]
        LOCK[&amp;#34;分布式锁&amp;lt;br/&amp;gt;AsyncJobExecutorManager&amp;#34;]
    end

    subgraph STORE[&amp;#34;存储层&amp;#34;]
        MYSQL[(&amp;#34;MySQL&amp;lt;br/&amp;gt;事实源 + 同步任务表&amp;#34;)]
        subgraph OS[&amp;#34;AWS OpenSearch&amp;#34;]
            DETAIL[&amp;#34;meta_detail_index&amp;lt;br/&amp;gt;媒资详情（重索引）&amp;#34;]
            PERSON[&amp;#34;meta_person_index&amp;lt;br/&amp;gt;人物详情&amp;#34;]
            ART[&amp;#34;meta_artwork_index&amp;lt;br/&amp;gt;剧照物料&amp;#34;]
            TRAILER[&amp;#34;meta_trailer_index&amp;lt;br/&amp;gt;预告片&amp;#34;]
            SEARCH[&amp;#34;search_engine_index&amp;lt;br/&amp;gt;C端搜索（轻索引）&amp;#34;]
        end
    end

    subgraph DOWN[&amp;#34;下游消费方&amp;#34;]
        PORTAL[&amp;#34;门户/推荐服务&amp;#34;]
        SUB[&amp;#34;增量订阅方&amp;lt;br/&amp;gt;（时间水位拉取变更）&amp;#34;]
    end

    BOSS --&amp;gt; SVC
    PP --&amp;gt; JOB
    SVC --&amp;gt;|&amp;#34;事务内：先写库，再直写 ES&amp;#34;| MYSQL
    SVC --&amp;gt; DETAIL
    SVC --&amp;gt; SEARCH
    JOB --&amp;gt; LOCK --&amp;gt; MYSQL
    JOB --&amp;gt;|&amp;#34;批量灌入&amp;#34;| SEARCH
    API --&amp;gt;|&amp;#34;全文搜索/补全&amp;#34;| SEARCH
    API --&amp;gt;|&amp;#34;mget 回查详情&amp;#34;| DETAIL
    API --&amp;gt; PERSON &amp;amp; ART &amp;amp; TRAILER
    DETAIL --&amp;gt;|&amp;#34;lastModifiedIndicator 水位&amp;#34;| SUB
    SEARCH --&amp;gt; PORTAL&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;二先补原理理解后面设计决策的三个支点&#34;&gt;二、先补原理：理解后面设计决策的三个支点&lt;/h2&gt;
&lt;p&gt;在进入项目细节前，用三句话回顾《Elasticsearch in Action》里最重要的原理，后面每个设计决策都会用到它们。&lt;/p&gt;
&lt;h3 id=&#34;21-倒排索引与分词&#34;&gt;2.1 倒排索引与分词&lt;/h3&gt;
&lt;p&gt;ES 搜索快的根本原因是&lt;strong&gt;倒排索引&lt;/strong&gt;：term → 文档列表。而文本进入倒排索引前要经过分析链：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[&amp;#34;原始文本&amp;lt;br/&amp;gt;O Auto da Compadecida&amp;#34;] --&amp;gt; B[&amp;#34;Character Filter&amp;lt;br/&amp;gt;变音符号映射 á→a&amp;#34;]
    B --&amp;gt; C[&amp;#34;Tokenizer&amp;lt;br/&amp;gt;standard 分词&amp;#34;]
    C --&amp;gt; D[&amp;#34;Token Filter&amp;lt;br/&amp;gt;lowercase + stopword + stemmer&amp;#34;]
    D --&amp;gt; E[&amp;#34;词项 Term&amp;lt;br/&amp;gt;auto / compadecida&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;一个字段建索引时用哪个 analyzer、搜索时用哪个 analyzer，直接决定了&amp;quot;搜不搜得到&amp;quot;。&lt;strong&gt;这就是为什么 mapping 必须在灌数据之前设计好&lt;/strong&gt;——改 analyzer 意味着重建索引。&lt;/p&gt;
&lt;h3 id=&#34;22-近实时nrt与-refresh&#34;&gt;2.2 近实时（NRT）与 refresh&lt;/h3&gt;
&lt;p&gt;文档写入后先进内存缓冲区，默认 1 秒后 refresh 生成 segment 才可被搜索。批量灌数据时调大 &lt;code&gt;refresh_interval&lt;/code&gt;、或显式控制 &lt;code&gt;refresh&lt;/code&gt; 时机，是写入性能优化的第一手段——后面会看到项目里批量写入怎么用它。&lt;/p&gt;
&lt;h3 id=&#34;23-query-与-filter-的区别&#34;&gt;2.3 Query 与 Filter 的区别&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Query&lt;/strong&gt;：算相关性得分 &lt;code&gt;_score&lt;/code&gt;，用于全文匹配排序；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Filter&lt;/strong&gt;：只判断 yes/no，&lt;strong&gt;不算分、结果可被缓存&lt;/strong&gt;，用于精确过滤。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;项目里几乎所有查询都是&amp;quot;filter 套 query&amp;quot;的 bool 结构：关键词进 &lt;code&gt;must&lt;/code&gt;，租户、类型、软删除标记进 &lt;code&gt;filter&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三索引设计一个搜索索引--四个详情索引&#34;&gt;三、索引设计：一个搜索索引 + 四个详情索引&lt;/h2&gt;
&lt;h3 id=&#34;31-索引清单&#34;&gt;3.1 索引清单&lt;/h3&gt;
&lt;p&gt;项目中&lt;strong&gt;没有任何 &lt;code&gt;@Document&lt;/code&gt; 注解或 mapping JSON 文件&lt;/strong&gt;，索引 DDL 采用类似 Liquibase 的 changelog 形式管理（&lt;code&gt;changelog/elasticsearch/*.yaml&lt;/code&gt;，HTTP method + path + body，由外部部署工具执行），别名字段统一管理：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;代码中的别名&lt;/th&gt;
					&lt;th&gt;实体索引&lt;/th&gt;
					&lt;th&gt;分片&lt;/th&gt;
					&lt;th&gt;存什么&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;meta_detail_index&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ftmao-meta-detail-1&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;6 主 2 副&lt;/td&gt;
					&lt;td&gt;媒资详情全集&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;meta_person_index&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ftmao-meta-person-detail&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;6 主 2 副&lt;/td&gt;
					&lt;td&gt;演职人员详情&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;meta_artwork_index&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ftmao-meta-media-artwork&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;6 主 2 副&lt;/td&gt;
					&lt;td&gt;剧照物料&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;meta_trailer_index&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ftmao-meta-media-trailer&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;6 主 2 副&lt;/td&gt;
					&lt;td&gt;预告片&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;search_engine_index&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;ftmao-meta-search-engine&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;1 分片&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;C 端搜索/补全&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;别名与文档类的映射集中在一个枚举里（&lt;code&gt;OpenSearchIndexEnum&lt;/code&gt;），业务代码只认别名——这是&amp;quot;换索引不改代码&amp;quot;的基本功，比如 &lt;code&gt;ftmao-meta-detail-1&lt;/code&gt; 后面出现 &lt;code&gt;-2&lt;/code&gt;，只需要 reindex 后切别名。&lt;/p&gt;
&lt;h3 id=&#34;32-详情索引重存储轻检索&#34;&gt;3.2 详情索引：重存储、轻检索&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;meta_detail_index&lt;/code&gt; 的 mapping 有个鲜明特征：&lt;strong&gt;几乎所有字段都是 keyword，标题、剧情简介都是 keyword + nested，不参与分词检索&lt;/strong&gt;，大量 nested 对象（displayTitles / plots / posters / casts）配 &lt;code&gt;dynamic: false&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;为什么？因为详情索引的职责是**&amp;ldquo;按 ID/结构化条件精确取数&amp;rdquo;**，不是全文搜索。keyword 不做分词，聚合、terms 过滤、精确匹配都快；&lt;code&gt;dynamic: false&lt;/code&gt; 防止上游数据乱塞字段把 mapping 撑爆（mapping explosion 是生产事故常客）。&lt;/p&gt;
&lt;h3 id=&#34;33-搜索索引整个项目最精彩的部分&#34;&gt;3.3 搜索索引：整个项目最精彩的部分&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;search_engine_index&lt;/code&gt; 只有 1 个分片（轻量、追求查询 RT），但它的 analysis 配置是整个项目技术含量最高的地方。以主搜索字段 &lt;code&gt;mainTitle&lt;/code&gt; 为例：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# 摘自 changelog（简化）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;mainTitle&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;text&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;store&lt;/span&gt;: &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;term_vector&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;with_positions_offsets  &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 为 fast-vector-highlighter 高亮做准备&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;analyzer&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;en_es_pt                    &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 英/西/葡三语混合分词器&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#f92672&#34;&gt;fields&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;cut_word&lt;/span&gt;:    { &lt;span style=&#34;color:#f92672&#34;&gt;analyzer&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;en_es_pt }             &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 正常分词匹配&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;all_letters&lt;/span&gt;: { &lt;span style=&#34;color:#f92672&#34;&gt;analyzer&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;N_gram(4-15) }         &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 长 ngram：前缀/模糊匹配&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#ae81ff&#34;&gt;word_letters:{ analyzer: N_gram(2-10) }         &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 短 ngram：更激进的模糊匹配&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#f92672&#34;&gt;origin&lt;/span&gt;:      { &lt;span style=&#34;color:#f92672&#34;&gt;type: keyword, normalizer&lt;/span&gt;: &lt;span style=&#34;color:#ae81ff&#34;&gt;lowercase } &lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;# 精确匹配/排序&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;配套自定义组件：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;多语分析器 &lt;code&gt;en_es_pt&lt;/code&gt;&lt;/strong&gt;：standard tokenizer + 英语/西语/葡语三套 stopword + stemmer；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;变音符号 char_filter&lt;/strong&gt;：把 &lt;code&gt;áéíóúàèç&lt;/code&gt; 等西葡变音符号映射为 ASCII——拉美用户搜 &lt;code&gt;compadecida&lt;/code&gt; 也能命中 &lt;code&gt;Compadecida&lt;/code&gt;，&lt;strong&gt;对多语言市场这是体验生死线&lt;/strong&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ngram tokenizer&lt;/strong&gt; 一批（1–10、4–15 等不同窗口），&lt;code&gt;max_ngram_diff=14&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;completion suggester&lt;/strong&gt;：&lt;code&gt;suggest&lt;/code&gt; 字段配 &lt;code&gt;suggester_trigram&lt;/code&gt; 分析器，带 3 个 &lt;strong&gt;category context&lt;/strong&gt;（&lt;code&gt;markCate&lt;/code&gt; / &lt;code&gt;tenantCate&lt;/code&gt; / &lt;code&gt;comboCate&lt;/code&gt;）——自动补全可以按内容类型和租户隔离，A 运营商的用户不会看到 B 运营商专有的片名提示。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;设计启示&lt;/strong&gt;：一个&amp;quot;能搜&amp;quot;的字段，实际需要四层子字段分工——正常分词管召回、长 ngram 管前缀联想、短 ngram 管纠错、keyword 管精确和排序。这也正是《Elasticsearch in Action》里&amp;quot;多字段映射&amp;quot;思想的生产级演绎。&lt;/p&gt;
&lt;h3 id=&#34;34-双索引架构为什么搜索和详情要分开&#34;&gt;3.4 双索引架构：为什么搜索和详情要分开&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    subgraph 写入[&amp;#34;写入侧&amp;#34;]
        D[&amp;#34;MetaDetail 完整文档&amp;lt;br/&amp;gt;（几十 KB，含 nested 海报/演员/多语言）&amp;#34;] --&amp;gt; DETAIL[&amp;#34;详情索引&amp;lt;br/&amp;gt;6分片 重存储&amp;#34;]
        D --&amp;gt;|&amp;#34;裁剪：docId + mainTitle +&amp;lt;br/&amp;gt;suggest + 过滤字段 + 租户&amp;#34;| SEARCH[&amp;#34;搜索索引&amp;lt;br/&amp;gt;1分片 轻量&amp;#34;]
    end
    subgraph 查询[&amp;#34;查询侧&amp;#34;]
        U[&amp;#34;用户搜索 &amp;#39;auto&amp;#39;&amp;#34;] --&amp;gt;|&amp;#34;① matchSearch 命中&amp;lt;br/&amp;gt;返回 docId 列表&amp;#34;| SEARCH
        SEARCH --&amp;gt;|&amp;#34;② 按 docId mget&amp;#34;| DETAIL
        DETAIL --&amp;gt;|&amp;#34;组装完整详情返回&amp;#34;| U
    end&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;搜索索引里&lt;strong&gt;不存详情&lt;/strong&gt;，只有：docId、mainTitle（及 ngram 子字段）、suggest、40+ 个 keyword 过滤字段（&lt;code&gt;ext-field-to-index-map&lt;/code&gt; 里配置，如媒资类型、地区、年份、分级）、租户字段。好处有三个：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;搜索索引小&lt;/strong&gt;，1 个分片 + 内存友好，C 端 QPS 扛得住；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;详情变更不用动搜索索引&lt;/strong&gt;——海报换了只 update 详情索引；搜索索引只在标题/过滤字段变化时更新；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两类索引可以独立扩缩容、独立重建&lt;/strong&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;代价也很明显：多一次 mget 回查、数据一致性要维护两份。这个项目用下一节的同步机制来兜底。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四数据同步mysql--es-的三条路径&#34;&gt;四、数据同步：MySQL → ES 的三条路径&lt;/h2&gt;
&lt;p&gt;ES 是&amp;quot;读模型&amp;quot;，MySQL 才是事实源。项目没用 Canal/MQ，而是自己实现了一套&lt;strong&gt;事务内直写 + 任务表轮询&lt;/strong&gt;的混合方案：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[&amp;#34;业务写请求&amp;lt;br/&amp;gt;MetaService.save()&amp;#34;] --&amp;gt; B{&amp;#34;@Transactional&amp;#34;}
    B --&amp;gt; C[&amp;#34;① 写 MySQL 业务表&amp;#34;]
    B --&amp;gt; D[&amp;#34;② 写 MySQL 同步任务表&amp;lt;br/&amp;gt;MixedMetaSyncTask&amp;#34;]
    B --&amp;gt; E[&amp;#34;③ 同事务内直写详情索引&amp;lt;br/&amp;gt;bulkOperationByBatch(refresh=True)&amp;#34;]
    B --&amp;gt; F[&amp;#34;④ saveMetaToSearchEngine&amp;lt;br/&amp;gt;同步搜索索引&amp;#34;]
    E --&amp;gt; G{&amp;#34;ES 写失败?&amp;#34;}
    G --&amp;gt;|&amp;#34;是，事务回滚或留下任务&amp;#34;| H[&amp;#34;任务表残留记录&amp;#34;]
    D --&amp;gt; I[&amp;#34;PpMetaSyncJob&amp;lt;br/&amp;gt;@Scheduled 每 15s&amp;#34;]
    H --&amp;gt; I
    I --&amp;gt; J[&amp;#34;分布式锁&amp;lt;br/&amp;gt;executeWithLock&amp;#34;]
    J --&amp;gt; K[&amp;#34;MetaTaskProcessService&amp;lt;br/&amp;gt;分批 Lists.partition(batchSize)&amp;#34;]
    K --&amp;gt;|&amp;#34;searchSync.save(batch)&amp;#34;| L[&amp;#34;搜索索引&amp;#34;]
    M[&amp;#34;失败重试 Job&amp;lt;br/&amp;gt;每小时&amp;#34;] --&amp;gt; J&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三条路径各有分工：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;同事务直写&lt;/strong&gt;（主路径）：保证&amp;quot;后台保存完，前台立刻能搜到&amp;quot;。写入时统一刷新 &lt;code&gt;updateTime&lt;/code&gt; 和 &lt;code&gt;lastModifiedIndicator&lt;/code&gt;（格式：&lt;code&gt;时间戳_docId&lt;/code&gt; 的版本游标，下一节详述）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;任务表 + 定时轮询&lt;/strong&gt;（兜底路径）：15 秒一轮，分布式锁防多实例并发，分批灌入；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;失败重试 Job&lt;/strong&gt;（保险）：每小时扫描失败任务重放。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;为什么不纯用 MQ？&lt;/strong&gt; 这个系统的写入量级不大（媒资是低频高价值数据，不是日志），事务内直写的一致性收益远大于引入 MQ 的复杂度。这是&amp;quot;按场景选方案&amp;quot;而不是&amp;quot;按潮流选方案&amp;quot;的好例子。&lt;/p&gt;
&lt;h3 id=&#34;41-批量写入的性能细节&#34;&gt;4.1 批量写入的性能细节&lt;/h3&gt;
&lt;p&gt;工具类 &lt;code&gt;OpenSearchUtil.bulkUpsertDocuments&lt;/code&gt; 里有两个值得抄的实践：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 按配置的 bulkSize（默认 200）分批，避免单个 bulk 过大压垮集群&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 每批显式 refresh(WaitFor)：写完后“等 refresh 完成再返回”，&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 保证同步接口的调用方&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;“&lt;/span&gt;写完立即可见&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;”，&lt;/span&gt;同时比 refresh&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;（&lt;/span&gt;每文档刷新&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;）&lt;/span&gt;省资源&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;对照第 2.2 节的 NRT 原理：&lt;code&gt;refresh=WaitFor&lt;/code&gt; 是吞吐和可见性之间的甜点——批量场景不要无脑 &lt;code&gt;refresh=true&lt;/code&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五查询实战从业务代码反推-es-用法&#34;&gt;五、查询实战：从业务代码反推 ES 用法&lt;/h2&gt;
&lt;h3 id=&#34;51-一切查询的起点软删除过滤&#34;&gt;5.1 一切查询的起点：软删除过滤&lt;/h3&gt;
&lt;p&gt;项目里文档不物理删除，而是软删除——所有读路径都要叠一层 &lt;code&gt;isDeleted=0&lt;/code&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// OpenSearchUtil.getUndeletedQuery：所有查询都要过这层包装&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Query undeletedQuery &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; openSearchUtil.&lt;span style=&#34;color:#a6e22e&#34;&gt;getUndeletedQuery&lt;/span&gt;(boolQueryBuilder.&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;_toQuery&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 底层就是 bool.&lt;span style=&#34;color:#a6e22e&#34;&gt;filter&lt;/span&gt;(terms(isDeleted, &lt;span style=&#34;color:#f92672&#34;&gt;[&lt;/span&gt;0&lt;span style=&#34;color:#f92672&#34;&gt;]&lt;/span&gt;))&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;，&lt;/span&gt;可能再叠加 status&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;1&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;（&lt;/span&gt;已发布&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;）&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;业务侧的典型用法（&lt;code&gt;ArtworkService&lt;/code&gt;，按媒资 ID 查剧照）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Query metaIdQuery &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; Query.&lt;span style=&#34;color:#a6e22e&#34;&gt;of&lt;/span&gt;(q &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; q.&lt;span style=&#34;color:#a6e22e&#34;&gt;terms&lt;/span&gt;(t &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; t.&lt;span style=&#34;color:#a6e22e&#34;&gt;field&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;metaId&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;terms&lt;/span&gt;(tv &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; tv.&lt;span style=&#34;color:#a6e22e&#34;&gt;value&lt;/span&gt;(Stream.&lt;span style=&#34;color:#a6e22e&#34;&gt;of&lt;/span&gt;(request.&lt;span style=&#34;color:#a6e22e&#34;&gt;getMetaId&lt;/span&gt;())
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .&lt;span style=&#34;color:#a6e22e&#34;&gt;map&lt;/span&gt;(FieldValue::of).&lt;span style=&#34;color:#a6e22e&#34;&gt;toList&lt;/span&gt;()))));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;boolQueryBuilder.&lt;span style=&#34;color:#a6e22e&#34;&gt;filter&lt;/span&gt;(metaIdQuery);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;Query undeletedQuery &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; openSearchUtil.&lt;span style=&#34;color:#a6e22e&#34;&gt;getUndeletedQuery&lt;/span&gt;(boolQueryBuilder.&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;().&lt;span style=&#34;color:#a6e22e&#34;&gt;_toQuery&lt;/span&gt;());
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;SortOptions sortOption &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; SortOptions.&lt;span style=&#34;color:#a6e22e&#34;&gt;of&lt;/span&gt;(s &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; s.&lt;span style=&#34;color:#a6e22e&#34;&gt;field&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    f &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; f.&lt;span style=&#34;color:#a6e22e&#34;&gt;field&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;pos&amp;#34;&lt;/span&gt;).&lt;span style=&#34;color:#a6e22e&#34;&gt;order&lt;/span&gt;(SortOrder.&lt;span style=&#34;color:#a6e22e&#34;&gt;Asc&lt;/span&gt;)));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;MediaArtwork&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; artworks &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; openSearchUtil.&lt;span style=&#34;color:#a6e22e&#34;&gt;searchDoc&lt;/span&gt;(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    OpenSearchIndexEnum.&lt;span style=&#34;color:#a6e22e&#34;&gt;MEDIA_ARTWORK&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;getIndexName&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    undeletedQuery, MediaArtwork.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    request.&lt;span style=&#34;color:#a6e22e&#34;&gt;getPageId&lt;/span&gt;(), request.&lt;span style=&#34;color:#a6e22e&#34;&gt;getPageSize&lt;/span&gt;(),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Collections.&lt;span style=&#34;color:#a6e22e&#34;&gt;singletonList&lt;/span&gt;(sortOption), totalCount);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;这正是第 2.3 节&amp;quot;filter 不算分、可缓存&amp;quot;原理的落地：&lt;strong&gt;结构化条件全部进 filter&lt;/strong&gt;。&lt;/p&gt;
&lt;h3 id=&#34;52-软删除的实现updatebyquery--painless-脚本&#34;&gt;5.2 软删除的实现：updateByQuery + Painless 脚本&lt;/h3&gt;
&lt;p&gt;删除不是 &lt;code&gt;delete&lt;/code&gt;，而是 &lt;code&gt;updateByQuery&lt;/code&gt; 打标记：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;String script &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34; ctx._source.isDeleted = 1;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ctx._source.updateTime = params.currentTime;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;color:#f92672&#34;&gt;+&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;ctx._source.lastModifiedIndicator = params.indicatorPrefix + &amp;#39;_&amp;#39; + ctx._id&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;UpdateByQueryRequest request &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;new&lt;/span&gt; UpdateByQueryRequest.&lt;span style=&#34;color:#a6e22e&#34;&gt;Builder&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;index&lt;/span&gt;(indexName).&lt;span style=&#34;color:#a6e22e&#34;&gt;query&lt;/span&gt;(query)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;script&lt;/span&gt;(s &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; s.&lt;span style=&#34;color:#a6e22e&#34;&gt;inline&lt;/span&gt;(i &lt;span style=&#34;color:#f92672&#34;&gt;-&amp;gt;&lt;/span&gt; i.&lt;span style=&#34;color:#a6e22e&#34;&gt;source&lt;/span&gt;(finalScript)
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .&lt;span style=&#34;color:#a6e22e&#34;&gt;params&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;currentTime&amp;#34;&lt;/span&gt;, JsonData.&lt;span style=&#34;color:#a6e22e&#34;&gt;of&lt;/span&gt;(currentTime))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        .&lt;span style=&#34;color:#a6e22e&#34;&gt;params&lt;/span&gt;(&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;indicatorPrefix&amp;#34;&lt;/span&gt;, JsonData.&lt;span style=&#34;color:#a6e22e&#34;&gt;of&lt;/span&gt;(String.&lt;span style=&#34;color:#a6e22e&#34;&gt;valueOf&lt;/span&gt;(modifyTime)))))
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    .&lt;span style=&#34;color:#a6e22e&#34;&gt;build&lt;/span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;注意脚本里同时更新了 &lt;code&gt;lastModifiedIndicator&lt;/code&gt;——&lt;strong&gt;软删除也是一次&amp;quot;变更&amp;quot;，必须让下游订阅方感知到&lt;/strong&gt;（下游才能同步删）。这个细节是订阅体系能闭环的关键。&lt;/p&gt;
&lt;h3 id=&#34;53-两阶段全文搜索&#34;&gt;5.3 两阶段全文搜索&lt;/h3&gt;
&lt;p&gt;C 端搜索入口 &lt;code&gt;SearchService.searchMedia&lt;/code&gt; 的分流逻辑：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; (StringUtils.&lt;span style=&#34;color:#a6e22e&#34;&gt;isNotEmpty&lt;/span&gt;(fuzzyMatchingParams.&lt;span style=&#34;color:#a6e22e&#34;&gt;getKeyword&lt;/span&gt;())) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 有关键词：内部库全文检索 → 返回 docId + mark（类型）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; searchApi.&lt;span style=&#34;color:#a6e22e&#34;&gt;matchSearch&lt;/span&gt;(keyword, marks, pageId, pageSize, tenantIds, params);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;} &lt;span style=&#34;color:#66d9ef&#34;&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;color:#75715e&#34;&gt;// 无关键词：纯结构化筛选（找片页、分类浏览）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    resp &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; searchApi.&lt;span style=&#34;color:#a6e22e&#34;&gt;find&lt;/span&gt;(marks, pageId, pageSize, tenantIds, params);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 按 mark 分流 person / meta，回查详情索引取完整数据&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;openSearchUtil.&lt;span style=&#34;color:#a6e22e&#34;&gt;batchGetDocuments&lt;/span&gt;(PERSON_DETAIL.&lt;span style=&#34;color:#a6e22e&#34;&gt;getIndexName&lt;/span&gt;(), personDocIds, PersonDetail.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;openSearchUtil.&lt;span style=&#34;color:#a6e22e&#34;&gt;batchGetDocuments&lt;/span&gt;(META_DETAIL.&lt;span style=&#34;color:#a6e22e&#34;&gt;getIndexName&lt;/span&gt;(), metaDocIds, MetaDetail.&lt;span style=&#34;color:#a6e22e&#34;&gt;class&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;查询条件从哪来？API 层收的是 &lt;strong&gt;SQL 风格的 where 模板&lt;/strong&gt;（&lt;code&gt;MetaQuery.whereTemplate&lt;/code&gt; + &lt;code&gt;parameters&lt;/code&gt;），&lt;code&gt;WhereClauseUtil&lt;/code&gt; 用正则把 &lt;code&gt;in (:x)&lt;/code&gt;、&lt;code&gt;between and&lt;/code&gt;、&lt;code&gt;JSON_CONTAINS_ALL/ANY&lt;/code&gt; 解析成 ES 的 Condition（IN / GREATER_THAN 等），字段名经 &lt;code&gt;FieldNameMappingEnum&lt;/code&gt; 做外部名→索引字段名映射。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这个设计的好处&lt;/strong&gt;：对前端/上游服务来说，查询语法是稳定的数据库风格，不暴露 ES DSL；ES mapping 改名、换引擎，外部无感。&lt;/p&gt;
&lt;h3 id=&#34;54-自动补全completion-suggester--租户隔离&#34;&gt;5.4 自动补全：completion suggester + 租户隔离&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// SearchService.suggest&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;List&lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt;Prompt&lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; prompts &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; searchApi.&lt;span style=&#34;color:#a6e22e&#34;&gt;getPromptList&lt;/span&gt;(keyword, marks, &lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;, companyId, size, uuid);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// 底层 = 搜索索引 suggest 字段的 completion suggester，&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// context 里带 tenantCate=companyId → 补全结果按运营商隔离&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 拿到 docId 后再回查详情索引组装标题和海报&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;举例如&amp;quot;用户输入 &lt;code&gt;int&lt;/code&gt;&amp;quot;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant U as 用户(运营商A)
    participant S as SearchService
    participant SE as 搜索索引 suggester
    participant D as 详情索引
    U-&amp;gt;&amp;gt;S: suggest(&amp;#34;int&amp;#34;, companyId=A)
    S-&amp;gt;&amp;gt;SE: completion suggest&amp;lt;br/&amp;gt;context: tenantCate=A
    SE--&amp;gt;&amp;gt;S: [&amp;#34;Interestelar&amp;#34;, &amp;#34;Intocáveis&amp;#34;]&amp;lt;br/&amp;gt;+ docId 列表
    S-&amp;gt;&amp;gt;D: mget(docIds)
    D--&amp;gt;&amp;gt;S: 标题 + 海报 URL
    S--&amp;gt;&amp;gt;U: 补全列表（带封面）&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;55-一个常被忽略的性能点只取-id-就别加载-_source&#34;&gt;5.5 一个常被忽略的性能点：只取 ID 就别加载 _source&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// fastGetDocumentById：判断文档存在/取 ID 时&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// storedFields(&amp;#34;_id&amp;#34;) + size(1) + trackTotalHits(false)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 注释原话&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;：&lt;/span&gt;类似 MySQL 的 LIMIT 1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;trackTotalHits(false)&lt;/code&gt;：不精确统计总数（默认只算到 10000），省掉一次全量计数；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;storedFields(&amp;quot;_id&amp;quot;)&lt;/code&gt;：不加载 &lt;code&gt;_source&lt;/code&gt;，网络传输量从几十 KB 降到几十 B。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;列表页&amp;quot;先查 ID 再回查&amp;quot;的场景，这两招能把响应时间砍掉一个量级。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六增量订阅es-作为变更数据源&#34;&gt;六、增量订阅：ES 作为&amp;quot;变更数据源&amp;quot;&lt;/h2&gt;
&lt;p&gt;下游服务（推荐、缓存、其他中台）需要感知媒资变更。项目没有用 CDC 或 MQ 广播，而是让下游&lt;strong&gt;基于水位游标主动拉取 ES&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant SUB as 下游订阅方
    participant Q as SubqueryService
    participant ES as 详情索引

    Note over SUB: 记录上次水位 W0&amp;lt;br/&amp;gt;（lastModifiedIndicator 字符串）
    SUB-&amp;gt;&amp;gt;Q: querySubQuery(from=W0, size=500)
    Q-&amp;gt;&amp;gt;ES: range(lastModifiedIndicator: gt W0, lt 当前时间-2s)&amp;lt;br/&amp;gt;+ 按 lastModifiedIndicator 升序
    ES--&amp;gt;&amp;gt;Q: 本批变更文档（含软删除的）
    Q--&amp;gt;&amp;gt;SUB: 文档 + 新水位 W1
    SUB-&amp;gt;&amp;gt;SUB: 落库/更新缓存，记录 W1
    Note over Q,ES: 对账：subQueryCount 用 count + sort desc + size(1)&amp;lt;br/&amp;gt;取区间最大水位，生产方/消费方核对数量&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;几个工程细节值得细说：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;水位是字符串而非时间戳&lt;/strong&gt;：&lt;code&gt;lastModifiedIndicator = 时间戳 + &amp;quot;_&amp;quot; + docId&lt;/code&gt;。纯时间戳会在同一毫秒内有多个文档时漏数；拼上 docId 后字典序唯一，range &lt;code&gt;gt&lt;/code&gt; 翻页不会漏、不会重。这是项目&lt;strong&gt;用手写游标替代 &lt;code&gt;search_after&lt;/code&gt;&lt;/strong&gt; 的原因——行为完全可控，且不依赖 ES 的 PIT（point in time）上下文。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;lt 当前时间 - 2s&lt;/code&gt; 的回调偏移&lt;/strong&gt;：规避&amp;quot;刚写入还没 refresh 可见&amp;quot;的窗口（第 2.2 节 NRT 原理的直接应用），宁可晚 2 秒，不可漏数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;软删除文档也带着新水位出现&lt;/strong&gt;，下游据此删除本地副本——和第 5.2 节的脚本闭环。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七多租户设计docid-里藏着租户&#34;&gt;七、多租户设计：docId 里藏着租户&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// OpenSearchUtil.getDocId：文档 ID = companyId + &amp;#34;#&amp;#34; + metaId&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt; 例&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;：&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;1001#mv_8848123&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;、&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;public#mv_8848123&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;（&lt;/span&gt;公共库&lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;）&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;同一部影片，A 运营商和 B 运营商是&lt;strong&gt;两份文档&lt;/strong&gt;（各自的标题、定价、上架状态可能不同），天然物理隔离；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sourceType&lt;/code&gt; 字段区分 PRIVATE（私有）/ PUBLIC（公共）/ MIXED（混合），公共内容用 &lt;code&gt;public#&lt;/code&gt; 前缀，查询时按租户策略合并；&lt;/li&gt;
&lt;li&gt;所有查询 filter 里都带 &lt;code&gt;companyId&lt;/code&gt; / &lt;code&gt;tenant_id&lt;/code&gt;，补全用 context 隔离（第 5.4 节）。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;经验&lt;/strong&gt;：多租户搜索系统，把租户编进 docId 比单纯加过滤字段更稳——docId 冲突本身就是一道防串租户的保险。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八回到原理这个项目验证了哪些书里的论断&#34;&gt;八、回到原理：这个项目验证了哪些书里的论断&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;书中原理&lt;/th&gt;
					&lt;th&gt;项目里的体现&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Mapping 先行，改 analyzer 要重建索引&lt;/td&gt;
					&lt;td&gt;索引 DDL 用 changelog 版本化管理，上线流程化&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Filter 不算分、可缓存&lt;/td&gt;
					&lt;td&gt;租户/类型/软删除/状态全部进 filter，只有关键词算分&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;多字段映射解决&amp;quot;一个字段多种搜法&amp;quot;&lt;/td&gt;
					&lt;td&gt;mainTitle 四层子字段：分词/长ngram/短ngram/keyword&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;NRT 的 refresh 是性能旋钮&lt;/td&gt;
					&lt;td&gt;批量写入用 &lt;code&gt;refresh=WaitFor&lt;/code&gt;；订阅端留 2s 偏移&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;深度翻页要避免 from+size 深分页&lt;/td&gt;
					&lt;td&gt;手写 &lt;code&gt;lastModifiedIndicator&lt;/code&gt; 游标替代 search_after&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;聚合昂贵，慎用&lt;/td&gt;
					&lt;td&gt;项目几乎零聚合；取最大值用 sort+size(1) 替代 max agg&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;分片数创建时定死，规划要前置&lt;/td&gt;
					&lt;td&gt;详情索引 6 分片按数据增长预估；搜索索引刻意 1 分片&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九总结&#34;&gt;九、总结&lt;/h2&gt;
&lt;p&gt;如果把这套架构压缩成一张图：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    W[&amp;#34;写路径&amp;lt;br/&amp;gt;MySQL 事务&amp;lt;br/&amp;gt;+ 任务表兜底&amp;#34;] --&amp;gt; ES
    subgraph ES[&amp;#34;OpenSearch&amp;#34;]
        S[&amp;#34;搜索索引&amp;lt;br/&amp;gt;小而快：ngram+suggester&amp;lt;br/&amp;gt;+租户 context&amp;#34;]
        D[&amp;#34;详情索引&amp;lt;br/&amp;gt;大而全：keyword+nested&amp;lt;br/&amp;gt;dynamic:false&amp;#34;]
    end
    ES --&amp;gt; R1[&amp;#34;读路径 ①&amp;lt;br/&amp;gt;搜索/补全 → docId&amp;#34;]
    R1 --&amp;gt; R2[&amp;#34;读路径 ②&amp;lt;br/&amp;gt;mget 详情组装&amp;#34;]
    ES --&amp;gt; R3[&amp;#34;订阅路径&amp;lt;br/&amp;gt;水位游标增量拉取&amp;#34;]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;三条核心经验：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;索引按&amp;quot;查询模式&amp;quot;而不是&amp;quot;数据表&amp;quot;来设计&lt;/strong&gt;：搜索索引和详情索引分开，一个为召回优化（ngram、suggester、多语分析器），一个为取数优化（keyword、nested、禁动态字段）；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一致性靠机制不靠运气&lt;/strong&gt;：事务内直写是主路径，任务表 + 定时 Job + 失败重试是兜底，水位游标让下游最终一致——三层防护各管一段；&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;所有&amp;quot;黑科技&amp;quot;都是基本原理的应用&lt;/strong&gt;：refresh 偏移、filter 缓存、storedFields 取 ID、游标翻页……吃透《Elasticsearch in Action》那套原理，生产方案都是它的排列组合。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;参考资料：Radu Gheorghe, Matthew Lee Hinman, Roy Russo. 《Elasticsearch in Action》. Manning Publications, 2015；ftmao-meta 项目源码（OpenSearchConfig / OpenSearchUtil / SearchService / SubqueryService / changelog/elasticsearch）。&lt;/em&gt;&lt;/p&gt;
</description>
    </item>
    <item>
      <title>MySQL 实战笔记：安装、事务、索引、锁与高性能优化</title>
      <link>https://ftmao.com/blog/posts/mysql-practical-guide/</link>
      <pubDate>Thu, 28 Nov 2019 14:00:00 +0800</pubDate>
      <guid>https://ftmao.com/blog/posts/mysql-practical-guide/</guid>
      <description>&lt;h1 id=&#34;mysql-实战笔记安装事务索引锁与高性能优化&#34;&gt;MySQL 实战笔记：安装、事务、索引、锁与高性能优化&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;MySQL 是互联网业务中使用最广泛的关系型数据库之一。掌握它不能只停留在“会写 CRUD”，还要理解事务隔离、索引结构、锁机制以及高性能架构设计。本文从实战出发，结合常见坑点与优化建议，帮你建立系统性的 MySQL 知识地图。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;一高层次思想mysql-在解决什么问题&#34;&gt;一、高层次思想：MySQL 在解决什么问题？&lt;/h2&gt;
&lt;p&gt;关系型数据库的核心价值可以概括为三句话：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;持久化&lt;/strong&gt;：让数据安全地落在磁盘上，程序重启不丢失。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;一致性&lt;/strong&gt;：通过事务与约束，保证数据始终符合业务规则。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;高效访问&lt;/strong&gt;：通过索引、缓存、分区分表等手段，在海量数据中快速读写。&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[业务应用] --&amp;gt;|SQL| B[MySQL Server]
    B --&amp;gt; C[SQL 解析 / 优化]
    C --&amp;gt; D[执行引擎]
    D --&amp;gt; E[存储引擎 InnoDB/MyISAM]
    E --&amp;gt; F[Buffer Pool 缓存]
    E --&amp;gt; G[Redo / Undo Log]
    E --&amp;gt; H[磁盘数据文件]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;11-mysql-架构分层&#34;&gt;1.1 MySQL 架构分层&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[连接层] --&amp;gt; B[服务层]
    B --&amp;gt; C[存储引擎层]
    B --&amp;gt; D[查询缓存 / 解析器 / 优化器]
    C --&amp;gt; E[InnoDB 支持事务/行锁]
    C --&amp;gt; F[MyISAM 高速查询/表锁]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;层级&lt;/th&gt;
					&lt;th&gt;职责&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;连接层&lt;/td&gt;
					&lt;td&gt;管理客户端连接、权限验证&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;服务层&lt;/td&gt;
					&lt;td&gt;SQL 解析、优化、执行&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;存储引擎层&lt;/td&gt;
					&lt;td&gt;负责数据的存储与读取&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;12-buffer-poolinnodb-的内存缓存&#34;&gt;1.2 Buffer Pool：InnoDB 的内存缓存&lt;/h3&gt;
&lt;p&gt;Buffer Pool 是 InnoDB 在内存中开辟的一块区域，用来缓存&lt;strong&gt;数据页&lt;/strong&gt;和&lt;strong&gt;索引页&lt;/strong&gt;。它是 MySQL 性能的核心，绝大多数读写操作都先发生在 Buffer Pool 中。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;为什么需要 Buffer Pool？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;磁盘的读写速度比内存慢几个数量级。如果每次查询都读磁盘，性能会极差。Buffer Pool 把热数据放在内存里，大幅减少磁盘 I/O。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Buffer Pool 的工作流程：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant App as 应用
    participant BP as Buffer Pool
    participant Disk as 磁盘

    App-&amp;gt;&amp;gt;BP: 查询 id=1
    BP--&amp;gt;&amp;gt;App: 命中，直接返回
    Note over BP: 数据页已在内存

    App-&amp;gt;&amp;gt;BP: 查询 id=2
    BP-&amp;gt;&amp;gt;Disk: 未命中，从磁盘加载
    Disk--&amp;gt;&amp;gt;BP: 返回数据页
    BP--&amp;gt;&amp;gt;App: 返回数据&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;写数据时的流程：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先把数据页读到 Buffer Pool（如果不在）。&lt;/li&gt;
&lt;li&gt;在 Buffer Pool 中修改数据页，此时这个页变成&lt;strong&gt;脏页（Dirty Page）&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;写 Redo Log（WAL）。&lt;/li&gt;
&lt;li&gt;返回成功给应用。&lt;/li&gt;
&lt;li&gt;后台线程（Page Cleaner）异步把脏页刷回磁盘。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Buffer Pool 的内部结构：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;结构&lt;/th&gt;
					&lt;th&gt;作用&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;LRU 链表&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;管理数据页的使用热度，最近最少使用的页被淘汰&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Free 链表&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;管理空闲页&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Flush 链表&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;管理脏页，等待后台刷盘&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Change Buffer&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;缓存二级索引的修改，减少随机 I/O&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;Adaptive Hash Index&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;自动为热点数据页建立哈希索引，加速查找&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：脏页是异步刷盘的，所以提交成功不代表数据页已经落盘。InnoDB 通过 Redo Log 保证即使崩溃也能恢复。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;二安装启动与基础配置&#34;&gt;二、安装、启动与基础配置&lt;/h2&gt;
&lt;h3 id=&#34;21-启动-mysql-服务&#34;&gt;2.1 启动 MySQL 服务&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Windows&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;net start MySQL
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Linux/macOS&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;sudo systemctl start mysql&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;22-修改-root-密码&#34;&gt;2.2 修改 root 密码&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ALTER&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;root&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;localhost&amp;#39;&lt;/span&gt; IDENTIFIED &lt;span style=&#34;color:#66d9ef&#34;&gt;BY&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;root&amp;#39;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;23-创建数据库时务必指定编码&#34;&gt;2.3 创建数据库时务必指定编码&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CREATE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;DATABASE&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;test&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#75715e&#34;&gt;/*!40100 COLLATE &amp;#39;utf8mb4_bin&amp;#39; */&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #1&lt;/strong&gt;：默认的 &lt;code&gt;utf8&lt;/code&gt; 在 MySQL 中其实是阉割版（最多 3 字节），无法存储 Emoji 等 4 字节字符。生产环境请统一使用 &lt;strong&gt;&lt;code&gt;utf8mb4&lt;/code&gt;&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;24-jdbc-连接串必须带编码与时区&#34;&gt;2.4 JDBC 连接串必须带编码与时区&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;jdbc:mysql:&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt;127.&lt;span style=&#34;color:#a6e22e&#34;&gt;0&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;0&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;1&lt;/span&gt;:3306&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;crmeb_java&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;serverTimeZone&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;UTC&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;useUnicode&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;characterEncoding&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;utf8&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;MySQL 8 推荐写法：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-java&#34; data-lang=&#34;java&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;jdbc:mysql:&lt;span style=&#34;color:#f92672&#34;&gt;//&lt;/span&gt;127.&lt;span style=&#34;color:#a6e22e&#34;&gt;0&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;0&lt;/span&gt;.&lt;span style=&#34;color:#a6e22e&#34;&gt;1&lt;/span&gt;:3306&lt;span style=&#34;color:#f92672&#34;&gt;/&lt;/span&gt;db_name&lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt;serverTimezone&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;UTC&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;useSSL&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;false&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;useUnicode&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;true&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;&amp;amp;&lt;/span&gt;characterEncoding&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;utf8mb4&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #2&lt;/strong&gt;：MySQL 8 默认启用 SSL，但旧版驱动连接时可能报错，可显式设置 &lt;code&gt;useSSL=false&lt;/code&gt;（内网环境）。时区必须指定，否则会出现时间差 8 小时的问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;25-开启通用查询日志&#34;&gt;2.5 开启通用查询日志&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SET&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;GLOBAL&lt;/span&gt; general_log &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;ON&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SET&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;GLOBAL&lt;/span&gt; general_log_file &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;/tmp/mysql.log&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SHOW&lt;/span&gt; VARIABLES &lt;span style=&#34;color:#66d9ef&#34;&gt;LIKE&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;general_log%&amp;#34;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #3&lt;/strong&gt;：通用日志会记录所有 SQL，性能开销极大。仅用于临时排查问题，&lt;strong&gt;生产环境不要长期开启&lt;/strong&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;三存储过程谨慎使用&#34;&gt;三、存储过程：谨慎使用&lt;/h2&gt;
&lt;p&gt;存储过程（Stored Procedure）是把一组 SQL 语句预编译后存储在数据库内部，通过 &lt;code&gt;CALL&lt;/code&gt; 调用的程序单元。它能减少网络往返、封装固定逻辑，但在现代工程实践中已不再被推荐作为首选方案。&lt;/p&gt;
&lt;h3 id=&#34;为什么不推荐重度使用存储过程&#34;&gt;为什么不推荐重度使用存储过程？&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;问题&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;调试困难&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;缺乏现代 IDE 的断点、单步调试和日志追踪能力，定位问题成本高。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;版本控制弱&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;存储过程游离在应用代码仓库之外，难以做 Code Review 和版本回滚。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;迁移成本高&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;不同数据库（MySQL、Oracle、SQL Server、PostgreSQL）的存储过程语法差异大，换库时重写代价大。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;测试困难&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;不易做单元测试、集成测试，CI/CD 流程中难以保证质量。&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;与微服务理念冲突&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;业务逻辑下沉到数据库，导致数据库成为“另一个服务边界”，增加耦合。&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;适合使用存储过程的场景&#34;&gt;适合使用存储过程的场景&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;数据迁移与 ETL&lt;/strong&gt;：一次性、数据库内部的数据清洗、转换任务。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;简单的批量操作&lt;/strong&gt;：例如按固定规则批量归档历史数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;强依赖数据库原子性的脚本&lt;/strong&gt;：需要利用数据库事务保证一致性的短时脚本。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&#34;更推荐的替代方案&#34;&gt;更推荐的替代方案&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[复杂业务逻辑] --&amp;gt; B[应用层 / Service 层实现]
    A --&amp;gt; C[ORM / MyBatis 等持久层框架]
    B --&amp;gt; D[便于测试、调试、版本控制]
    C --&amp;gt; D&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;应用层处理&lt;/strong&gt;：把复杂逻辑放在 Java/Go/Python 等服务层，配合事务注解保证一致性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;批量 SQL&lt;/strong&gt;：使用 &lt;code&gt;INSERT ... SELECT&lt;/code&gt; 或 &lt;code&gt;LOAD DATA&lt;/code&gt; 完成批量导入。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;定时任务&lt;/strong&gt;：使用调度框架（如 Quartz、XXL-JOB）替代存储过程做周期化数据处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #4&lt;/strong&gt;：存储过程调试困难、不利于迁移，现代微服务架构中建议&lt;strong&gt;慎用&lt;/strong&gt;。批量数据处理优先考虑在应用层分批插入，或使用 &lt;code&gt;INSERT ... SELECT&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;四事务acid-与隔离级别&#34;&gt;四、事务：ACID 与隔离级别&lt;/h2&gt;
&lt;h3 id=&#34;41-事务的-acid-特性&#34;&gt;4.1 事务的 ACID 特性&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[事务] --&amp;gt; B[Atomicity 原子性]
    A --&amp;gt; C[Consistency 一致性]
    A --&amp;gt; D[Isolation 隔离性]
    A --&amp;gt; E[Durability 持久性]
    B --&amp;gt; F[Undo Log]
    E --&amp;gt; G[Redo Log]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;特性&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
					&lt;th&gt;MySQL 实现&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;原子性&lt;/td&gt;
					&lt;td&gt;要么全成功，要么全回滚&lt;/td&gt;
					&lt;td&gt;Undo Log&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;一致性&lt;/td&gt;
					&lt;td&gt;事务前后数据满足约束&lt;/td&gt;
					&lt;td&gt;约束 + 触发器 + 业务校验&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;隔离性&lt;/td&gt;
					&lt;td&gt;并发事务互不干扰&lt;/td&gt;
					&lt;td&gt;锁 + MVCC&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;持久性&lt;/td&gt;
					&lt;td&gt;提交后数据不丢失&lt;/td&gt;
					&lt;td&gt;Redo Log&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;42-undo-log-与-redo-log&#34;&gt;4.2 Undo Log 与 Redo Log&lt;/h3&gt;
&lt;h4 id=&#34;421-redo-log崩溃恢复与何时返回成功&#34;&gt;4.2.1 Redo Log：崩溃恢复与“何时返回成功”&lt;/h4&gt;
&lt;p&gt;InnoDB 使用 &lt;strong&gt;WAL（Write-Ahead Logging）&lt;/strong&gt; 保证持久性：事务提交前先把修改写到 Redo Log，真正的脏页可以延迟异步刷回磁盘。核心流程如下：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant App as 应用/客户端
    participant MySQL as MySQL Server
    participant BP as Buffer Pool
    participant Redo as Redo Log Buffer / 文件
    participant Disk as 磁盘数据文件

    App-&amp;gt;&amp;gt;MySQL: BEGIN
    App-&amp;gt;&amp;gt;MySQL: UPDATE user SET age=20 WHERE id=1
    MySQL-&amp;gt;&amp;gt;BP: 读取数据页并修改 age=20
    MySQL-&amp;gt;&amp;gt;Redo: 写入 Redo Log：&amp;lt;br/&amp;gt;&amp;#34;将 id=1 的 age 改为 20&amp;#34;
    Note over Redo: WAL：先写日志，再写磁盘
    App-&amp;gt;&amp;gt;MySQL: COMMIT
    MySQL-&amp;gt;&amp;gt;Redo: 按策略刷新 Redo Log
    MySQL--&amp;gt;&amp;gt;App: 返回 Commit 成功
    Note over BP,Disk: 脏页由后台线程异步刷盘&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Redo 写完后，MySQL 才告诉应用写完成了吗？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;答案是：&lt;strong&gt;默认配置（&lt;code&gt;innodb_flush_log_at_trx_commit = 1&lt;/code&gt;）下，是的。&lt;/strong&gt; MySQL 把 Redo Log &lt;code&gt;fsync&lt;/code&gt; 到磁盘后，才会返回 &lt;code&gt;COMMIT&lt;/code&gt; 成功。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;参数值&lt;/th&gt;
					&lt;th&gt;提交时的行为&lt;/th&gt;
					&lt;th&gt;持久性&lt;/th&gt;
					&lt;th&gt;典型场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;1（默认）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;每次提交都 &lt;code&gt;fsync&lt;/code&gt; 刷盘&lt;/td&gt;
					&lt;td&gt;最高，提交即持久&lt;/td&gt;
					&lt;td&gt;生产默认，强一致业务&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;2&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;写到 OS Page Cache，每秒刷盘&lt;/td&gt;
					&lt;td&gt;可能丢失 1 秒内数据&lt;/td&gt;
					&lt;td&gt;追求性能，可容忍少量丢失&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;0&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;由后台线程每秒刷一次&lt;/td&gt;
					&lt;td&gt;最低，进程崩溃可能丢 1 秒&lt;/td&gt;
					&lt;td&gt;极少使用&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;关键结论&lt;/strong&gt;：默认情况下，应用收到 &lt;code&gt;COMMIT&lt;/code&gt; 成功，就意味着对应事务的 Redo Log 已经持久化到磁盘。此后即使 MySQL 立刻崩溃，重启时也能根据 Redo Log 恢复这条已提交事务；如果崩溃发生在 &lt;code&gt;fsync&lt;/code&gt; 之前，则该事务会被回滚或视为未提交。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;崩溃恢复流程：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[MySQL 崩溃后重启] --&amp;gt; B[加载磁盘 Redo Log]
    B --&amp;gt; C[找到上一次 Checkpoint LSN]
    C --&amp;gt; D[扫描 Checkpoint 之后的 Redo 记录]
    D --&amp;gt; E{Redo 记录对应页&amp;lt;br/&amp;gt;是否需要重放?}
    E --&amp;gt;|Redo LSN &amp;gt; 页 LSN| F[重放该条 Redo]
    F --&amp;gt; G[恢复已提交事务的修改]
    E --&amp;gt;|已落盘，无需重放| H[跳过]
    G --&amp;gt; I[恢复完成，数据库可用]
    H --&amp;gt; I
    J[崩溃时 Redo 尚未 fsync] --&amp;gt; K[事务视为未提交&amp;lt;br/&amp;gt;不会恢复]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：Redo Log 是物理逻辑日志，恢复时按 LSN 顺序重放。InnoDB 会对比 Redo 记录与数据页上的 LSN，避免对已经落盘的页重复应用，保证幂等性。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id=&#34;422-undo-log回滚是如何实现的&#34;&gt;4.2.2 Undo Log：回滚是如何实现的？&lt;/h4&gt;
&lt;p&gt;Redo 记录“新值”用于崩溃后重做；Undo 记录“旧值”用于事务回滚和 MVCC。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Undo Log 回滚流程：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[事务开始 BEGIN] --&amp;gt; B[执行 UPDATE / DELETE]
    B --&amp;gt; C[生成 Undo Log&amp;lt;br/&amp;gt;记录修改前的旧值]
    C --&amp;gt; D[修改 Buffer Pool 数据页]
    D --&amp;gt; E{事务结果}
    E --&amp;gt;|ROLLBACK| F[读取本事务的 Undo Log]
    F --&amp;gt; G[用旧值覆盖 Buffer Pool 中的修改]
    G --&amp;gt; H[释放事务持有的锁]
    H --&amp;gt; I[事务结束，修改被撤销]
    E --&amp;gt;|COMMIT| J[Redo Log 持久化后提交]
    J --&amp;gt; K[Undo Log 标记为可清理]
    K --&amp;gt; L[Purge 线程异步回收]
    L --&amp;gt; M[事务结束]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;Undo Log 的两个核心作用：&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;事务回滚&lt;/strong&gt;：&lt;code&gt;ROLLBACK&lt;/code&gt; 或语句失败时，InnoDB 沿着 Undo Log 把修改逐项撤销，恢复数据到事务开始前的状态，保证原子性。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MVCC 一致性读&lt;/strong&gt;：RR/RC 级别下，普通 &lt;code&gt;SELECT&lt;/code&gt; 不加锁，而是通过 Undo Log 链构造事务开始时可见的数据版本（ReadView），实现无锁并发读。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：Undo Log 的修改本身也受到 Redo Log 保护。崩溃恢复时，Undo Log 页也会通过 Redo Log 重建，否则回滚能力会丢失。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt;：事务提交后，Undo Log 不会立即删除。如果其他事务还需要用它构造快照，Undo Log 会继续保留，直到没有事务依赖该旧版本后，Purge 线程才会回收。长事务会导致 Undo 堆积、表空间膨胀。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;43-事务的并发问题&#34;&gt;4.3 事务的并发问题&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;问题&lt;/th&gt;
					&lt;th&gt;描述&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;脏读&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;事务 A 读到事务 B 未提交的数据，B 回滚后 A 读到的是脏数据&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;不可重复读&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;事务 A 多次读取同一行，事务 B 修改并提交，导致 A 两次结果不同&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;幻读&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;事务 A 按条件查询一批数据，事务 B 新增/删除满足条件的数据，导致 A 再次查询时结果集不同&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #5&lt;/strong&gt;：不可重复读侧重“修改”，幻读侧重“新增或删除”。InnoDB 默认的可重复读通过 MVCC + 间隙锁（Gap Lock）在很大程度上解决了幻读。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;44-四种隔离级别&#34;&gt;4.4 四种隔离级别&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[读未提交&amp;lt;br&amp;gt;Read Uncommitted] --&amp;gt; B[读已提交&amp;lt;br&amp;gt;Read Committed]
    B --&amp;gt; C[可重复读&amp;lt;br&amp;gt;Repeatable Read]
    C --&amp;gt; D[串行化&amp;lt;br&amp;gt;Serializable]
    style C fill:#e1f5e1&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;隔离级别&lt;/th&gt;
					&lt;th&gt;脏读&lt;/th&gt;
					&lt;th&gt;不可重复读&lt;/th&gt;
					&lt;th&gt;幻读&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;读未提交（RU）&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
					&lt;td&gt;基本不使用&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;读已提交（RC）&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
					&lt;td&gt;是&lt;/td&gt;
					&lt;td&gt;Oracle / SQL Server 默认&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;可重复读（RR）&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;否*&lt;/td&gt;
					&lt;td&gt;&lt;strong&gt;MySQL 默认&lt;/strong&gt;，InnoDB 通过 MVCC + Gap Lock 解决幻读&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;串行化（S）&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;否&lt;/td&gt;
					&lt;td&gt;性能最差，完全串行&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #6&lt;/strong&gt;：MySQL 默认是可重复读（RR），但很多互联网项目会改为读已提交（RC），因为 RC 锁粒度更小、并发更高。但改为 RC 后，需要业务层自行处理不可重复读问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;五索引类型&#34;&gt;五、索引类型&lt;/h2&gt;
&lt;p&gt;索引是 MySQL 高性能的基石。理解索引类型与适用场景，比单纯记住语法更重要。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph TD
    A[MySQL 索引] --&amp;gt; B[普通索引]
    A --&amp;gt; C[唯一索引]
    A --&amp;gt; D[主键索引]
    A --&amp;gt; E[组合索引]
    A --&amp;gt; F[全文索引]
    D --&amp;gt; G[聚簇索引：数据与索引在一起]
    B --&amp;gt; H[二级索引：叶子节点存主键]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;51-普通索引&#34;&gt;5.1 普通索引&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CREATE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;INDEX&lt;/span&gt; index_name &lt;span style=&#34;color:#66d9ef&#34;&gt;ON&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;table&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;column&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;length&lt;/span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;52-唯一索引&#34;&gt;5.2 唯一索引&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CREATE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;UNIQUE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;INDEX&lt;/span&gt; indexName &lt;span style=&#34;color:#66d9ef&#34;&gt;ON&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;table&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;column&lt;/span&gt;(&lt;span style=&#34;color:#66d9ef&#34;&gt;length&lt;/span&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h3 id=&#34;53-主键索引&#34;&gt;5.3 主键索引&lt;/h3&gt;
&lt;p&gt;主键索引是一种特殊的唯一索引，一张表只能有一个主键，且不允许为空。&lt;/p&gt;
&lt;h3 id=&#34;54-组合索引&#34;&gt;5.4 组合索引&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CREATE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;INDEX&lt;/span&gt; idx_name_age &lt;span style=&#34;color:#66d9ef&#34;&gt;ON&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt;(name, age);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #7&lt;/strong&gt;：组合索引遵循&lt;strong&gt;最左前缀原则&lt;/strong&gt;。&lt;code&gt;idx_name_age&lt;/code&gt; 对 &lt;code&gt;name&lt;/code&gt;、&lt;code&gt;name+age&lt;/code&gt; 有效，但对单独 &lt;code&gt;age&lt;/code&gt; 无效。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;55-全文索引&#34;&gt;5.5 全文索引&lt;/h3&gt;
&lt;p&gt;适用于 &lt;code&gt;LIKE &#39;%关键词%&#39;&lt;/code&gt; 场景，MyISAM 和 InnoDB 5.6+ 都支持。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;六锁机制与-mvcc&#34;&gt;六、锁机制与 MVCC&lt;/h2&gt;
&lt;h3 id=&#34;61-共享锁与排他锁&#34;&gt;6.1 共享锁与排他锁&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;锁类型&lt;/th&gt;
					&lt;th&gt;英文&lt;/th&gt;
					&lt;th&gt;读/写&lt;/th&gt;
					&lt;th&gt;兼容性&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;共享锁&lt;/td&gt;
					&lt;td&gt;S Lock / 读锁&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;SELECT ... LOCK IN SHARE MODE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;读读兼容，读写互斥&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;排他锁&lt;/td&gt;
					&lt;td&gt;X Lock / 写锁&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; / 增删改&lt;/td&gt;
					&lt;td&gt;与任何锁都互斥&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[共享锁 S] -- 兼容 --&amp;gt; B[共享锁 S]
    A -- 互斥 --&amp;gt; C[排他锁 X]
    C -- 互斥 --&amp;gt; D[排他锁 X]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #8&lt;/strong&gt;：只有 &lt;code&gt;INSERT / UPDATE / DELETE&lt;/code&gt; 会自动加排他锁；普通 &lt;code&gt;SELECT&lt;/code&gt; 不加锁。如果需要保证读取的是最新已提交数据并防止被修改，应显式使用 &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;62-mvcc-实现可重复读&#34;&gt;6.2 MVCC 实现可重复读&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;sequenceDiagram
    participant T1 as 事务 A
    participant DB as InnoDB
    participant T2 as 事务 B

    T1-&amp;gt;&amp;gt;DB: START TRANSACTION&amp;lt;br&amp;gt;创建 ReadView
    T2-&amp;gt;&amp;gt;DB: UPDATE row SET x=2&amp;lt;br&amp;gt;并提交
    T1-&amp;gt;&amp;gt;DB: SELECT x&amp;lt;br&amp;gt;仍读取快照中的 x=1
    Note over T1,DB: 事务 A 始终使用开始时的 ReadView&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;MVCC 不是复制一份数据，而是通过 &lt;strong&gt;ReadView + 数据行隐藏字段 + Undo Log 版本链&lt;/strong&gt;，来判断当前事务应该看到哪个版本的数据。&lt;/p&gt;
&lt;h4 id=&#34;621-数据行的两个隐藏字段&#34;&gt;6.2.1 数据行的两个隐藏字段&lt;/h4&gt;
&lt;p&gt;InnoDB 每行数据都有两个隐藏字段：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DB_TRX_ID&lt;/code&gt;：最后修改这行的事务 ID。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DB_ROLL_PTR&lt;/code&gt;：回滚指针，指向这行数据在 Undo Log 中的上一个版本。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;622-readview-里面有什么&#34;&gt;6.2.2 ReadView 里面有什么？&lt;/h4&gt;
&lt;p&gt;ReadView 是一个轻量的结构，不保存任何实际数据，只保存生成时刻的事务状态：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;字段&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;m_ids&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;生成 ReadView 时，所有未提交事务的 ID 列表&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;min_trx_id&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;m_ids&lt;/code&gt; 中的最小事务 ID&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;max_trx_id&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;生成 ReadView 时，系统下一个要分配的事务 ID&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;creator_trx_id&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;创建该 ReadView 的事务自身 ID&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&#34;623-可见性判断算法&#34;&gt;6.2.3 可见性判断算法&lt;/h4&gt;
&lt;p&gt;对于查询到的一行数据，取出它的 &lt;code&gt;DB_TRX_ID&lt;/code&gt;，按以下规则判断：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DB_TRX_ID == creator_trx_id&lt;/code&gt;&lt;/strong&gt;：当前事务自己修改的，可见。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DB_TRX_ID &amp;lt; min_trx_id&lt;/code&gt;&lt;/strong&gt;：在 ReadView 创建前已提交，可见。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DB_TRX_ID &amp;gt;= max_trx_id&lt;/code&gt;&lt;/strong&gt;：在 ReadView 创建后才启动的事务修改的，不可见。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;min_trx_id &amp;lt;= DB_TRX_ID &amp;lt; max_trx_id&lt;/code&gt;&lt;/strong&gt;：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;DB_TRX_ID&lt;/code&gt; 在 &lt;code&gt;m_ids&lt;/code&gt; 列表中 → 事务未提交，不可见。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DB_TRX_ID&lt;/code&gt; 不在 &lt;code&gt;m_ids&lt;/code&gt; 列表中 → 事务已提交，可见。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果当前版本不可见，就沿着 &lt;code&gt;DB_ROLL_PTR&lt;/code&gt; 到 Undo Log 链中找上一个版本，重复判断，直到找到可见版本。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;flowchart TD
    A[SELECT 读取一行] --&amp;gt; B[获取 DB_TRX_ID]
    B --&amp;gt; C{可见性判断}
    C --&amp;gt;|可见| D[返回当前版本]
    C --&amp;gt;|不可见| E[沿 DB_ROLL_PTR 读 Undo Log 上一个版本]
    E --&amp;gt; C&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h4 id=&#34;624-一个具体例子&#34;&gt;6.2.4 一个具体例子&lt;/h4&gt;
&lt;p&gt;假设 &lt;code&gt;id=1&lt;/code&gt; 的行初始值为 &lt;code&gt;x=1&lt;/code&gt;，事务时间线如下：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;时间&lt;/th&gt;
					&lt;th&gt;事务 10&lt;/th&gt;
					&lt;th&gt;事务 20&lt;/th&gt;
					&lt;th&gt;事务 30（查询）&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;T1&lt;/td&gt;
					&lt;td&gt;开始&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;T2&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;开始&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;T3&lt;/td&gt;
					&lt;td&gt;更新 x=2，未提交&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;T4&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;更新 x=3，未提交&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;T5&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;&lt;/td&gt;
					&lt;td&gt;开始，SELECT x&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;事务 30 生成的 ReadView：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;m_ids = [10, 20]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;min_trx_id = 10&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_trx_id = 31&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;creator_trx_id = 30&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;查询时，当前行最新版本的 &lt;code&gt;DB_TRX_ID = 20&lt;/code&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;20&lt;/code&gt; 在 &lt;code&gt;m_ids&lt;/code&gt; 中 → 未提交 → 不可见。&lt;/li&gt;
&lt;li&gt;沿 Undo Log 找到上一个版本，&lt;code&gt;DB_TRX_ID = 10&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;10&lt;/code&gt; 在 &lt;code&gt;m_ids&lt;/code&gt; 中 → 未提交 → 不可见。&lt;/li&gt;
&lt;li&gt;再沿 Undo Log 找到初始版本，&lt;code&gt;DB_TRX_ID = 5&lt;/code&gt;（假设为初始插入）。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;5 &amp;lt; min_trx_id 10&lt;/code&gt; → 已提交 → 可见。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以事务 30 读到的是 &lt;code&gt;x=1&lt;/code&gt;。&lt;/p&gt;
&lt;h4 id=&#34;625-rr-与-rc-的区别&#34;&gt;6.2.5 RR 与 RC 的区别&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RR（可重复读）&lt;/strong&gt;：ReadView 在事务的第一次 SELECT 时创建，之后整个事务复用该 ReadView。因此同一个事务内多次查询结果一致。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RC（读已提交）&lt;/strong&gt;：每次 SELECT 都会创建新的 ReadView，所以能看到其他事务已经提交的最新数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #9&lt;/strong&gt;：RR 级别下，事务开始后创建一致性视图，后续查询都基于该快照。&lt;code&gt;FOR UPDATE&lt;/code&gt; 可以读到最新数据，同时解决第二类更新丢失问题。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;七mysql-优化四大方向&#34;&gt;七、MySQL 优化四大方向&lt;/h2&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[MySQL 优化] --&amp;gt; B[设计层面]
    A --&amp;gt; C[功能层面]
    A --&amp;gt; D[架构层面]
    A --&amp;gt; E[SQL 层面]
    B --&amp;gt; B1[存储引擎 / 字段类型 / 范式]
    C --&amp;gt; C1[索引 / 缓存 / 分区分表]
    D --&amp;gt; D1[主从复制 / 读写分离 / 负载均衡]
    E --&amp;gt; E1[慢 SQL 分析 / 执行计划]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;h3 id=&#34;71-存储引擎选择&#34;&gt;7.1 存储引擎选择&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;引擎&lt;/th&gt;
					&lt;th&gt;特点&lt;/th&gt;
					&lt;th&gt;适用场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;InnoDB&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;支持事务、行锁、外键、崩溃恢复&lt;/td&gt;
					&lt;td&gt;需要数据完整性、高并发的 OLTP 业务&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;MyISAM&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;表锁、无事务、查询和插入速度快&lt;/td&gt;
					&lt;td&gt;读多写少、日志型、临时统计表&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #10&lt;/strong&gt;：MySQL 5.5+ 默认引擎是 InnoDB，新项目&lt;strong&gt;无脑选 InnoDB&lt;/strong&gt;。除非有非常明确的只读/归档场景，否则不要用 MyISAM。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;72-字段类型选择原则&#34;&gt;7.2 字段类型选择原则&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;原则&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;尽可能小&lt;/td&gt;
					&lt;td&gt;能用 &lt;code&gt;INT&lt;/code&gt; 不用 &lt;code&gt;BIGINT&lt;/code&gt;，能用 &lt;code&gt;TINYINT&lt;/code&gt; 不用 &lt;code&gt;INT&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;尽可能定长&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;CHAR&lt;/code&gt; 比 &lt;code&gt;VARCHAR&lt;/code&gt; 更适合短定长文本&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;尽可能用整数&lt;/td&gt;
					&lt;td&gt;整数比较和索引效率高于字符串&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;短文本定长&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;CHAR&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;短文本变长&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;VARCHAR&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;长文本&lt;/td&gt;
					&lt;td&gt;用 &lt;code&gt;TEXT&lt;/code&gt; / &lt;code&gt;BLOB&lt;/code&gt;，避免直接索引大字段&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&#34;73-数据库三大范式&#34;&gt;7.3 数据库三大范式&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[1NF 原子性] --&amp;gt; B[2NF 消除部分依赖]
    B --&amp;gt; C[3NF 消除传递依赖]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;范式&lt;/th&gt;
					&lt;th&gt;核心要求&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;第一范式 1NF&lt;/td&gt;
					&lt;td&gt;字段不可再分，每个字段只存单一值&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;第二范式 2NF&lt;/td&gt;
					&lt;td&gt;非主键字段必须完全依赖主键，不能依赖部分主键&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;第三范式 3NF&lt;/td&gt;
					&lt;td&gt;消除传递依赖，非主键字段只依赖主键&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #11&lt;/strong&gt;：不要过度范式化。互联网业务为了查询性能，常采用&lt;strong&gt;逆范式&lt;/strong&gt;设计，允许适当冗余，用空间换时间。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;74-功能层面优化索引缓存分区分表&#34;&gt;7.4 功能层面优化：索引、缓存、分区分表&lt;/h3&gt;
&lt;p&gt;功能层面优化是在不改变核心业务表结构的前提下，通过索引、缓存、分区分表等手段提升性能。&lt;/p&gt;
&lt;h4 id=&#34;索引优化&#34;&gt;索引优化&lt;/h4&gt;
&lt;p&gt;索引是最常用的功能优化手段。核心原则：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;WHERE&lt;/code&gt;、&lt;code&gt;JOIN&lt;/code&gt;、&lt;code&gt;ORDER BY&lt;/code&gt; 字段建立索引&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;覆盖索引&lt;/strong&gt;：查询字段都在索引中，避免回表。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;最左前缀&lt;/strong&gt;：组合索引按使用频率和字段顺序设计。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;避免索引失效&lt;/strong&gt;：如函数操作索引列、隐式类型转换、前模糊 &lt;code&gt;LIKE&lt;/code&gt;、OR 半边无索引等。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;缓存策略&#34;&gt;缓存策略&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;查询缓存&lt;/strong&gt;：MySQL 8.0 已移除，不建议依赖。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用层缓存&lt;/strong&gt;：Redis / Memcached 缓存热点数据，减少数据库压力。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;注意缓存一致性&lt;/strong&gt;：写数据库后要及时失效或更新缓存，避免脏读。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;分区分表&#34;&gt;分区分表&lt;/h4&gt;
&lt;p&gt;当单表数据量过大（通常超过 500 万 - 1000 万行，或单表超过 10GB）时，需要考虑拆分。主要分为两种：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方式&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;对应用是否透明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;分区（Partition）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;单张表在物理上拆成多个分区文件，逻辑上仍是一张表&lt;/td&gt;
					&lt;td&gt;基本透明，应用按普通表读写&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;分表（Sharding）&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;把数据拆到多张物理表中，如 &lt;code&gt;user_0&lt;/code&gt; - &lt;code&gt;user_15&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;不透明，通常需要分库分表中间件或应用层路由&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;h5 id=&#34;分区partition&#34;&gt;分区（Partition）&lt;/h5&gt;
&lt;p&gt;分区是 MySQL 提供的原生功能，一张逻辑表由多个物理分区组成，每个分区独立存储。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;常见分区类型：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;类型&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;适用场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;RANGE&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;按连续范围分区，如按时间、ID 范围&lt;/td&gt;
					&lt;td&gt;日志表、订单表按月份归档&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;LIST&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;按离散值列表分区&lt;/td&gt;
					&lt;td&gt;按地区、状态等枚举值&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;HASH&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;按分区键哈希取模&lt;/td&gt;
					&lt;td&gt;数据分布均匀，无业务热点&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;KEY&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;类似 HASH，但由 MySQL 内部决定哈希函数&lt;/td&gt;
					&lt;td&gt;简化使用&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;创建分区表示例（RANGE）：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CREATE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TABLE&lt;/span&gt; user_log (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    id BIGINT &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    user_id BIGINT &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    action VARCHAR(&lt;span style=&#34;color:#ae81ff&#34;&gt;50&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    created_at DATETIME &lt;span style=&#34;color:#66d9ef&#34;&gt;NOT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;NULL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;) PARTITION &lt;span style=&#34;color:#66d9ef&#34;&gt;BY&lt;/span&gt; RANGE (&lt;span style=&#34;color:#66d9ef&#34;&gt;YEAR&lt;/span&gt;(created_at)) (
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PARTITION p2022 &lt;span style=&#34;color:#66d9ef&#34;&gt;VALUES&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;LESS&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;THAN&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;2023&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PARTITION p2023 &lt;span style=&#34;color:#66d9ef&#34;&gt;VALUES&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;LESS&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;THAN&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;2024&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PARTITION p2024 &lt;span style=&#34;color:#66d9ef&#34;&gt;VALUES&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;LESS&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;THAN&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;2025&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    PARTITION pfuture &lt;span style=&#34;color:#66d9ef&#34;&gt;VALUES&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;LESS&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;THAN&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;MAXVALUE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;分区表的增删改查：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 查询整个表（和普通表一样）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; user_log &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; created_at &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2024-01-01&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 指定分区查询（强制扫描某个分区）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; user_log PARTITION (p2024) &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; action &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;login&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 插入数据（MySQL 自动根据分区键路由）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;INSERT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;INTO&lt;/span&gt; user_log &lt;span style=&#34;color:#66d9ef&#34;&gt;VALUES&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10086&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;login&amp;#39;&lt;/span&gt;, &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2024-06-01 10:00:00&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 更新数据（如果修改分区键，可能需要跨分区移动行）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;UPDATE&lt;/span&gt; user_log &lt;span style=&#34;color:#66d9ef&#34;&gt;SET&lt;/span&gt; created_at &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2025-01-01&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 删除整个分区的数据（比分批 DELETE 快得多）
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;ALTER&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TABLE&lt;/span&gt; user_log &lt;span style=&#34;color:#66d9ef&#34;&gt;DROP&lt;/span&gt; PARTITION p2022;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;分区表的关键点：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分区键必须包含在唯一索引/主键中&lt;/strong&gt;。例如主键是 &lt;code&gt;(id)&lt;/code&gt;，那么不能按 &lt;code&gt;created_at&lt;/code&gt; 分区，除非主键改成 &lt;code&gt;(id, created_at)&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;查询要命中分区键才能裁剪&lt;/strong&gt;。如果 &lt;code&gt;WHERE&lt;/code&gt; 里没有分区键，MySQL 会扫描所有分区，反而更慢。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分区数量不宜过多&lt;/strong&gt;。MySQL 官方建议单表分区数控制在 1000 以内，过多会消耗文件描述符和内存。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&#34;分表sharding&#34;&gt;分表（Sharding）&lt;/h5&gt;
&lt;p&gt;分表是把一张逻辑表拆成多张物理表，通常需要配合分库分表中间件（如 ShardingSphere、MyCat）或应用层路由。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;拆分方式&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
					&lt;th&gt;适用场景&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;垂直拆分&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;按业务/字段拆分，如 &lt;code&gt;user_base&lt;/code&gt; 和 &lt;code&gt;user_profile&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;单表字段过多、冷热数据分离&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;水平拆分&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;按行拆分，如 &lt;code&gt;user_id % 16&lt;/code&gt; 分 16 张表&lt;/td&gt;
					&lt;td&gt;单表数据量过大、并发过高&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;水平拆分策略：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;策略&lt;/th&gt;
					&lt;th&gt;优点&lt;/th&gt;
					&lt;th&gt;缺点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;范围分片&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;按时间/ID 范围，扩容简单&lt;/td&gt;
					&lt;td&gt;容易热点，如最新时间片集中写入&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;哈希分片&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;数据分布均匀&lt;/td&gt;
					&lt;td&gt;扩容需要迁移数据&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;一致性哈希&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;扩容时迁移数据量少&lt;/td&gt;
					&lt;td&gt;实现稍复杂&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;列表分片&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;按地区/类型等枚举值&lt;/td&gt;
					&lt;td&gt;适合有明显业务边界的数据&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;分库分表带来的问题：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;分布式事务&lt;/strong&gt;：跨库事务无法靠单机 ACID 保证。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;跨库 JOIN&lt;/strong&gt;：需要在应用层聚合，或反范式冗余字段。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;全局唯一 ID&lt;/strong&gt;：不能用自增主键，需要雪花算法、号段等方案。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;分页排序&lt;/strong&gt;：&lt;code&gt;ORDER BY&lt;/code&gt; + &lt;code&gt;LIMIT&lt;/code&gt; 跨多表后合并复杂。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据迁移&lt;/strong&gt;：扩容时需要平滑迁移历史数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #14&lt;/strong&gt;：不要为了拆分而拆分。大多数业务先做好索引、SQL 优化、加缓存，单表撑到千万级没问题。拆分成本：分区 &amp;lt; 分表 &amp;lt; 分库。优先考虑分区；分区也撑不住时，再考虑分库分表。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;75-sql-层面优化慢-sql-分析与执行计划&#34;&gt;7.5 SQL 层面优化：慢 SQL 分析与执行计划&lt;/h3&gt;
&lt;p&gt;SQL 层面的优化收益往往最高，因为一条烂 SQL 可以把整个数据库拖垮。&lt;/p&gt;
&lt;h4 id=&#34;如何发现慢-sql&#34;&gt;如何发现慢 SQL&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;慢查询日志&lt;/strong&gt;：&lt;code&gt;slow_query_log = ON&lt;/code&gt;，&lt;code&gt;long_query_time&lt;/code&gt; 设置阈值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;performance_schema&lt;/strong&gt;：MySQL 内置性能监控。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pt-query-digest&lt;/strong&gt;：Percona Toolkit 分析慢日志。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;应用层 APM&lt;/strong&gt;：SkyWalking、Pinpoint 等追踪 SQL 耗时。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;explain-看执行计划&#34;&gt;EXPLAIN 看执行计划&lt;/h4&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;EXPLAIN&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; age &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;18&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ORDER&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;BY&lt;/span&gt; created_at &lt;span style=&#34;color:#66d9ef&#34;&gt;LIMIT&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;关键字段：&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;字段&lt;/th&gt;
					&lt;th&gt;含义&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;type&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;访问类型，越靠前越好：&lt;code&gt;system &amp;gt; const &amp;gt; eq_ref &amp;gt; ref &amp;gt; range &amp;gt; index &amp;gt; ALL&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;key&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;实际使用的索引&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;rows&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;预估扫描行数&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;Extra&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;额外信息&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;type&lt;/code&gt; 详解：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;type&lt;/code&gt; 表示 MySQL 访问表的方式，可以理解为“查资料时翻书的方式”。&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;type&lt;/th&gt;
					&lt;th&gt;通俗解释&lt;/th&gt;
					&lt;th&gt;效率&lt;/th&gt;
					&lt;th&gt;典型 SQL&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;system&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;表里最多 1 条数据，直接当常量&lt;/td&gt;
					&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
					&lt;td&gt;查只有一条配置的配置表&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;const&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;通过主键/唯一索引等值查，最多 1 条&lt;/td&gt;
					&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;WHERE id = 1&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;eq_ref&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;JOIN 时，被驱动表用主键/唯一索引匹配，最多 1 条&lt;/td&gt;
					&lt;td&gt;⭐⭐⭐⭐⭐&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;A JOIN B ON A.b_id = B.id&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;ref&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;通过普通索引等值查，可能有多条&lt;/td&gt;
					&lt;td&gt;⭐⭐⭐⭐&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;WHERE phone = &#39;xxx&#39;&lt;/code&gt;（phone 是普通索引）&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;range&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;索引范围扫描&lt;/td&gt;
					&lt;td&gt;⭐⭐⭐&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;WHERE age &amp;gt; 18&lt;/code&gt;、&lt;code&gt;BETWEEN&lt;/code&gt;、&lt;code&gt;IN&lt;/code&gt;、&lt;code&gt;LIKE &#39;prefix%&#39;&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;index&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;全索引扫描&lt;/td&gt;
					&lt;td&gt;⭐⭐&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;SELECT 索引列 FROM 表&lt;/code&gt;、按索引排序&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;strong&gt;ALL&lt;/strong&gt;&lt;/td&gt;
					&lt;td&gt;全表扫描&lt;/td&gt;
					&lt;td&gt;⭐&lt;/td&gt;
					&lt;td&gt;没走索引，逐行读&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;各 type 详细说明与示例：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. system：表本身就是常量&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;表里只有 0 条或 1 条数据，MySQL 把它当成常量处理。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; system_config;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;如果 &lt;code&gt;system_config&lt;/code&gt; 只有一条记录，EXPLAIN 可能看到 &lt;code&gt;type = system&lt;/code&gt;。实际业务表中很少见。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. const：精确命中唯一值&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过主键或唯一索引做等值查询，最多返回 1 行。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- id 是主键
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10086&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- phone 是唯一索引
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; phone &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;13800138000&amp;#39;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;这是单表查询里最好的 type。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. eq_ref：JOIN 中的 const&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;多表 JOIN 时，被驱动表通过主键或唯一索引与驱动表匹配，每次匹配最多返回 1 行。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; o.&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;, u.name
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; o
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;JOIN&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; u &lt;span style=&#34;color:#66d9ef&#34;&gt;ON&lt;/span&gt; o.user_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; u.id;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;假设 &lt;code&gt;user.id&lt;/code&gt; 是主键，&lt;code&gt;order&lt;/code&gt; 表的每一行去 &lt;code&gt;user&lt;/code&gt; 表查都是精确命中 1 行，此时 &lt;code&gt;user&lt;/code&gt; 表的 &lt;code&gt;type = eq_ref&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. ref：普通索引等值查询&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;通过普通索引（非唯一索引）做等值查询，可能返回多条。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- status 是普通索引
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; status &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;PAID&amp;#39;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;code&gt;status = &#39;PAID&#39;&lt;/code&gt; 可能对应很多订单。这是生产环境最常见的良好 type。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. range：索引范围扫描&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;使用索引做范围查询。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- age 有索引
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; age &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;18&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- create_time 有索引
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; create_time &lt;span style=&#34;color:#66d9ef&#34;&gt;BETWEEN&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2024-01-01&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;AND&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2024-01-31&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- id 是主键
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; id &lt;span style=&#34;color:#66d9ef&#34;&gt;IN&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;4&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- phone 有索引
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; phone &lt;span style=&#34;color:#66d9ef&#34;&gt;LIKE&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;1380%&amp;#39;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;注意：&lt;code&gt;LIKE &#39;1380%&#39;&lt;/code&gt; 走 range；&lt;code&gt;LIKE &#39;%1380&#39;&lt;/code&gt; 或 &lt;code&gt;LIKE &#39;%1380%&#39;&lt;/code&gt; 会退化成 ALL。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. index：全索引扫描&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;遍历整个索引树，而不是全表扫描数据。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- name 是索引列
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; name &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 按索引列排序
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; id &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ORDER&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;BY&lt;/span&gt; id;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;因为索引通常比实际数据小，读索引比读整行快。但如果查询列不在索引里或需要大量回表，index 也会很慢。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7. ALL：全表扫描&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;没有用到索引，逐行读取整张表。&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- phone 没有索引
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; phone &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;13800138000&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 对索引列使用函数
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; DATE(create_time) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2024-01-01&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 前模糊 LIKE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; phone &lt;span style=&#34;color:#66d9ef&#34;&gt;LIKE&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;%1380%&amp;#39;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;生产环境出现 &lt;code&gt;type = ALL&lt;/code&gt; 通常需要优化。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;简单记忆口诀：&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;system/const 是精确，一行数据最省事；
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;eq_ref 是 JOIN 版 const，主键关联一对一；
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ref 普通索引等值查，可能返回好几行；
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;range 范围扫描用得广，BETWEEN IN 都能上；
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;index 全扫索引比表强；
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;ALL 全表扫描要提防。&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;生产环境建议：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;type&lt;/th&gt;
					&lt;th&gt;是否需要关注&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;system / const / eq_ref / ref&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;优秀，基本不用管&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;range&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;正常，注意范围不要太大&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;index&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;需要警惕，确认是否真的需要全索引扫描&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;ALL&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;必须优化，除非表很小（几万行以内）&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;一般原则：单表查询尽量做到 &lt;code&gt;ref&lt;/code&gt; 或更好，范围查询 &lt;code&gt;range&lt;/code&gt; 可接受，尽量避免 &lt;code&gt;ALL&lt;/code&gt; 和大量数据的 &lt;code&gt;index&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;危险的 Extra：&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Using filesort&lt;/code&gt;：需要额外排序，常见于无索引的 &lt;code&gt;ORDER BY&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Using temporary&lt;/code&gt;：需要创建临时表，常见于 &lt;code&gt;GROUP BY&lt;/code&gt;、&lt;code&gt;DISTINCT&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Using index&lt;/code&gt;：好现象，使用了覆盖索引。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Using where&lt;/code&gt;：在存储引擎返回数据后再过滤。&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&#34;典型慢-sql-写法与严重踩坑案例&#34;&gt;典型慢 SQL 写法与严重踩坑案例&lt;/h4&gt;
&lt;h5 id=&#34;案例-1select-&#34;&gt;案例 1：&lt;code&gt;SELECT *&lt;/code&gt;&lt;/h5&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; user_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10086&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：读取不需要的列，增加网络传输和 Buffer Pool 占用，容易触发回表。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; order_id, status, amount &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; user_id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10086&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h5 id=&#34;案例-2前模糊-like-导致索引失效&#34;&gt;案例 2：前模糊 LIKE 导致索引失效&lt;/h5&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; phone &lt;span style=&#34;color:#66d9ef&#34;&gt;LIKE&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;%1380%&amp;#39;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：&lt;code&gt;%&lt;/code&gt; 在开头无法使用 B+ 树索引，会全表扫描。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;改为后缀匹配：&lt;code&gt;LIKE &#39;1380%&#39;&lt;/code&gt;（如果有索引仍可用）。&lt;/li&gt;
&lt;li&gt;使用全文索引或 Elasticsearch。&lt;/li&gt;
&lt;/ul&gt;
&lt;h5 id=&#34;案例-3or-条件半边无索引&#34;&gt;案例 3：&lt;code&gt;OR&lt;/code&gt; 条件半边无索引&lt;/h5&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; phone &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;13800138000&amp;#39;&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;OR&lt;/span&gt; email &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;a@b.com&amp;#39;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：如果 &lt;code&gt;email&lt;/code&gt; 没有索引，MySQL 可能放弃索引，直接全表扫描。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; phone &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;13800138000&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;UNION&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ALL&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; email &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;a@b.com&amp;#39;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;或给 &lt;code&gt;email&lt;/code&gt; 也建索引。&lt;/p&gt;
&lt;h5 id=&#34;案例-4隐式类型转换&#34;&gt;案例 4：隐式类型转换&lt;/h5&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; phone &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;13800138000&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：&lt;code&gt;phone&lt;/code&gt; 是 &lt;code&gt;varchar&lt;/code&gt;，MySQL 会先把所有行的 &lt;code&gt;phone&lt;/code&gt; 转成整数再比较，索引失效。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; phone &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;13800138000&amp;#39;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h5 id=&#34;案例-5函数操作索引列&#34;&gt;案例 5：函数操作索引列&lt;/h5&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; DATE(create_time) &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2024-01-01&amp;#39;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：对索引列使用函数，&lt;code&gt;DATE(create_time)&lt;/code&gt; 不是索引本身，索引失效。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化&lt;/strong&gt;：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; create_time &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2024-01-01&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;color:#66d9ef&#34;&gt;AND&lt;/span&gt; create_time &lt;span style=&#34;color:#f92672&#34;&gt;&amp;lt;&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;2024-01-02&amp;#39;&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h5 id=&#34;案例-6大-offset-深分页&#34;&gt;案例 6：大 OFFSET 深分页&lt;/h5&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ORDER&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;BY&lt;/span&gt; id &lt;span style=&#34;color:#66d9ef&#34;&gt;LIMIT&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000000&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：MySQL 需要先扫描 1000010 行，再扔掉前面的 1000000 行，越往后越慢。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化&lt;/strong&gt;（延迟关联）：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; o.&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; o
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;JOIN&lt;/span&gt; (&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; id &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ORDER&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;BY&lt;/span&gt; id &lt;span style=&#34;color:#66d9ef&#34;&gt;LIMIT&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1000000&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;) tmp &lt;span style=&#34;color:#66d9ef&#34;&gt;ON&lt;/span&gt; o.id &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; tmp.id;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;或记录上次位置：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;order&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; id &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;?&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ORDER&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;BY&lt;/span&gt; id &lt;span style=&#34;color:#66d9ef&#34;&gt;LIMIT&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;10&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;h5 id=&#34;案例-7in-子句元素过多&#34;&gt;案例 7：&lt;code&gt;IN&lt;/code&gt; 子句元素过多&lt;/h5&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;SELECT&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;FROM&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;user&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; id &lt;span style=&#34;color:#66d9ef&#34;&gt;IN&lt;/span&gt; (&lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;2&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;, ..., &lt;span style=&#34;color:#ae81ff&#34;&gt;50000&lt;/span&gt;);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：IN 元素过多会导致解析和执行效率下降，甚至超出限制。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化&lt;/strong&gt;：分批查询，或把 ID 写入临时表再 JOIN。&lt;/p&gt;
&lt;h5 id=&#34;案例-8大事务长时间持有锁&#34;&gt;案例 8：大事务长时间持有锁&lt;/h5&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;START&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TRANSACTION&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 大量更新操作
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;UPDATE&lt;/span&gt; huge_table &lt;span style=&#34;color:#66d9ef&#34;&gt;SET&lt;/span&gt; ... &lt;span style=&#34;color:#66d9ef&#34;&gt;WHERE&lt;/span&gt; ...;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 业务逻辑处理
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 几分钟后才 COMMIT&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;p&gt;&lt;strong&gt;问题&lt;/strong&gt;：长时间持有行锁/间隙锁，阻塞其他事务，Undo Log 堆积，主从延迟放大。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;优化&lt;/strong&gt;：拆分为小批量事务，减少单次锁定范围。&lt;/p&gt;
&lt;h4 id=&#34;线上最严重的几个踩坑&#34;&gt;线上最严重的几个踩坑&lt;/h4&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;踩坑行为&lt;/th&gt;
					&lt;th&gt;后果&lt;/th&gt;
					&lt;th&gt;正确做法&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;&lt;code&gt;UPDATE / DELETE&lt;/code&gt; 忘记写 &lt;code&gt;WHERE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;全表数据被改/删，可能导致业务瘫痪&lt;/td&gt;
					&lt;td&gt;先 &lt;code&gt;SELECT&lt;/code&gt; 确认影响行数，使用 &lt;code&gt;LIMIT&lt;/code&gt;，小批量执行&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;高峰期执行 &lt;code&gt;ALTER TABLE&lt;/code&gt; / &lt;code&gt;OPTIMIZE&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;锁表，查询堆积，服务雪崩&lt;/td&gt;
					&lt;td&gt;使用 Online DDL（如 &lt;code&gt;pt-online-schema-change&lt;/code&gt;），低峰期执行&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;直接 &lt;code&gt;SELECT * FROM huge_table&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;打满 Buffer Pool，拖垮数据库&lt;/td&gt;
					&lt;td&gt;加分页、加条件、只查必要字段&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;慢 SQL 打满连接池&lt;/td&gt;
					&lt;td&gt;应用无法获取连接，全面超时&lt;/td&gt;
					&lt;td&gt;设置 SQL 超时，连接池隔离，熔断降级&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #15&lt;/strong&gt;：上线任何 SQL 前，先用 &lt;code&gt;EXPLAIN&lt;/code&gt; 检查执行计划。看到 &lt;code&gt;type = ALL&lt;/code&gt;、&lt;code&gt;Extra = Using filesort&lt;/code&gt; 或 &lt;code&gt;Using temporary&lt;/code&gt; 时，必须优化后再上线。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&#34;八高性能架构设计&#34;&gt;八、高性能架构设计&lt;/h2&gt;
&lt;h3 id=&#34;81-主从复制--读写分离&#34;&gt;8.1 主从复制 + 读写分离&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[写请求] --&amp;gt; B[主库 Master]
    B --&amp;gt;|binlog 同步| C[从库 Slave 1]
    B --&amp;gt;|binlog 同步| D[从库 Slave 2]
    E[读请求] --&amp;gt; C
    E --&amp;gt; D&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #12&lt;/strong&gt;：主从复制存在延迟。如果对实时性要求极高（如支付后立刻查询余额），读请求应强制走主库，或在业务层做延迟容忍设计。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;82-分区分表&#34;&gt;8.2 分区分表&lt;/h3&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;方式&lt;/th&gt;
					&lt;th&gt;说明&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;垂直拆分&lt;/td&gt;
					&lt;td&gt;按业务/字段拆分，如用户表拆成 &lt;code&gt;user_base&lt;/code&gt; 和 &lt;code&gt;user_profile&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;水平拆分&lt;/td&gt;
					&lt;td&gt;按行拆分，如按 &lt;code&gt;user_id % 16&lt;/code&gt; 分 16 张表&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;九用户权限与数据导出&#34;&gt;九、用户权限与数据导出&lt;/h2&gt;
&lt;h3 id=&#34;91-创建新用户并授权&#34;&gt;9.1 创建新用户并授权&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-sql&#34; data-lang=&#34;sql&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 创建用户
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;CREATE&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;USER&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;goose_vod&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;@&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;%&amp;#39;&lt;/span&gt; IDENTIFIED &lt;span style=&#34;color:#66d9ef&#34;&gt;BY&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;xxxx&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 授权
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;GRANT&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ALL&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;PRIVILEGES&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;ON&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;ad_db&lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;.&lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;TO&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;`&lt;/span&gt;goose_vod&lt;span style=&#34;color:#f92672&#34;&gt;`@`%`&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;-- 刷新权限
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;FLUSH &lt;span style=&#34;color:#66d9ef&#34;&gt;PRIVILEGES&lt;/span&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;避坑指南 #13&lt;/strong&gt;：生产环境请遵循最小权限原则，不要给业务账号 &lt;code&gt;ALL PRIVILEGES&lt;/code&gt;。例如只读账号只授予 &lt;code&gt;SELECT&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&#34;92-导出数据库&#34;&gt;9.2 导出数据库&lt;/h3&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;mysqldump -h xxxx-rds.xxxx.us-east-1.rds.amazonaws.com &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  -u goose_vod -p appstore_db &amp;gt; ~/database_dump.txt&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;/div&gt;&lt;hr&gt;
&lt;h2 id=&#34;十避坑清单速查表&#34;&gt;十、避坑清单速查表&lt;/h2&gt;
&lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;编号&lt;/th&gt;
					&lt;th&gt;避坑点&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;1&lt;/td&gt;
					&lt;td&gt;数据库字符集用 &lt;code&gt;utf8mb4&lt;/code&gt;，不要用默认 &lt;code&gt;utf8&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;2&lt;/td&gt;
					&lt;td&gt;JDBC 连接串指定 &lt;code&gt;serverTimezone&lt;/code&gt; 和 &lt;code&gt;characterEncoding&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;3&lt;/td&gt;
					&lt;td&gt;通用日志只用于临时排查，不要长期开启&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;4&lt;/td&gt;
					&lt;td&gt;存储过程谨慎使用，优先应用层处理业务逻辑&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;5&lt;/td&gt;
					&lt;td&gt;不可重复读 ≠ 幻读，RR 级别下 InnoDB 基本解决幻读&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;6&lt;/td&gt;
					&lt;td&gt;根据业务决定是否将隔离级别从 RR 改为 RC&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;7&lt;/td&gt;
					&lt;td&gt;组合索引遵循最左前缀原则&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;8&lt;/td&gt;
					&lt;td&gt;普通 &lt;code&gt;SELECT&lt;/code&gt; 不加锁，需要锁时显式 &lt;code&gt;FOR UPDATE&lt;/code&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;9&lt;/td&gt;
					&lt;td&gt;RR 下快照读基于 ReadView，&lt;code&gt;FOR UPDATE&lt;/code&gt; 读最新数据&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;10&lt;/td&gt;
					&lt;td&gt;新项目默认使用 InnoDB，不要选 MyISAM&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;11&lt;/td&gt;
					&lt;td&gt;适度逆范式，用空间换查询时间&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;12&lt;/td&gt;
					&lt;td&gt;主从延迟存在，实时读考虑强制走主库&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;13&lt;/td&gt;
					&lt;td&gt;生产环境遵循最小权限原则&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;14&lt;/td&gt;
					&lt;td&gt;不要为了拆分而拆分；拆分成本：分区 &amp;lt; 分表 &amp;lt; 分库，优先考虑分区&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;15&lt;/td&gt;
					&lt;td&gt;上线前用 EXPLAIN 检查执行计划，警惕 ALL / filesort / temporary&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id=&#34;十一总结&#34;&gt;十一、总结&lt;/h2&gt;
&lt;p&gt;MySQL 的学习路径可以概括为：&lt;/p&gt;
&lt;div class=&#34;code-block&#34;&gt;
    &lt;button class=&#34;code-copy&#34; type=&#34;button&#34; hidden aria-label=&#34;Copy code to clipboard&#34;&gt;
      &lt;span class=&#34;code-copy-label&#34; aria-hidden=&#34;true&#34;&gt;Copy&lt;/span&gt;
    &lt;/button&gt;
    &lt;pre tabindex=&#34;0&#34;&gt;&lt;code class=&#34;language-mermaid&#34; data-lang=&#34;mermaid&#34;&gt;graph LR
    A[熟练 SQL 与安装配置] --&amp;gt; B[理解事务与隔离级别]
    B --&amp;gt; C[掌握索引设计与优化]
    C --&amp;gt; D[理解锁与 MVCC]
    D --&amp;gt; E[掌握主从复制与读写分离]
    E --&amp;gt; F[持续分析慢 SQL 与执行计划]&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;&lt;p&gt;高性能 MySQL 没有银弹，核心在于：&lt;strong&gt;合理的表结构设计、精准的索引、恰到好处的事务隔离、以及能够水平扩展的架构&lt;/strong&gt;。希望这份笔记能成为你日常开发与面试复习的速查手册。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;参考&#34;&gt;参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/zhangkanghui/p/9613844.html&#34;&gt;MySQL 5.7 安装教程&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.cnblogs.com/sharpest/p/10390035.html&#34;&gt;MySQL 优化四大方面&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.csdn.net/weixin_43895671/article/details/107402521&#34;&gt;MySQL 锁与事务详解&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
    </item>
    
  </channel>
</rss>
