<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
    <channel>
            <title>Error's Blog</title>
            <link>https://zygzyg.com</link>
                <description>陰霾天气，隐约雷鸣，即使天无雨，我亦留此地。</description>
        <generator>Halo 1.6.0</generator>
        <lastBuildDate>Wed, 10 May 2023 09:07:28 CST</lastBuildDate>
                <item>
                    <title>
                        <![CDATA[Redis数据结构-SkipList]]>
                    </title>
                    <link>https://zygzyg.com/archives/a167b8335bb44ab79a8f1915687f4737</link>
                    <description>
                            <![CDATA[<h1 id="redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-skiplist" tabindex="-1">Redis数据结构-SkipList</h1><blockquote><p>在<a href="/archives/656e671f7923431489632abbab88acc1">《Redis数据结构》</a>中我们说到Redis的<strong>Soted Set</strong>类型底层使用的是<code>ziplist</code>以及<code>skiplist</code>，然后在Redis7.0的时候正式使用<code>listpack</code>替换了<code>ziplist</code>，本文主要简单说下<code>skiplist</code>，观关于<code>ziplist</code>以及<code>listpack</code>可以看看<a href="/archives/ec31e437eaab44d195729c2e8f548722">《Redis数据结构-ZipList与ListPack》</a>这篇文章。</p></blockquote><h2 id="%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E4%BD%BF%E7%94%A8skiplist" tabindex="-1">什么时候使用skiplist</h2><blockquote><p>在Redis中只有<strong>Sorted Set</strong>使用到了<code>skiplist</code>。<code>skiplist</code>在查找、删除、添加等操作的时间复杂度都是O(log<sub>2</sub>N)，这就是跳跃表的优点，其缺点就是需要的存储空间比较大，属于用空间换时间的类型。</p></blockquote><p>在Redis的配置文件中我们可以看到这么两个配置：</p><pre><code class="language-conf"># 与Hash以及List类型类似，为了节省大量空间，# Sorted Set在存储时也会被特殊编码。这种编码仅在Sorted Set的长度和元素低于以下限制时使用：# ==============================Redis6.0==============================zset-max-ziplist-entries 128zset-max-ziplist-value 64# ==============================Redis7.0==============================zset-max-listpack-entries 128zset-max-listpack-value 64</code></pre><p>这里使用Redis7.0来测试，首先将配置<code>zset-max-listpack-entries</code>改小一点方便测试，这里我们改成3：</p><pre><code class="language-shell">127.0.0.1:6379&gt; CONFIG SET zset-max-listpack-entries 3OK127.0.0.1:6379&gt; ZADD zset1 1 a 2 b 3 c(integer) 3127.0.0.1:6379&gt; OBJECT ENCODING zset1&quot;listpack&quot;127.0.0.1:6379&gt; ZADD zset1 4 d(integer) 1127.0.0.1:6379&gt; OBJECT ENCODING zset1&quot;skiplist&quot;</code></pre><p>可以看到这里元素数量超过3后，编码方式就从<code>listpack</code>变成了<code>skiplist</code>，然后我们再将配置<code>zset-max-listpack-value</code>改成5：</p><pre><code class="language-shell">127.0.0.1:6379&gt; CONFIG SET zset-max-listpack-value 5OK127.0.0.1:6379&gt; ZADD zset2 1 a(integer) 1127.0.0.1:6379&gt; OBJECT ENCODING zset2&quot;listpack&quot;127.0.0.1:6379&gt; ZADD zset2 1 abcdefg(integer) 1127.0.0.1:6379&gt; OBJECT ENCODING zset2&quot;skiplist&quot;</code></pre><p>同理当我们添加的元素长度超过5后，编码方式也会从从<code>listpack</code>变成了<code>skiplist</code>。</p><h2 id="skiplist%E6%98%AF%E4%BB%80%E4%B9%88" tabindex="-1">skiplist是什么</h2><h3 id="%E5%8D%95%E5%90%91%E9%93%BE%E8%A1%A8" tabindex="-1">单向链表</h3><p>首先我们先来看一个单向链表，即使这个链表中的数据都是有序的，但是当我们想要查找某一个数据的时候，也只能遍历这个链表，时间复杂度是O(n)，比如在下面这个链表中，想要查找39，则需要遍历8次才行。</p><p><img src="/upload/2023/04/%E5%8D%95%E5%90%91%E9%93%BE%E8%A1%A8.png" alt="单向链表" /></p><blockquote><p>我们都知道数组以及链表它们都有自己的优缺点，那有没有办法搞一种数据结构，使得链表的遍历也能快呢？</p></blockquote><p>既然链表遍历慢，时间复杂度是O(n)，那尝试给链表加个索引是否能变快呢？这里将上面的链表稍微改造一下，我们在每个节点上新增一个指针<code>down</code>，并且垂直扩充链表，最后可以得到下面这样一个数据结构：</p><p><img src="/upload/2023/04/%E5%A2%9E%E5%8A%A0%E5%A4%9A%E7%BA%A7%E7%B4%A2%E5%BC%95%E7%9A%84%E5%8D%95%E5%90%91%E9%93%BE%E8%A1%A8.png" alt="增加多级索引的单向链表" /></p><p>这样就相当于增加一个“捷径”（多级索引），减少了遍历的次数，这个时候我们仅仅只需要查找2次就可以查找到39，但是相对的维护这么一个结构也增加了内存空间的消耗。</p><h3 id="skiplist" tabindex="-1">skiplist</h3><p>从上面我们就可以得知，<code>skiplist</code>实际就是一个用空间换时间的数据结构，<code>skiplist</code>提取出链表中的关键节点（索引），先在关键节点上面查找，再进入下层链表查找，这样提取出多层的关键节点，最后就形成了跳跃表（<code>skiplist</code>）。</p><h4 id="%E6%97%B6%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6" tabindex="-1">时间复杂度</h4><blockquote><p>如果一个链表有<strong>N</strong>个节点，那么<code>skiplist</code>里会有多少级索引呢？</p></blockquote><p>按照上面的结构来推算：</p><ol><li>第一级索引大约有<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mi>N</mi><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">N\over2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.217331em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>个节点。</li><li>第二级索引大约有<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mi>N</mi><mn>4</mn></mfrac></mrow><annotation encoding="application/x-tex">N\over4</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.217331em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">4</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>个节点。</li><li>第三级索引大约有<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mi>N</mi><mn>8</mn></mfrac></mrow><annotation encoding="application/x-tex">N\over8</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.217331em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">8</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>个节点。</li></ol><p>依次类推，所以我们可以知道第<strong>M</strong>级索引将大约有<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mi>N</mi><msup><mn>2</mn><mi>M</mi></msup></mfrac></mrow><annotation encoding="application/x-tex">\frac{N}{2^M}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2241959999999998em;vertical-align:-0.35186499999999993em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.648135em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7740928571428571em;"><span style="top:-2.786em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">M</span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35186499999999993em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>。假设索引的最高级<strong>T</strong>，只有两个节点（头节点和尾节点），通过这个公式我们可以得知，<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mi>N</mi><msup><mn>2</mn><mi>T</mi></msup></mfrac><mo>=</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">\frac{N}{2^T}=2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.2241959999999998em;vertical-align:-0.35186499999999993em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.648135em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight"><span class="mord mtight">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7740928571428571em;"><span style="top:-2.786em;margin-right:0.07142857142857144em;"><span class="pstrut" style="height:2.5em;"></span><span class="sizing reset-size3 size1 mtight"><span class="mord mathnormal mtight" style="margin-right:0.13889em;">T</span></span></span></span></span></span></span></span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.35186499999999993em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">2</span></span></span></span>，所以最后我们可以得知最高级<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi><mo>=</mo><mi>log</mi><mo>⁡</mo><mo stretchy="false">(</mo><msup><mn>2</mn><mi>N</mi></msup><mo stretchy="false">)</mo><mo>−</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">T=\log(2^{N})-1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.68333em;vertical-align:0em;"></span><span class="mord mathnormal" style="margin-right:0.13889em;">T</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:1.0913309999999998em;vertical-align:-0.25em;"></span><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="mopen">(</span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8413309999999999em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span></span></span></span></span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">1</span></span></span></span>，如果包含原始的链表，那么整个<code>skiplist</code>的高度就是<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>log</mi><mo>⁡</mo><mo stretchy="false">(</mo><msup><mn>2</mn><mi>N</mi></msup><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\log(2^N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0913309999999998em;vertical-align:-0.25em;"></span><span class="mop">lo<span style="margin-right:0.01389em;">g</span></span><span class="mopen">(</span><span class="mord"><span class="mord">2</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8413309999999999em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span>。</p><p>所以<code>skiplist</code>的时间复杂度也就是O(log<sub>2</sub>N)。</p><h4 id="%E7%A9%BA%E9%97%B4%E5%A4%8D%E6%9D%82%E5%BA%A6" tabindex="-1">空间复杂度</h4><blockquote><p>相对于一个普通单向链表，一个<code>skiplist</code>需要额外存储多级索引，那这些额外的索引需要消耗多少空间呢？</p></blockquote><p>这里还是假设有一个长度为<strong>N</strong>的单向链表，根据上面我们得知从第一级索引开始每层的节点数分别是：<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mi>N</mi><mn>2</mn></mfrac></mrow><annotation encoding="application/x-tex">N\over2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.217331em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>、<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mi>N</mi><mn>4</mn></mfrac></mrow><annotation encoding="application/x-tex">N\over4</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.217331em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">4</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>、<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mi>N</mi><mn>8</mn></mfrac></mrow><annotation encoding="application/x-tex">N\over8</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.217331em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">8</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span>…<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>8</mn></mrow><annotation encoding="application/x-tex">8</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">8</span></span></span></span>、<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>4</mn></mrow><annotation encoding="application/x-tex">4</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">4</span></span></span></span>、<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>2</mn></mrow><annotation encoding="application/x-tex">2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">2</span></span></span></span>，每往上一层，节点就少一半，直到最后只剩下2个节点。就是一个等比数列。那么这些索引的总和也就是<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mfrac><mi>N</mi><mn>2</mn></mfrac><mo>+</mo><mfrac><mi>N</mi><mn>4</mn></mfrac><mo>+</mo><mfrac><mi>N</mi><mn>8</mn></mfrac><mo>+</mo><mi mathvariant="normal">.</mi><mi mathvariant="normal">.</mi><mi mathvariant="normal">.</mi><mo>+</mo><mn>8</mn><mo>+</mo><mn>4</mn><mo>+</mo><mn>2</mn><mo>=</mo><mi>n</mi><mo>−</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">\frac{N}{2}+\frac{N}{4}+\frac{N}{8}+...+8+4+2=n-2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.217331em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">2</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1.217331em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">4</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:1.217331em;vertical-align:-0.345em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.872331em;"><span style="top:-2.6550000000000002em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">8</span></span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.394em;"><span class="pstrut" style="height:3em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.345em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.66666em;vertical-align:-0.08333em;"></span><span class="mord">.</span><span class="mord">.</span><span class="mord">.</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">8</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.72777em;vertical-align:-0.08333em;"></span><span class="mord">4</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">2</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2777777777777778em;"></span></span><span class="base"><span class="strut" style="height:0.66666em;vertical-align:-0.08333em;"></span><span class="mord mathnormal">n</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222222222222222em;"></span></span><span class="base"><span class="strut" style="height:0.64444em;vertical-align:0em;"></span><span class="mord">2</span></span></span></span>，所以我们可以得知skiplist的空间复杂度就是O(N)，也就是说如果将包含<strong>N</strong>个节点的单向链表构造成<code>skiplist</code>，还需要额外再使用接近N个节点的存储空间。</p><h2 id="%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1" tabindex="-1">结构设计</h2><p>在源码<a href="https://github.com/redis/redis/blob/6.0/src/server.h#L930" target="_blank">server.h第930行</a>可以看到定义了一个叫<code>zskiplist</code>的结构：</p><pre><code class="language-c">typedef struct zskiplist {    struct zskiplistNode *header, *tail;    unsigned long length;    int level;} zskiplist;</code></pre><ol><li><code>*header</code>：指向头节点的指针，可以通过这个指针在O(1)的时间复杂度内定位到头节点。</li><li><code>*tail</code>：指向尾节点的指针，可以通过这个指针在O(1)的时间复杂度内定位到头节点。</li><li><code>length</code>：记录了<code>skiplist</code>的长度，也就是<code>skiplist</code>包含的节点数量（不包括头节点），可以在O(1)的时间复杂度内返回<code>skiplist</code>长度。</li><li><code>level</code>：记录了<code>skiplist</code>的最大层数，可以在O(1)的时间复杂度内最大层数。</li></ol><h3 id="%E8%8A%82%E7%82%B9%E7%9A%84%E7%BB%93%E6%9E%84" tabindex="-1">节点的结构</h3><p>而<code>skiplist</code>的节点的结构则在源码<a href="https://github.com/redis/redis/blob/6.0/src/server.h#L920" target="_blank">server.h第920行</a>：</p><pre><code class="language-c">typedef struct zskiplistNode {    /* 成员对象 */    sds ele;    /* 分值 作为索引 */    double score;    /*后退指针，用于从后往前遍历使用 */    struct zskiplistNode *backward;    /* 节点的层结构 数组 */    struct zskiplistLevel {        /* 前指针 */        struct zskiplistNode *forward;        /* 跨度，用来确定本节点再链表中的排位  zrank */        unsigned long span;    } level[];} zskiplistNode;</code></pre><ol><li><p><code>ele</code>：每个节点保存的成员对象，在同一个<code>skiplist</code>中，每个节点保存的成员对象必须是唯一的，但是多个节点保存的<code>score</code>（分值）却可以是相同的。</p><blockquote><p><code>score</code>（分值）相同的节点将按照成员对象在字典排序中的大小来进行排序，成员对象较小的节点会排在前面，而成员对象较大的节点则会排在后面。</p></blockquote></li><li><p><code>score</code>：在<code>skiplist</code>中，每个节点按各自所保存的分值从小到大排列。</p></li><li><p><code>*backward</code>：指向位于当前层的前一个节点，在从后往前遍历时使用。与前进指针所不同的是每个节点只有一个后退指针，因此每次只能后退一个节点。</p></li><li><p><code>zskiplistLevel</code>：节点的层结构，每个层结构都有两个属性分别是<code>*forward</code>和<code>span</code>（前进指针和跨度）：</p><ol><li><code>*forward</code>：用来访问位于表尾方向的其他节点</li><li><code>span</code>：记录了<code>*forward</code>（前进指针）所指向节点和当前节点的距离（也就是跨度越大，距离越远）。</li></ol></li></ol><h3 id="%E7%BB%93%E6%9E%84%E5%9B%BE" tabindex="-1">结构图</h3><p>那么在Redis中，<code>skiplist</code>的结构就是这样的：</p><p><img src="/upload/2023/04/zskiplist%E7%BB%93%E6%9E%84.png" alt="zskiplist结构" /></p>]]>
                    </description>
                    <pubDate>Wed, 15 Mar 2023 20:30:36 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Redis数据结构-IntSet]]>
                    </title>
                    <link>https://zygzyg.com/archives/05d7b23a1de74880a68d7525f9f5a9d1</link>
                    <description>
                            <![CDATA[<h1 id="redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-intset" tabindex="-1">Redis数据结构-IntSet</h1><blockquote><p>在<a href="/archives/656e671f7923431489632abbab88acc1">《Reids数据结构》</a>这篇文章中，我们说到了<strong>Redis</strong>中的<code>Set</code>数据类型底层是使用的<code>intset</code>或者<code>hashtable</code>数据结构，本文简单说下<code>intset</code>，关于<code>hashtable</code>可以看看这篇文章：<a href="/archives/7ef23e66bf0a4b6a9db2f0a9c7626319">《Redis数据结构-HashTable》</a>。</p></blockquote><h2 id="redis%E5%9C%A8%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E4%BD%BF%E7%94%A8intset%E5%91%A2%EF%BC%9F" tabindex="-1">Redis在什么时候使用intset呢？</h2><p>简单说如果<code>Set</code>中的元素都是整数类型且元素个数小于等于配置项<code>set-max-intset-entries</code>，那就用<code>intset</code>，反之<code>hashtable</code>。</p><p>下面我们将<code>set-max-intset-entries</code>修改为<strong>3</strong>方便测试：</p><pre><code class="language-shell">127.0.0.1:6379&gt; config set set-max-intset-entries 3OK127.0.0.1:6379&gt; SADD k1 1 2 3(integer) 3127.0.0.1:6379&gt; OBJECT ENCODING k1&quot;intset&quot;127.0.0.1:6379&gt; SADD intset 4(integer) 1127.0.0.1:6379&gt; OBJECT ENCODING intset&quot;hashtable&quot;</code></pre><p>可以看到当<code>k1</code>的元素个数超过3之后，编码方式从<code>intset</code>变为了<code>hashtable</code>。</p><p>同理集合中包含非整数元素也会使用<code>hashtable</code>：</p><pre><code class="language-shell">127.0.0.1:6379&gt; config set set-max-intset-entries 3OK127.0.0.1:6379&gt; SADD k2 1 2 a(integer) 3127.0.0.1:6379&gt; OBJECT ENCODING k2&quot;hashtable&quot;</code></pre><h2 id="%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1" tabindex="-1">结构设计</h2><p>在源码<a href="https://github.com/redis/redis/blob/6.0/src/intset.h#L35" target="_blank">intset.h第35行</a>可以看到<code>intset</code>的结构是这样的：</p><pre><code class="language-c">typedef struct intset {    uint32_t encoding;    uint32_t length;    int8_t contents[];} intset;</code></pre><ol><li><code>encoding</code>：编码方式，共支持三种范围：<ol><li><code>INTSET_ENC_INT16</code>：占用2个字节，存储范围为<code>[-2^16^,2^16^-1]</code>。</li><li><code>INTSET_ENC_INT32</code>：占用4个字节，存储范围为<code>[-2^32^,2^32^-1]</code>。</li><li><code>INTSET_ENC_INT64</code>：占用8个字节，存储范围为<code>[-2^64^,2^64^-1]</code>。</li></ol></li><li><code>length</code>：存储元素个数。</li><li><code>contents[]</code>：指向实际存储数值的连续内存区域（也就是一个数组），<code>intset</code>的每个元素都是<code>contents[]</code>数组的一个数组项，每个项在数组中升序排列，且数组中不包含任何重复项。（虽然这里将<code>contents[]</code>属性声明为<code>int8_t</code>类型的数组，但实际上<code>contents[]</code>数组并不保存任何<code>int8_t</code>类型的值，<code>contents[]</code>数组的真正类型取决于<code>encoding</code>属性的值）。</li></ol><h2 id="%E5%B8%B8%E7%94%A8%E6%93%8D%E4%BD%9C" tabindex="-1">常用操作</h2><h3 id="intset%E7%9A%84%E5%8D%87%E7%BA%A7%EF%BC%88%E6%89%A9%E5%AE%B9%EF%BC%89" tabindex="-1">intset的升级（扩容）</h3><blockquote><p>前面说到了<code>contents[]</code>的元素类型是由<code>encoding</code>来决定的，那么假设当前<code>contents[]</code>里的元素类型是<code>int32</code>（<code>INTSET_ENC_INT32</code>），然后插入一个新的数据，但是类型是<code>int64</code>（<code>INTSET_ENC_INT64</code>），会发生什么呢？</p></blockquote><p>在源码<a href="https://github.com/redis/redis/blob/6.0/src/intset.c#L159" target="_blank">intset.c第159行</a>有个函数<code>intsetUpgradeAndAdd</code>，这个函数就是用来执行intset的升级的：</p><pre><code class="language-c">/* 升级intset并插入给定整数。 */static intset *intsetUpgradeAndAdd(intset *is, int64_t value) {    /* 当前的编码方式 */    uint8_t curenc = intrev32ifbe(is-&gt;encoding);    /* 使用要添加的新值获取新的编码方式 */    uint8_t newenc = _intsetValueEncoding(value);    int length = intrev32ifbe(is-&gt;length);    /* 确定数据添加的位置 */    int prepend = value &lt; 0 ? 1 : 0;    /* 首先设置新的类型并调整大小 */    is-&gt;encoding = intrev32ifbe(newenc);    is = intsetResize(is,intrev32ifbe(is-&gt;length)+1);    /* 从后往前升级，这样就不会覆盖值。比如原本集合有1、2、3、4、5、6INTSET_ENC_INT16，新加入的65538则需要用INTSET_ENC_INT32。      * 注意：“prepend”变量用于确保intset的开头或结尾有一个空的位置。 */    while(length--)        _intsetSet(is,length+prepend,_intsetGetEncoded(is,length,curenc));    /* 将待添加的value添加到首部或者尾部, 因为是扩容所以value是大于原有最大值或小于最小值。 */    if (prepend)        _intsetSet(is,0,value);    else        _intsetSet(is,intrev32ifbe(is-&gt;length),value);    /* 长度加1*/    is-&gt;length = intrev32ifbe(intrev32ifbe(is-&gt;length)+1);    return is;}</code></pre><p>如果在一个<code>int32</code>类型的<code>intset</code>中新增一个<code>int64</code>类型的值，那么整个<code>intset</code>里面的元素类型都会变成<code>int64</code>，这个过程主要分为三步：</p><ol><li>根据新增的元素的类型，扩展<code>contents[]</code>的空间大小，并为新元素分配空间。</li><li>将<code>contents[]</code>当前的所有元素，都转换为和新增元素相同的类型，然后将类型转换后的元素放置到正确的位上，且在放置的过程中还要保证原本数组的有序性。</li><li>最后修改<code>encoding</code>以及<code>length</code>长度加1。</li></ol><blockquote><p>注意：<code>intset</code>升级后不会再次降级，这里主要是考虑到资源消耗问题，没有必要在降级。</p></blockquote><h3 id="%E6%B7%BB%E5%8A%A0" tabindex="-1">添加</h3><p><code>intset</code>的添加元素操作的源码在<a href="https://github.com/redis/redis/blob/6.0/src/intset.c#L206" target="_blank">intset.c第206行</a>的<code>*intsetAdd</code>函数：</p><pre><code class="language-C">/* 在 intset 中插入一个整数 */intset *intsetAdd(intset *is, int64_t value, uint8_t *success) {    /*获取合适的编码方式*/    uint8_t valenc = _intsetValueEncoding(value);    uint32_t pos;    if (success) *success = 1;    /* 如果需要升级编码，则进行升级。     * 如果我们需要升级，我们知道这个值应该被追加（如果 &gt; 0）或预置（如果 &lt; 0），      * 因为它在现有值的范围之外。 */    if (valenc &gt; intrev32ifbe(is-&gt;encoding)) {        /* 这里执行intset的升级以及添加 */        return intsetUpgradeAndAdd(is,value);    } else {        /* 如果集合中已经存在值，则中止。使用了二分查询。         * 这个调用将使用正确的位置填充“pos”，以便在找不到值时插入值。*/        if (intsetSearch(is,value,&amp;pos)) {            if (success) *success = 0;            return is;        }        /*调整集合大小*/        is = intsetResize(is,intrev32ifbe(is-&gt;length)+1);        if (pos &lt; intrev32ifbe(is-&gt;length)) intsetMoveTail(is,pos,pos+1);    }    /* 真正执行写入集合的函数 */    _intsetSet(is,pos,value);    /* 长度加1*/    is-&gt;length = intrev32ifbe(intrev32ifbe(is-&gt;length)+1);    return is;}</code></pre><h3 id="%E5%88%A0%E9%99%A4" tabindex="-1">删除</h3><p>删除操作的源码在<a href="https://github.com/redis/redis/blob/6.0/src/intset.c#L236" target="_blank">intset.c第236行</a>的<code>intsetRemove</code>函数中：</p><pre><code class="language-c">/* 从intset中删除整数 */intset *intsetRemove(intset *is, int64_t value, int *success) {    uint8_t valenc = _intsetValueEncoding(value);    uint32_t pos;    if (success) *success = 0;    /* 当给定值处于当前集合的编码范围且存在集合当中 */    if (valenc &lt;= intrev32ifbe(is-&gt;encoding) &amp;&amp; intsetSearch(is,value,&amp;pos)) {        uint32_t len = intrev32ifbe(is-&gt;length);        /* 可以删除 */        if (success) *success = 1;        /* 用尾部覆盖值并更新长度 */        if (pos &lt; (len-1)) intsetMoveTail(is,pos+1,pos);        is = intsetResize(is,len-1);        /*长度减1*/        is-&gt;length = intrev32ifbe(len-1);    }    return is;}</code></pre><h3 id="%E6%9F%A5%E6%89%BE" tabindex="-1">查找</h3><p>一般查找操作（比如返回一个元素）、判断是否存在、查找位置的操作都是调用<a href="https://github.com/redis/redis/blob/6.0/src/intset.c#L56" target="_blank">intset.c第56行</a>的函数<code>_intsetGetEncoded</code>：</p><pre><code class="language-c">/* 返回给定编码的位置pos的值 */static int64_t _intsetGetEncoded(intset *is, int pos, uint8_t enc) {    int64_t v64;    int32_t v32;    int16_t v16;    if (enc == INTSET_ENC_INT64) {        memcpy(&amp;v64,((int64_t*)is-&gt;contents)+pos,sizeof(v64));        memrev64ifbe(&amp;v64);        return v64;    } else if (enc == INTSET_ENC_INT32) {        memcpy(&amp;v32,((int32_t*)is-&gt;contents)+pos,sizeof(v32));        memrev32ifbe(&amp;v32);        return v32;    } else {        memcpy(&amp;v16,((int16_t*)is-&gt;contents)+pos,sizeof(v16));        memrev16ifbe(&amp;v16);        return v16;    }}</code></pre><h3 id="%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AAintset" tabindex="-1">创建一个intset</h3><p>要创建一个<code>intset</code>，调用的是<a href="https://github.com/redis/redis/blob/6.0/src/intset.c#L98" target="_blank">intset.c第98行</a>中的<code>*intsetNew</code>函数：</p><pre><code class="language-c">/* 创建一个空的intset */intset *intsetNew(void) {    /* 分配内存 */    intset *is = zmalloc(sizeof(intset));    /* 默认编码是INTSET_ENC_INT16 */    is-&gt;encoding = intrev32ifbe(INTSET_ENC_INT16);    /* 长度初始为0 */    is-&gt;length = 0;    return is;}</code></pre>]]>
                    </description>
                    <pubDate>Tue, 14 Mar 2023 23:13:49 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Redis数据结构-QuickList]]>
                    </title>
                    <link>https://zygzyg.com/archives/c59a970681a64c8780d7a1a7eca628d2</link>
                    <description>
                            <![CDATA[<h1 id="redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-quicklist" tabindex="-1">Redis数据结构-QuickList</h1><blockquote><p>在<a href="/archives/656e671f7923431489632abbab88acc1">《Reids数据结构》</a>这篇文章中，我们说到了Redis的<code>List</code>数据类型底层的数据结构是使用的<code>quicklist</code>，本文简单说下<code>quicklist</code>。</p></blockquote><p>Redis在版本7.0的时候已经将<code>quicklist</code>的节点从<code>ziplist</code>换成了<code>listpack</code>，关于<code>ziplist</code>以及<code>listpack</code>可以看看这篇文章：<a href="/archives/ec31e437eaab44d195729c2e8f548722">《Redis数据结构-ZipList与ListPack》</a>。本文主要以<code>quicklist</code>的节点是<code>ziplist</code>来说明。</p><p>首先<code>quicklist</code>是一个双向链表结构，像是<code>ziplist</code>以及<code>linkedlist</code>的集合体，每个节点就是一个个<code>ziplist</code>，然后每个节点使用双向指针连接起来。</p><p><img src="/upload/2023/04/QuickList%E7%BB%93%E6%9E%84.png" alt="QuickList结构" /></p><h2 id="%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1" tabindex="-1">结构设计</h2><p>在源码<a href="https://github.com/redis/redis/blob/6.0/src/quicklist.h#L105" target="_blank">quicklist.h第105行</a>可以看到<code>quicklist</code>的结构是这样定义的：</p><pre><code class="language-C">/* quicklist是一个 40字节的结构体（在 64 位系统上），用于描述 quicklist。 * &#39;count&#39; 是所有节点中的总entry数。 * &#39;len&#39; 是quicklist节点的数目。 * &#39;compress&#39; 是一个数字，表示不压缩的 quicklist 节点数（如果压缩功能被禁用，则为 0）。 * &#39;fill&#39; 是用户请求（或默认）的填充因子。 * &#39;bookmarks&#39; 是一个可选特性，用于重新分配此结构体时使用，以避免不使用时占用内存。*/typedef struct quicklist {    quicklistNode *head;    quicklistNode *tail;    unsigned long count;        /* 所有 ziplist 中的entry数 */    unsigned long len;          /* quicklistNode 的数量 */    int fill : QL_FILL_BITS;              /* 每个节点的填充因子 */    unsigned int compress : QL_COMP_BITS; /* 深度不压缩的末尾节点；0=禁用 */    unsigned int bookmark_count: QL_BM_BITS;    quicklistBookmark bookmarks[];} quicklist;</code></pre><ul><li><code>*head</code>：表示指向头节点的指针。</li><li><code>*tail</code>：表示指向尾节点的指针。</li><li><code>count</code>： 所有<code>ziplist</code>中的<code>entry</code>数量总和。</li><li><code>len</code>：<code>quicklist</code>中<code>quicklistNode</code>的个数。</li><li><code>fill</code>：每个节点的填充因子，存放<code>list-max-ziplist-size</code>配置项的值。</li><li><code>compress</code>： 每个节点的压缩深度设置，存放<code>list-compress-depth</code>配置项的值。</li><li><code>bookmarks</code>：是一个可选特性，用于重新分配此结构体时使用，以避免不使用时占用内存。</li></ul><h3 id="quicklistnode%E7%9A%84%E7%BB%93%E6%9E%84" tabindex="-1">quicklistNode的结构</h3><p><code>quicklistNode</code>的结构同样在源码<a href="https://github.com/redis/redis/blob/6.0/src/quicklist.h#L46" target="_blank">quicklist.h</a>中：</p><pre><code class="language-c">/* 我们使用bit filed（位域）将quicklistNode限制在32字节。 * count：16 位，最大值为 65536（max zl bytes 为 65k，因此实际最大值 &lt; 32k）。 * encoding：2 位，RAW=1、LZF=2。 * container：2 位，NONE=1、ZIPLIST=2。 * recompress：1 位，bool，如果节点临时解压缩以供使用，则为 true。 * attempted_compress：1 位，boolean，用于测试时验证。 * extra：10 位，用于未来的自由使用；填充剩余的 32 位 */typedef struct quicklistNode {    struct quicklistNode *prev;    struct quicklistNode *next;    unsigned char *zl;    unsigned int sz;             /* ziplist 大小（以字节为单位） */    unsigned int count : 16;     /* ziplist 中项目的计数 */    unsigned int encoding : 2;   /* RAW==1 或 LZF==2 */    unsigned int container : 2;  /* NONE==1 或 ZIPLIST==2 */    unsigned int recompress : 1; /* 是否对节点进行了临时解压缩 */    unsigned int attempted_compress : 1; /* 节点无法压缩；太小 */    unsigned int extra : 10; /* 更多位用于未来使用 */} quicklistNode</code></pre><ul><li><p><code>*prev</code>：表示指向前一个节点的指针。</p></li><li><p><code>*next</code>：表示指向后一个节点的指针。</p></li><li><p><code>*zl</code>：数据指针。如果当前节点的数据没有压缩，那么它指向一个<code>ziplist</code>结构；否则，它指向一个<code>quicklistLZF</code>结构。</p></li><li><p><code>sz</code>：表示<code>*zl</code>指向的<code>ziplist</code>大小（包括<code>zlbytes</code>, <code>zltail</code>, <code>zllen</code>, <code>zlend</code>和各个数据项），单位是字节，。</p><blockquote><p>注意：如果<code>ziplist</code>被压缩了，<code>sz</code>的值依然还是压缩之前的<code>ziplist</code>的大小。</p></blockquote></li><li><p><code>count</code>：表示<code>ziplist</code>中<code>entry</code>节点数量。只有<code>16bit</code>。</p></li><li><p><code>encoding</code>：表示<code>ziplist</code>是否被压缩以及使用的什么压缩算法，1代表没有压缩，2代表使用的LZF压缩算法压缩的。</p></li><li><p><code>container</code>：预留字段，原本是用来表示<code>quicklist</code>节点是直接存数据还是使用<code>ziplist</code>（或者其他数据结构），但是Redis6.0的实现里，这个值固定为2。</p></li><li><p><code>recompress</code>：表示是否对节点进行了临时解压缩，比如当我们使用类似<code>lindex</code>这样的命令查看了某一项本来压缩的数据时，需要把数据暂时解压，这个时候就设置<code>recompress=1</code>做一个标记，等有机会再把数据重新压缩。</p></li></ul><p>上面说到<code>*zl</code>还可能指向一个<code>quicklistLZF</code>结构，这个结构的源码在<a href="https://github.com/redis/redis/blob/6.0/src/quicklist.h#L64" target="_blank">quicklist.h第64行</a>：</p><pre><code class="language-c">/* quicklistLZF 是一个 4+N 字节的结构体，其中包含了 &#39;sz&#39; 和 &#39;compressed&#39; 两个字段。 * &#39;sz&#39; 表示 &#39;compressed&#39; 字段的字节长度。 * &#39;compressed&#39; 是一个 LZF 压缩过的数据，它的总长度为 &#39;sz&#39; 字节。 * 注意：quicklistNode-&gt;zl 压缩后的长度存储在 quicklistLZF-&gt;sz 中。 * 当 quicklistNode-&gt;zl 被压缩时，node-&gt;zl 指向 quicklistLZF */typedef struct quicklistLZF {    unsigned int sz; /* LZF 大小，以字节为单位 */    char compressed[];} quicklistLZF;</code></pre><ul><li><code>sz</code>：表示<code>compressed</code>字段的字节长度。</li><li><code>compressed</code>：是一个LZF压缩过的数据，它的总长度为<code>sz</code>个字节。</li></ul>]]>
                    </description>
                    <pubDate>Tue, 14 Mar 2023 19:13:14 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Redis数据结构-ZipList与ListPack]]>
                    </title>
                    <link>https://zygzyg.com/archives/ec31e437eaab44d195729c2e8f548722</link>
                    <description>
                            <![CDATA[<h1 id="redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-ziplist%E4%B8%8Elistpack" tabindex="-1">Redis数据结构-ZipList与ListPack</h1><blockquote><p>在<a href="/archives/656e671f7923431489632abbab88acc1">《Reids数据结构》</a>这篇文章中，说到了在Redis6中<code>List</code>、<code>Sorted Set</code>以及<code>Hash</code>底层都使用了<code>ziplist</code>这一数据结构，并在Redis7后新增了一个数据结构<code>listpack</code>替换了<code>ziplist</code>，这里再简单说下<code>ziplist</code>和<code>listpack</code>。</p></blockquote><h2 id="redis%E5%9C%A8%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E4%BD%BF%E7%94%A8ziplist%E6%88%96%E8%80%85listpack" tabindex="-1">Redis在什么时候使用ziplist或者listpack</h2><p>这里我们使用<code>Hash</code>这个数据类型来举例，首先在Redis6的配置文件中，我们可以看到两个配置项分别是：</p><pre><code class="language-conf"># 当哈希表的条目数量较少且最大条目不超过一个给定的阈值时，# 哈希表会使用一种内存高效的数据结构进行编码。可以使用以下指令配置这些阈值。hash-max-ziplist-entries 512hash-max-ziplist-value 64</code></pre><ul><li><code>hash-max-ziplist-entries</code>：使用<code>ziplist</code>保存时哈希集合中的最大元素个数。</li><li><code>hash-max-ziplist-value</code>：使用<code>ziplist</code>保存时哈希集合中单个元素的最大长度。</li></ul><p>简单说这两个配置就是表示Hash类型键的字段个数小于<code>hash-max-ziplist-entries</code>并且每个字段名和字段值的长度小于<code>hash-max-ziplist-value</code>时，Redis才会使用<code>OBJ_ENCODING_ZIPLIST</code>来存储，上面的两个条件任意一项不满足都会转变为使用<code>OBJ_ENCODING_HT</code>。</p><p>下面我们在<strong>Redis6</strong>中测试一下：</p><ol><li><p>首先可以看到<code>hash-max-ziplist-entries</code>和<code>hash-max-ziplist-value</code>默认值分别是512和64，这里为了方便测试，我们把这两个配置改小，改成2和4，分别表示这个哈希集合中最大元素是2，单个元素的最大长度是4。</p><pre><code class="language-shell">127.0.0.1:6379&gt; CONFIG GET hash-max-ziplist-entries1) &quot;hash-max-ziplist-entries&quot;2) &quot;512&quot;127.0.0.1:6379&gt; CONFIG GET hash-max-ziplist-value1) &quot;hash-max-ziplist-value&quot;2) &quot;64&quot;127.0.0.1:6379&gt; CONFIG SET hash-max-ziplist-entries 2OK127.0.0.1:6379&gt; CONFIG SET hash-max-ziplist-value 4OK</code></pre></li><li><p>然后来验证<code>hash-max-ziplist-entries</code>（最大元素个数），这里创建一个key名为<code>user</code>，它有两个字段<code>name</code>和<code>age</code>，查看编码仍然还是<code>ziplist</code>。但是当我们再多加一个字段<code>gender</code>后，编码变成了<code>hashtable</code>。</p><pre><code class="language-shell">127.0.0.1:6379&gt; HSET user name jack age 18(integer) 2127.0.0.1:6379&gt; OBJECT ENCODING user&quot;ziplist&quot;127.0.0.1:6379&gt; HSET user name jack age 18 gender male(integer) 1127.0.0.1:6379&gt; OBJECT ENCODING user&quot;hashtable&quot;</code></pre></li><li><p>同理来验证<code>hash-max-ziplist-value</code>（单个元素最大长度），这里创建一个key名为<code>student</code>，它有一个字段<code>name</code>，值为<code>jack</code>长度为4，最后查看编码是<code>ziplist</code>。但是修改值为<code>jackson</code>后，长度变成了7，再次查看编码已经变成了<code>hashtable</code>。</p><pre><code class="language-shell">127.0.0.1:6379&gt; HSET student name jack(integer) 0127.0.0.1:6379&gt; OBJECT ENCODING student&quot;ziplist&quot;127.0.0.1:6379&gt; HSET student name jackson(integer) 0127.0.0.1:6379&gt; OBJECT ENCODING student&quot;hashtable&quot;</code></pre></li></ol><blockquote><p>注意：一旦<code>ziplist</code>转换成了<code>hashtable</code>，Hash类型编码就会一直使用<code>hashtable</code>了，不会再次转换回来。</p></blockquote><p>同理在Redis7中也有类似的配置项，只是换成了<code>listpack</code>：</p><pre><code class="language-conf"># 当哈希表的条目数量较少且最大条目不超过给定的阈值时，# 它们会使用一种内存高效的数据结构进行编码。可以使用以下指令配置这些阈值。hash-max-listpack-entries 512hash-max-listpack-value 64</code></pre><h2 id="ziplist" tabindex="-1">ziplist</h2><blockquote><p>上面说到<strong>Redis6</strong>的<strong>Hash</strong>类型在底层是用的<code>ziplist</code>这个数据结构，那这个<code>ziplist</code>是什么呢？</p></blockquote><p>在源码<a href="https://github.com/redis/redis/blob/6.0/src/ziplist.c" target="_blank">ziplist.c</a>的头部有着这么一段注释：</p><pre><code class="language-c">/* ziplist是一个经过特殊编码的双向链表，旨在提高内存效率。 * 存储字符串和整数值，其中整数被编码为实际整数而不是一系列字符。  * 允许在O(1)时间内在列表的任一侧进行push和pop操作。  * 但是由于每个操作都需要重新分配ziplist使用的内存，因此实际复杂性与ziplist使用的内存量有关。*/</code></pre><p>也就是说不同的数据类型在<code>ziplist</code>里由不同的编码方式（可以理解为压缩数据）。但是随着数据量的增加，查询时间复杂度也在增加，因为只有对头尾节点的操作是O(1)复杂度，其他节点都是O(N)。</p><h3 id="%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1" tabindex="-1">结构设计</h3><blockquote><p>通常说到链表，不管是单向或者双向链表，在节点中都是由前/后节点指针的，但是<code>ziplist</code>中的节点并没由指针，这是为什么呢？</p></blockquote><p>这主要是因为<code>ziplist</code>是<strong>Redis</strong>为了节约内存而开发的，它一个是<strong>由连续内存块组成的顺序型数据结构</strong>，也就是说它是存储在连续的内存上的。ziplist的整体结构如下：</p><pre><code class="language-">    4 bytes   4 bytes   2 bytes                                 1 byte# +---------+---------+---------+---------+---------+---------+---------+# | zlbytes | zltail  |  zllen  |  entry  | ......  |  entry  |  zlend  |# +---------+---------+---------+---------+---------+---------+---------+</code></pre><ul><li><code>zlbytes</code>：是一个<code>uint_32_t</code>（32位无符号整型），用来记录整个<code>ziplist</code>占用的内存字节数，计算<code>zlend</code>的位置或者对<code>ziplist</code>内存重新分配时会用到。有个好处就是和之前说到的<strong>SDS</strong>里面保存一个<code>len</code>来记录字符串长度类似，即获取大小的时候不需要再次遍历计算了，直接获取<code>zlbytes</code>就行了。</li><li><code>zltail</code>：是一个<code>uint_32_t</code>（32位无符号整型），用来记录<code>ziplist</code>尾节点距离<code>ziplist</code>起始位置有多少字节，通过这个偏移量，就可以不用遍历整个<code>ziplist</code>也可以找到尾节点的位置。</li><li><code>zllen</code>：是一个<code>uint_16_t</code>（16位无符号整型），记录了<code>ziplist</code>中<code>entry</code>的数量，<code>uint_16_t</code>最大是2<sup>16</sup>，但是Redis是这么做的，当<code>entry</code>数量超过最大值后，<code>zllen</code>会被固定成2<sup>16</sup>-1，之后如果要获取<code>entry</code>数量那就需要遍历一遍了。</li><li><code>entry</code>：<code>ziplist</code>的每一个节点，真正用来存储数据的结构，长度由保存的内容来决定。</li><li><code>zlend</code>：是一个<code>uint_8_t</code>（8位无符号整型），固定为<code>0xFF</code>（十进制表示255），用来标记压缩列表的末端。</li></ul><h4 id="entry%E7%9A%84%E7%BB%93%E6%9E%84" tabindex="-1">entry的结构</h4><p>在源码<a href="https://github.com/redis/redis/blob/unstable/src/ziplist.c#L37" target="_blank">ziplist.c第37行</a>中有这么一段注释：</p><pre><code class="language-c">/* ZIPLIST ENTRIES * =============== * * 每个entry节点都包含一些元数据，这些元数据包含两个信息： * 1. 存储了前一个节点的长度，以便能够从后向前遍历列表。 * 2. 提供了entry节点的编码方式。可以用来表示节点类型（整数或者字符串），如果是字符串，还可以表示字符串的有效长度。 * 所以一个完整的&#96;entry&#96;节点的结构如下： * * +-----------+-----------+-----------+ * |  prevlen  |  encoding | entrydata | * +-----------+-----------+-----------+ * * 有时候，编码方式encoding代表了entry节点本身（比如对于比较小的整数），在这种情况下，entrydata部分是不存在的： * * +-----------+-----------+ * |  prevlen  |  encoding | * +-----------+-----------+ * */</code></pre><p>所以<code>entry</code>结构分两种情况：</p><ol><li><p><strong>第一种情况</strong>：</p><ol><li><code>prevlen</code>：记录前一个节点的长度。</li><li><code>encoding</code>：记录当前节点实际数据的类型和长度。</li><li><code>entrydata</code>：记录当前节点实际的数据。</li></ol></li><li><p><strong>第二种情况</strong>：</p><ol><li><p><code>prevlen</code>：记录前一个节点的长度。</p></li><li><p><code>encoding</code>：在<code>entry</code>节点中存储的是比较小的int类型时，<code>encoding</code>和<code>entrydata</code>会合并在<code>encoding</code>中表示，此时没有<code>entrydata</code>字段。</p></li></ol></li></ol><h5 id="prelen" tabindex="-1">prelen</h5><p><code>prelen</code>的编码分为两种情况：</p><ol><li>当前一个<code>entry</code>节点长度小于254的时候，<code>prelen</code>的长度为1个字节，值就是前一个<code>entry</code>节点的长度。</li><li>当前一个<code>entry</code>节点长度大于等于254的时候，<code>prelen</code>用5个字节来表示，其中第一个字节固定为254（FE）作为标识，剩余的四个字节则用来表示前一个<code>entry</code>的实际大小。</li></ol><p>所以<code>prelen</code>的编码可以是：</p><pre><code class="language-c">/* * &lt;prevlen from 0 to 253&gt; &lt;encoding&gt; &lt;entry&gt; */</code></pre><p>或者前一个节点长度大于等于254的时候：</p><pre><code class="language-c">/* * 0xFE &lt;4 bytes unsigned little endian prevlen&gt; &lt;encoding&gt; &lt;entry&gt; */</code></pre><h5 id="encoding" tabindex="-1">encoding</h5><p><code>encoding</code>的编码取决于保存的内容，前两位表示类型，比如前面两位都是1，即<code>11</code>，则表示整数，除此之外的其他都表示字符串。</p><ol><li>当保存的是字符串的时候，有3种编码方式：<ul><li><code>|00pppppp|</code> ：此时长度是1字节，后6位用来存储字符串长度。所以长度不能超过63。</li><li><code>|01pppppp|qqqqqqqq|</code>：此时长度是2字节，后14位用来存储字符串长度，长度不能超过16383。</li><li><code>|10000000|qqqqqqqq|rrrrrrrr|ssssssss|ttttttt|</code>：此时长度是5字节，后面的32位用来存储字符串长度，长度不能超过2<sup>32</sup> - 1。</li></ul></li><li>当保存的是整数的时候，有6种编码方式：<ul><li><code>|11000000|</code>：3个字节，后2个字节表示一个<code>int16_t</code>。</li><li><code>|11010000|</code>：5个字节，后4个字节表示一个<code>int32_t</code>。</li><li><code>|11100000|</code>：9个字节，后8个字节表示一个<code>int64_t</code>。</li><li><code>|11110000|</code>：4个字节，后3个字节表示一个24 bit带符号。</li><li><code>|11111110|</code>：2个字节，后1个字节表示一个8  bit带符号。</li><li><code>|1111xxxx|</code>：1个字节，[0,12]的无符号整数，编码后的值实际上是1到13，因为0000和1111不能用，所以要从编码后的4位值中减去1才能得到正确的值。</li></ul></li></ol><blockquote><p><code>|11111111|</code>则表示<code>zlend</code>，用来表示<code>ziplist</code>的结尾。</p></blockquote><h4 id="zlentry%E6%BA%90%E7%A0%81" tabindex="-1">zlentry源码</h4><p>在源码<a href="https://github.com/redis/redis/blob/unstable/src/ziplist.c#L284" target="_blank">ziplist.c第284</a>行可以看到<code>zlentry</code>的结构如下：</p><pre><code class="language-c">/* 我们使用这个函数来接收关于 ziplist 条目的信息。 * 注意这不是数据实际上的编码方式，只是我们得到的填充信息，以便更轻松地操作。*/typedef struct zlentry {    unsigned int prevrawlensize; /* 编码前一个节点的长度需要的字节 */    unsigned int prevrawlen;     /* 前一个节点占用的长度 */    unsigned int lensize;        /* 用于编码此节点类型/长度的字节数。 例如，字符串使用 1、2 或 5 个字节的头。整数总是使用单个字节。*/    unsigned int len;            /* 表示实际条目所需的字节数。对于字符串，这只是字符串长度，而对于整数，它取决于数字范围，可能是 1、2、3、4、8 或 0（对于 4 位即时整数） */    unsigned int headersize;     /* 当前节点的头部大小（prevrawlensize + lensize）即非数据域的大小。 */    unsigned char encoding;      /* 根据条目编码设置为 ZIP_STR_* 或 ZIP_INT_*。但是对于 4 位即时整数，它可以假定一个范围的值，并且必须进行范围检查。 */    unsigned char *p;            /* 指向条目的起始点，即指向前一个条目长度字段。 */} zlentry;</code></pre><h3 id="%E8%BF%9E%E9%94%81%E6%9B%B4%E6%96%B0" tabindex="-1">连锁更新</h3><blockquote><p>连锁更新是什么呢？首先连锁更新是<code>ziplist</code>一个比较大的缺点，Redis7.0将<code>ziplist</code>更新位<code>listpack</code>的重要原因就是这个。</p></blockquote><p>上面我们说到了<code>ziplist</code>的每个操作（新增或者修改）都需要重新分配<code>ziplist</code>的内存空间。试想一下每当我们新增一个节点，都可能导致<code>prevlen</code>发生变化，从而引起连锁更新问题，导致每个元素的空间都需要重新分配，这会造成性能下降的问题。</p><p>这里假如我们有一个<code>ziplist</code>，现在有<strong>A</strong>、<strong>B</strong>、<strong>C</strong>、<strong>D</strong>四个节点，这四个的长度都刚好是253字节，按照前面我们说的<code>prelen</code>的编码情况，它们的<code>prelen</code>肯定都是1字节。然后我们要新增一个长度大于等于254字节的节点<strong>E</strong>（加到A前面），那么节点<strong>A</strong>的<code>prelen</code>则需要变为5个字节，而由于<strong>A</strong>的长度已经是253字节了，加上这个变化后的<code>prelen</code>，长度已经超过254，所以后面的节点<strong>B</strong>也需要跟着变化，依次类推。这便是连锁更新问题，这里举例只用了4个节点，实际应用中如果节点数量很多，那么则会造成很多性能消耗。</p><pre><code class="language-">#                       +-----+# +-----+               |  E  | 254 bytes# |  A  | 253 bytes     +-----+# +-----+               |  A  | 原本刚好253字节，但是前一个节点的长度超过254，prelen变成5个字节，加上本身的253字节，自己也超过了254字节# |  B  | 253 bytes     +-----+# +-----+==============&gt;|  B  | 同理节点A超过了254字节，B的prelen也变成5字节，自身长度也超过254字节# |  C  | 253 bytes     +-----+   # +-----+               |  C  | 同理节点B超过了254字节，C的prelen也变成5字节，自身长度也超过254字节# |  D  | 253 bytes     +-----+# +-----+               |  D  | 同理节点C超过了254字节，D的prelen也变成5字节，自身长度也超过254字节#                       +-----+</code></pre><h3 id="%E7%9B%B8%E5%85%B3%E9%97%AE%E9%A2%98" tabindex="-1">相关问题</h3><h4 id="%E4%B8%BA%E4%BB%80%E4%B9%88ziplist%E7%89%B9%E5%88%AB%E8%8A%82%E7%9C%81%E5%86%85%E5%AD%98" tabindex="-1">为什么ziplist特别节省内存</h4><p>首先说<code>ziplist</code>节省内存是对于普通的<code>List</code>数组，数组的每个元素占用内存都是一样的而且是取决于最大的那个元素。所以<code>ziplist</code>增加了<code>encoding</code>字段，用来针对不同的<code>encoding</code>来细化存储大小。最后<code>ziplist</code>使用<code>prelen</code>这个字段记录上一个节点的长度，解决了遍历的问题。</p><h4 id="%E4%B8%BA%E4%BB%80%E4%B9%88ziplist%E8%A6%81%E8%BF%99%E6%A0%B7%E9%87%8D%E6%96%B0%E8%AE%BE%E8%AE%A1" tabindex="-1">为什么ziplist要这样重新设计</h4><ol><li><p>首先是普通的单向链表或者双向链表的节点都会有一个或两个指针用来指向前后节点，在存储数据特别小的时候，可能数据本身还没有指针占用的空间大。然而从上面我们得知<code>ziplist</code>并没有维护指针，而是保存了前一个节点的长度和当前节点的长度，虽然牺牲了读取性能但是获得了更多的存储空间，也就是常说的<mark>时间换空间</mark>。</p></li><li><p>链表在内存中一般不是连续的内存空间，遍历相对来说比较慢。一般数组遍历是根据数组里面存储的数据类型来找到下一个元素的（比如<code>int</code>类型的数组访问下一个元素每次只需要移动一个<code>sizeof(int)</code>就可以），而<code>ziplist</code>的每个节点的长度是可以不一样的，对于不同长度的节点又不可能直接<code>sizeof(entry)</code>，所以<code>ziplist</code>将一些必要的偏移量信息记录在了每一个节点里面。</p><blockquote><p><code>sizeof()</code>实际上就是获取数据在内存中所占用的空间，单位是字节。</p></blockquote></li><li><p><code>ziplist</code>还有<code>zlbytes</code>以及<code>zllen</code>，前面也说到了分别用来记录<code>ziplist</code>的大小以及节点数量，那么有个好处就是（和SDS里面保存一个<code>len</code>来记录字符串长度类似）：获取<code>ziplist</code>大小以及节点数量的时候不需要再次遍历计算了。</p><blockquote><p>注意：节点数量超过<code>uint_16_max</code>（2<sup>16</sup>）个后还是需要重新遍历。</p></blockquote></li></ol><h2 id="listpack" tabindex="-1">listpack</h2><blockquote><p>前面说到<code>ziplist</code>，但是<code>ziplist</code>有个致命的缺点就是新增和修改元素可能导致连锁更新，这也是Redis7.0新增<code>listpack</code>替换<code>ziplist</code>的原因。那么<code>listpack</code>是什么呢？</p></blockquote><p>Redis作者在<a href="https://github.com/antirez/listpack/blob/master/listpack.md#general-structure" target="_blank">github</a>上是这样介绍的：</p><p>一个<code>listpack</code>被编码成一个单一线性块的内存。它有一个固定长度的6个字节的头部（与<code>ziplist</code>的10个字节不同，因为我们不再需要一个指向最后一个元素开头的指针）。头部后面直接就是 <code>listpack</code>的元素。理论上，这个数据结构不需要任何终止符，但是出于某些考虑，提供了一个特殊的条目，用于标记<code>listpack</code>的结尾，形式为单个字节，其值为<code>FF</code>（255）。终止符的主要优点是能够在不持有（并在每次迭代时比较）<code>listpack</code>结尾地址的情况下扫描<code>listpack</code>，并且容易识别<code>listpack</code>是否格式正确或缩短。</p><h3 id="%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1-1" tabindex="-1">结构设计</h3><p><code>listpack</code>结构如下：</p><pre><code class="language-">      4 bytes        2 bytes                                                     1 byte# +--------------+--------------+--------------+--------------+--------------+-----------------+# |   totbytes   | num-elements |     entry    |    ......    |    entry     |listpack-end-byte|# +--------------+--------------+--------------+--------------+--------------+-----------------+</code></pre><ul><li><code>tot-bytes</code>：是一个<code>uint_32_t</code>（32位无符号整型），表示<code>listpack</code>的总字节数（包括头部本身和终止符），也就是说<code>listpack</code>最多占用<code>4294967295</code>bytes。这基本上是保存<code>listpack</code>所需的总分配大小，当需要时，允许跳到结尾以从最后一个元素向前扫描<code>listpack</code>。</li><li><code>num-elements</code>：是一个<code>uint_16_t</code>（16位无符号整型），表示<code>listpack</code>包含的元素总数。 和<code>ziplist</code>的<code>zllen</code>同理，最多记录<code>uint_16_max</code>（2<sup>16</sup>）个元素，超过则固定为2<sup>16</sup>-1，此时要获取数量则需要遍历<code>listpack</code>。</li></ul><h4 id="entry%E7%9A%84%E7%BB%93%E6%9E%84-1" tabindex="-1">entry的结构</h4><p>Redis作者在<a href="https://github.com/antirez/listpack/blob/master/listpack.md#elements-representation" target="_blank">github</a>上是这样介绍的：</p><p><code>listpack</code>中的每个元素都具有以下结构：</p><pre><code class="language-markdown">+----------+----------+----------+| encoding | element  |  element ||  type    |  data    |  tot-len |+----------+----------+----------+|                                |+--------------------------------+      (This is an element)</code></pre><p>元素的类型（<code>encoding-type</code>）和元素的总长度（<code>element-tot-len</code>）始终存在。</p><ul><li><code>encoding-type</code>：当前元素的编码类型，会对不同长度的整数和字符串进行编码。</li><li><code>element-data</code>：实际存储的元素数据。</li><li><code>element-tot-len</code>：元素的总长度（<code>encoding-type</code>+<code>element-data</code>的总长度），代表当前节点的回朔起始地址长度的偏移量。</li></ul><p>在源码<a href="https://github.com/redis/redis/blob/7.0/src/listpack.h#L49" target="_blank">listpack.h第49行</a>是这样定义的：</p><pre><code class="language-c">/* listpack中的每一个节点，不是String就是int. */typedef struct {    /* When string is used, it is provided with the length (slen). */    unsigned char *sval;    uint32_t slen;     /* 当使用integer时，“sval”为 NULL，lval 保存该值。*/    long long lval;} listpackEntry;</code></pre><p>对比<code>ziplist</code>，<code>listpack</code>中的每个节点记录的是当前节点的长度，而不是前一个节点的长度。</p><h2 id="ziplist%E5%92%8Clistpack%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">ziplist和listpack的区别</h2><ol><li><code>listpack</code>相对于<code>ziplist</code>，它的每个节点中不在包含前一个节点的长度，从而避免的连锁更新的问题。</li><li><code>listpack</code>相对于<code>ziplist</code>，没有了<code>zltail</code>（尾节点距离<code>ziplist</code>起始位置有多少字节），解决<code>ziplist</code>内存长度限制的问题。<mark>但一个<code>listpack</code>最大内存使用不能超过1GB</mark>。</li></ol><h2 id="%E5%8F%82%E8%80%83%E6%96%87%E7%AB%A0" tabindex="-1">参考文章</h2><ul><li><a href="https://pdai.tech/md/db/nosql-redis/db-redis-x-redis-ds.html" target="_blank">https://pdai.tech/md/db/nosql-redis/db-redis-x-redis-ds.html</a></li><li><a href="https://blog.csdn.net/m0_51504545/article/details/117391204" target="_blank">https://blog.csdn.net/m0_51504545/article/details/117391204</a></li></ul>]]>
                    </description>
                    <pubDate>Mon, 13 Mar 2023 20:29:58 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Redis数据结构-HashTable]]>
                    </title>
                    <link>https://zygzyg.com/archives/7ef23e66bf0a4b6a9db2f0a9c7626319</link>
                    <description>
                            <![CDATA[<h1 id="redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-hashtable" tabindex="-1">Redis数据结构-HashTable</h1><blockquote><p>在<a href="/archives/656e671f7923431489632abbab88acc1">《Reids数据结构》</a>这篇文章中，我们说到了Redis中的<code>Set</code>和<code>Hash</code>数据类型底层都使用到了<code>hashtable</code>这个数据结构以及Redis的<code>key-value</code>数据库有一个全局哈希表，这里简单说下关于<code>hashtable</code>的结构。</p></blockquote><h2 id="redis%E4%B8%AD%E7%9A%84hashtable" tabindex="-1">Redis中的hashtable</h2><p><code>hashtable</code>在Redis中被称为<code>dictionary</code>（字典），是一个<mark>数组+链表</mark>的结构，正是因为这样<code>hashtable</code>查询数据的时间复杂度是O(1)。简单说就是将key通过Hash函数的计算，就能定位数据在表中的位置，通过索引值就可以直接在数组中获取到数据。既然是哈希表那肯定是有哈希冲突的，在数据逐渐增加的情况下，哈希冲突的可能性也在增加。Redis采用了<mark>链式哈希</mark>来解决哈希冲突，在不扩容哈希表的前提下，将具有相同哈希值的数据串起来，形成链接起，以便这些数据在表中仍然可以被查询到。（其实就是<mark>数组+链表</mark>，有点类似Java中的HashMap）。</p><h3 id="%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1" tabindex="-1">结构设计</h3><blockquote><p>简单来说可以理解成Redis有<code>dictht</code>这么一个结构，这个结构就是一个数组，而数组中的元素则是<code>dictEntry</code>，每个<code>dictEntry</code>元素都有一个指针<code>*next</code>指向哈希冲突的下一个节点形成一个链表。</p></blockquote><pre><code class="language-"># dictht数组#   +-----------+#   | dictEntry |#   +-----------+         +-----------+         +-----------+#   | dictEntry |--------&gt;| dictEntry |--------&gt;|  ......   |#   +-----------+         +-----------+         +-----------+#   | dictEntry |#   +-----------+#   | dictEntry |#   +-----------+         +-----------+         +-----------+         +-----------+#   | dictEntry |--------&gt;| dictEntry |--------&gt;| dictEntry |--------&gt;|  ......   |#   +-----------+         +-----------+         +-----------+         +-----------+#   | dictEntry |#   +-----------+#   | dictEntry |#   +-----------+</code></pre><p>在<strong>Redis6.0</strong>的源码<a href="https://github.com/redis/redis/blob/6.0/src/dict.h#L72" target="_blank">dict.h第72行</a>中是这样定义<code>dictht</code>的:</p><pre><code class="language-c">/* 这是我们哈希表的结构。每个字典都有两个这样的结构，因为我们实现了增量式重新哈希，用于旧表和新表。*/typedef struct dictht {    /* 哈希表的数组 */    dictEntry **table;    /* 哈希表的大小 */    unsigned long size;    /* 用来计算索引值的哈希表大小掩码 */    unsigned long sizemask;    /* 已经使用了的节点数量 */    unsigned long used;} dictht;</code></pre><p>从源码<a href="https://github.com/redis/redis/blob/6.0/src/dict.h#L50" target="_blank">dict.h第50行</a>中可以看到这个<code>dictht</code>就是一个数组，里面的元素就是指向<code>dictEntry</code>的指针。<code>dictEntry</code>的结构如下：</p><pre><code class="language-c">typedef struct dictEntry {    /* 键值对中的key */    void *key;    /* 键值对中的value */    union {        void *val;        uint64_t u64;        int64_t s64;        double d;    } v;    /* 指向下一个节点 */    struct dictEntry *next;} dictEntry;</code></pre><p>可以看到<code>dictEntry</code>结构里面不仅仅有<strong>key</strong>和<strong>value</strong>的指针，还有一个指向下一个节点的指针<code>*next</code>，这就是上面说到的用来解决哈希冲突的链式哈希。如果计算出来哈希相同，那么直接在相同的<code>dictEntry</code>里将下一个节点的指针指向冲突的节点就行了。</p><p>但是随着数据量的增加，这个链表可能会越来越长，这就会导致查询时间也随着增加，因为链表的遍历时间复杂度是O(N)，那Redis是怎么解决这个问题的呢？在源码<a href="https://github.com/redis/redis/blob/6.0/src/dict.h#L79" target="_blank">dict.h第79行</a>中可以看到还有一个结构体<code>dict</code>，在这个结构里面定义了两个哈希表：</p><pre><code class="language-c">typedef struct dict {    dictType *type;    void *privdata;    /* 两个哈希表，交替使用 */    dictht ht[2];    /* 用来记录字典是否处于rehashing过程中（如果 rehashidx == -1 则表示没有进行 rehashing）*/    long rehashidx;     /* 用来记录当前正在运行的迭代器数量。 */    unsigned long iterators; } dict;</code></pre><h3 id="rehash" tabindex="-1">rehash</h3><blockquote><p>为什么Redis在<code>dict</code>结构要定义两个哈希表呢？</p></blockquote><p>从上面我们知道，随着数据量增加，Redis中的链式哈希中链表长度也会越来越长，这也使得遍历的时间增加，为了解决这个问题，Redis会进行<code>rehash</code>操作，可以理解为给哈希表扩容，这样来减少哈希冲突，也就变向的减少了链表长度。</p><p>Redis中<code>rehash</code>的过程可以分为三个步骤：</p><ol><li>首先给<code>ht[1]</code>分配空间，一般是当前哈希表<code>ht[0]</code>的两倍。</li><li>把<code>ht[0]</code>的数据迁移到<code>ht[1]</code>中。（Redis大部分操作是单线程的，这里Redis采用了渐进式<code>rehash</code>操作。）</li><li>迁移完成后，<code>ht[0]</code>的空间会被释放，并把<code>ht[1]</code>设置给<code>ht[0]</code>，然后在<code>ht[1]</code>创建一个空白的哈希表，为下次<code>rehash</code>做准备。</li></ol><h4 id="%E6%B8%90%E8%BF%9B%E5%BC%8Frehash" tabindex="-1">渐进式rehash</h4><p>上面Redis在<code>rehash</code>过程中的第2步存在一个问题，那就是Redis大部分操作还是单线程的，这个过程可能会阻塞其他操作，如果是在数据量很大的时候，那可能会花很长时间来完成。所以Redis采用了渐进式<code>rehash</code>操作，简单说就是在每次操作的时候顺便copy到新的哈希表，而且Redis本身也有定时任务来处理。</p><p>在这个过程中，Redis中实际同时存在着两个哈希表（<code>ht[0]</code>和<code>ht[1]</code>），所以在这期间对于哈希表的查询操作会在这两张表上进行。比如想要查询一个key，那么Redis会先在<code>ht[0]</code>上查询，如果没有才会到<code>ht[1]</code>上查询。但是这个时候新增的key，也会直接保存到新的哈希表<code>ht[1]</code>中去，而对于<code>ht[0]</code>，不会在添加任何地key上去，这样慢慢地<code>ht[0]</code>就会变成空的哈希表。</p><h4 id="rehash%E8%A7%A6%E5%8F%91%E6%9D%A1%E4%BB%B6" tabindex="-1">rehash触发条件</h4><blockquote><p>Redis什么时候才会进行<code>rehash</code>呢？</p></blockquote><h5 id="%E6%89%A9%E5%AE%B9" tabindex="-1">扩容</h5><p>在源码<a href="https://github.com/redis/redis/blob/6.0/src/dict.c#L958" target="_blank">dict.c第958行</a>有这么一个函数<code>_dictExpandIfNeeded</code>：</p><pre><code class="language-C">/* 如果需要，扩展哈希表 */static int _dictExpandIfNeeded(dict *d){    /* 如果正在rehash那么就直接返回 */    if (dictIsRehashing(d)) return DICT_OK;    /* 如果哈希表为空，则将其扩展到初始大小 */    if (d-&gt;ht[0].size == 0) return dictExpand(d, DICT_HT_INITIAL_SIZE);    /* 如果哈希表ht[0]中保存的key个数与哈希表大小的比例已经达到1:1，即保存的节点数已经大于等于哈希表大小，     * 并且当前redis的服务允许执行rehash（此时Redis没有在执行&#96;bgsave&#96;或者&#96;bgrewiteaof&#96;命令的时候）。     * 或者保存的节点数与哈希表大小的比例超过了安全阈值（默认值为5），     * 则将哈希表大小扩容为原来的两倍 */    if ((dict_can_resize == DICT_RESIZE_ENABLE &amp;&amp;         d-&gt;ht[0].used &gt;= d-&gt;ht[0].size) ||        (dict_can_resize != DICT_RESIZE_FORBID &amp;&amp;         d-&gt;ht[0].used / d-&gt;ht[0].size &gt; dict_force_resize_ratio)){        return dictExpand(d, d-&gt;ht[0].used*2);    }    return DICT_OK;}</code></pre><p>从上面源码中的注释可以看到<code>rehash</code>触发条件主要有两个：</p><ol><li>当<code>used</code>&gt;=<code>size</code>（<mark>哈希表中已经使用了的节点数量大于等于哈希表的大小</mark>）并且此时Redis没有在执行<code>bgsave</code>或者<code>bgrewiteaof</code>命令时，会触发<code>rehash</code>。</li><li>或者当<code>used</code>/<code>size</code>&gt;<code>dict_force_resize_ratio</code>（<mark>哈希表中已经使用了的节点数量和哈希表的大小的比例超过5</mark>）时，不管此时Redis是否在执行<code>bgsave</code>或者<code>bgrewiteaof</code>命令，都会强制进行<code>rehash</code>操作。</li></ol><h5 id="%E7%BC%A9%E5%AE%B9" tabindex="-1">缩容</h5><blockquote><p>在数据量增加的时候，Redis会进行<code>rehash</code>，这个时候是扩容2倍，那么当数据减少的时候呢？</p></blockquote><p>Redis在数据量减少的时候也会进行缩容，以便节约空间。在源码<a href="https://github.com/redis/redis/blob/6.0/src/server.c#L1700" target="_blank">server.c第1700行</a>有<code>databasesCron</code>这么一个函数用来处理Redis数据库中我们需要增量执行的“后台”操作，例如活动键过期、<code>resize</code>、<code>rehash</code>等。这里主要看<code>resize</code>部分：</p><pre><code class="language-c">void databasesCron(void) {    /* 通过随机抽样使键过期。对于Slave服务器来说不需要，因为Master服务器会同步DEL命令。*/        if (server.active_expire_enabled) {        if (iAmMaster()) {            activeExpireCycle(ACTIVE_EXPIRE_CYCLE_SLOW);        } else {            expireSlaveKeys();        }    }    /* 逐步对键进行碎片整理。 */    activeDefragCycle();    /* 在没有BGSAVE或者BGREWRITEAOF执行时，对哈希表进行 rehash */    if (!hasActiveChildProcess()) {        /* 使用全局计数器，这样如果在给定的DB上停止计算，我们就能在下一个定时任务从后续的DB开始*/        static unsigned int resize_db = 0;        static unsigned int rehash_db = 0;        int dbs_per_call = CRON_DBS_PER_CALL;        int j;        /* 不能超过拥有的DB最大数量 */        if (dbs_per_call &gt; server.dbnum) dbs_per_call = server.dbnum;        /* Resize调整大小 */        for (j = 0; j &lt; dbs_per_call; j++) {            tryResizeHashTables(resize_db % server.dbnum);            resize_db++;        }        /* Rehash */        if (server.activerehashing) {            for (j = 0; j &lt; dbs_per_call; j++) {                int work_done = incrementallyRehash(rehash_db);                if (work_done) {                    /* 如果已经rehash了一部分，在这里停止并在下一个计划循环中继续进行。*/                    break;                } else {                    /* 如果这个DB不需要rehash，接着尝试下一个*/                    rehash_db++;                    rehash_db %= server.dbnum;                }            }        }    }}</code></pre><p>可以看到缩容的时候也会选在没有<code>bgsave</code>或者<code>bgrewiteaof</code>执行的时候。可以看到主要是<code>tryResizeHashTables</code>这个函数在调整大小，这个函数源码在<a href="https://github.com/redis/redis/blob/6.0/src/server.c#L1436" target="_blank">server.c第1436行</a>：</p><pre><code class="language-c">int htNeedsResize(dict *dict) {    long long size, used;    size = dictSlots(dict);    used = dictSize(dict);    return (size &gt; DICT_HT_INITIAL_SIZE &amp;&amp;            (used*100/size &lt; HASHTABLE_MIN_FILL));}/* 如果哈希表中已用槽位的百分比达到 HASHTABLE_MIN_FILL，我们会重新调整哈希表的大小，以节省内存 */void tryResizeHashTables(int dbid) {    if (htNeedsResize(server.db[dbid].dict))        dictResize(server.db[dbid].dict);    if (htNeedsResize(server.db[dbid].expires))        dictResize(server.db[dbid].expires);}</code></pre><p><code>HASHTABLE_MIN_FILL</code>和<code>DICT_HT_INITIAL_SIZE</code>分别定义在<a href="https://github.com/redis/redis/blob/6.0/src/server.h#L168" target="_blank">server.h第168行</a>和<a href="https://github.com/redis/redis/blob/e8c5b66ed2aaf40bec345ff5aca90721fb707d30/deps/hiredis/dict.h#L76" target="_blank">dict.h第76行</a>中：</p><pre><code class="language-c">/* 每个hashtable的初始大小 */#define DICT_HT_INITIAL_SIZE     4/* 哈希表参数 */#define HASHTABLE_MIN_FILL        10      /* 最小哈希表填充 10% */</code></pre><p>可以看到<code>HASHTABLE_MIN_FILL</code>和<code>DICT_HT_INITIAL_SIZE</code>分别是10和4，那么从<code>htNeedsResize</code>就可以得知当<mark>哈希表的大小大于初始容量</mark>且<mark>保存的key数量与哈希表的大小的比例小于0.1</mark>的时候需要缩容。</p>]]>
                    </description>
                    <pubDate>Sun, 12 Mar 2023 22:51:06 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Redis数据结构-SDS]]>
                    </title>
                    <link>https://zygzyg.com/archives/48f61eeb78ab477e83e3892ca0b4d67e</link>
                    <description>
                            <![CDATA[<h1 id="redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84-sds" tabindex="-1">Redis数据结构-SDS</h1><blockquote><p>Redis中的String类型有有三种编码方式：<code>int</code>、<code>embstr</code>、<code>raw</code>。</p></blockquote><ul><li><p><code>int</code>：保存long型（长整型）的64位（8个字节）有符号整数。范围是<code>[-2^63^,2^63^-1]</code>，也就是<code>[-9223372036854775808,9223372036854775807]</code>，最多19位。</p><blockquote><p>注意：只有整数才会使用<code>int</code>，如果是浮点数，Redis内部其实是先将浮点数转化为字符串值之后再保存。</p><pre><code class="language-shell">127.0.0.1:6379&gt; SET k1 1.1111111OK127.0.0.1:6379&gt; OBJECT ENCODING k1&quot;embstr&quot;</code></pre></blockquote></li><li><p><code>embstr</code>：代表<code>embstr</code>格式的<strong>SDS</strong>（<strong>Simple Dynamic String-简单动态字符串</strong>），保存长度小于44字节的字符串。<code>embstr</code>（embedded string），顾名思义表示嵌入式的String。</p></li><li><p><code>raw</code>：保存长度大于44字节的字符串。</p></li></ul><p>下面我们测试3个key：</p><ul><li><code>k1</code>的长度为9，可以看到编码方式是<code>int</code>。</li><li><code>k2</code>长度大于19，编码方式则为<code>embstr</code>。</li><li><code>k3</code>的长度大于44，编码方式则为<code>raw</code>。</li></ul><pre><code class="language-shell">127.0.0.1:6379&gt; SET k1 123456789OK127.0.0.1:6379&gt; OBJECT ENCODING k1&quot;int&quot;127.0.0.1:6379&gt; SET k2 123456789123456789123OK127.0.0.1:6379&gt; OBJECT ENCODING k2&quot;embstr&quot;127.0.0.1:6379&gt; SET k3 123456789123456789123456789123456789123456789123456789123456789123456789123456789OK127.0.0.1:6379&gt; OBJECT ENCODING k3&quot;raw&quot;</code></pre><h2 id="sds%EF%BC%88simple-dynamic-string-%E7%AE%80%E5%8D%95%E5%8A%A8%E6%80%81%E5%AD%97%E7%AC%A6%E4%B8%B2%EF%BC%89" tabindex="-1">SDS（Simple Dynamic String-简单动态字符串）</h2><blockquote><p>上面我们说到了<strong>SDS</strong>（<strong>Simple Dynamic String-简单动态字符串</strong>）。那么它是什么呢？</p></blockquote><p>Redis是用C语言实现的，但是它没有直接使用C语言的<code>char*</code>字符数组来实现字符串，而是自己封装了一个名为<strong>SDS</strong>（<strong>Simple Dynamic String-简单动态字符串</strong>）的数据结构来表示字符串，也就是 Redis的String数据类型的底层数据结构是<strong>SDS</strong>。</p><p>Redis中所有的key以及所有值对象中包含的字符串对象底层都是由<strong>SDS</strong>实现的。</p><h3 id="sds%E7%BB%93%E6%9E%84%E8%AE%BE%E8%AE%A1" tabindex="-1">SDS结构设计</h3><p>源码<a href="https://github.com/redis/redis/blob/unstable/src/sds.h" target="_blank">sds.h</a>中是这样写的：</p><pre><code class="language-C">/* Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. */struct __attribute__ ((__packed__)) sdshdr5 {    unsigned char flags; /* 3 lsb of type, and 5 msb of string length */    char buf[];};struct __attribute__ ((__packed__)) sdshdr8 {    uint8_t len; /* used */    uint8_t alloc; /* excluding the header and null terminator */    unsigned char flags; /* 3 lsb of type, 5 unused bits */    char buf[];};//和上面的不同只有len和alloc，也就是长度不同而已struct __attribute__ ((__packed__)) sdshdr16 {    uint16_t len; /* used */    uint16_t alloc; /* excluding the header and null terminator */    unsigned char flags; /* 3 lsb of type, 5 unused bits */    char buf[];};//和上面的不同只有len和alloc，也就是长度不同而已struct __attribute__ ((__packed__)) sdshdr32 {    uint32_t len; /* used */    uint32_t alloc; /* excluding the header and null terminator */    unsigned char flags; /* 3 lsb of type, 5 unused bits */    char buf[];};//和上面的不同只有len和alloc，也就是长度不同而已struct __attribute__ ((__packed__)) sdshdr64 {    uint64_t len; /* used */    uint64_t alloc; /* excluding the header and null terminator */    unsigned char flags; /* 3 lsb of type, 5 unused bits */    char buf[];};</code></pre><ul><li><p><code>len</code>：直接记录字符串的长度，这样在获取字符串长度的时候直接返回这个字段的值即可，时间复杂度是O(1)。</p></li><li><p><code>alloc</code>：分配给字符数组的空间长度。可以通过<mark>alloc-len</mark>计算出剩余的空间大小，在修改字符串的时候，可以判断剩余空间是否满足，不满足的话，会自动将SDS扩容至修改所需要的大小。所以使用SDS既不需要手动修改SDS的空间大小，也不会出现前面所说的缓冲区溢出的问题。</p></li><li><p><code>flags</code>：这个就是用来表示不同类型的SDS。从上面的源码可以看到一共设计了5种类型：<code>sdshdr5</code>、<code>sdshdr8</code>、<code>sdshdr16</code>、<code>sdshdr32</code>和<code>sdshdr64</code>。</p><blockquote><p>可以看到不同长度的SDS结构基本一样，只有<code>sdshdr5</code>不一样，但是注释中也说了不会使用到。</p></blockquote></li><li><p><code>buf[]</code>：字符数组，用来保存实际数据。不仅可以保存字符串，也可以保存二进制数据。</p></li></ul><h3 id="redis%E4%B8%BA%E4%BB%80%E4%B9%88%E8%A6%81%E8%87%AA%E5%B7%B1%E8%AE%BE%E8%AE%A1%E4%B8%80%E4%B8%AAsds" tabindex="-1">Redis为什么要自己设计一个SDS</h3><blockquote><p>Redis选择自己设计一个SDS结构来表示字符串，那是否说明C语言中<code>char*</code>字符数组存在一些缺陷？</p></blockquote><p>首先C语言的字符串其实就是一个字符数组，即数组中每个元素是字符串中的一个字符。</p><pre><code class="language-"># +-----+-----+-----+-----+-----+------+# |  R  |  E  |  D  |  I  |  S  |  \0  |# +-----+-----+-----+-----+-----+------+</code></pre><ol><li>C语言中获取字符串长度的时间复杂度为O(n)。</li><li>还有就是除了字符串的末尾之外，字符串里面不能含有<code>\0</code>字符，否则最先被程序读入的<code>\0</code>字符将被误认为是字符串结尾，这个限制使得C语言的字符串只能保存文本数据，不能保存像图片、音频、视频文化这样的二进制数据。</li><li>字符串操作函数不高效且不安全，比如有缓冲区溢出的风险，有可能会造成程序运行终止。</li></ol><p>SDS和C语言中的<code>char*</code>对比：</p><table>    <tr><th></th><th>C语言</th><th>SDS</th></tr>    <tr><th>对字符串长度的处理</th><td>需要从头开始遍历，直到遇到<code>\0</code>为止，时间复杂度是O(n)</td><td>直接记录当前字符串的长度，获取的时候直接返回`len`即可，时间复杂度O(1)</td></tr>    <tr>        <th>内存重新分配</th>        <td>分配的内存空间超过之后，会导致数组下标越界或者内存分配溢出</td>        <td>            <ul>                <li>空间预分配：<p>比如SDS修改后:</p>                    <ul>                            <li><code>len</code>长度小于1M，那么将会额外分配和<code>len</code>相同长度的未使用空间。</li>                            <li><code>len</code>长度大于1M，那么将分配1M的空间。</li>                    </ul>    </li>                <li>惰性空间释放：<p>既然有空间分配那就有空间释放。SDS缩短的时候并不会回收多余的内存空间，而是使用<code>free</code>字段将多出来的空间记录下来。如果后续有变更操作，直接使用<code>free</code>字段中记录的空间，减少了内存的分配。</p></li>            </ul>        </td>    </tr>    <tr>        <th>二进制安全</th>        <td>二进制数据并不是规则的字符串格式，可能会包含一些特殊的字符，比如<code>\0</code>这样的。上面说到C语言的<code>char*</code>中<code>\0</code>表示结束，这样<code>\0</code>后面的数据就会读取不到了</td>        <td>SDS是根据<code>len</code>长度来判断字符串结束的。</td>    </tr></table><h2 id="3%E4%B8%AA%E7%BC%96%E7%A0%81%E6%96%B9%E5%BC%8F%E7%9A%84%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90" tabindex="-1">3个编码方式的源码分析</h2><blockquote><p>当我们使用Redis的String类型的命令（比如<code>SET k1 v1</code>）来设置值的时候，Redis底层是怎么处理的呢？</p></blockquote><p>源码<a href="https://github.com/redis/redis/blob/unstable/src/t_string.c" target="_blank">t_string.c</a>中有个函数<code>setCommand(client *c)</code>是这样写的：</p><pre><code class="language-c">/* SET key value [NX] [XX] [KEEPTTL] [GET] [EX &lt;seconds&gt;] [PX &lt;milliseconds&gt;] *     [EXAT &lt;seconds-timestamp&gt;][PXAT &lt;milliseconds-timestamp&gt;] */void setCommand(client *c) {    robj *expire = NULL;    int unit = UNIT_SECONDS;    int flags = OBJ_NO_FLAGS;    if (parseExtendedStringArgumentsOrReply(c,&amp;flags,&amp;unit,&amp;expire,COMMAND_SET) != C_OK) {        return;    }    c-&gt;argv[2] = tryObjectEncoding(c-&gt;argv[2]);    setGenericCommand(c,flags,c-&gt;argv[1],c-&gt;argv[2],expire,unit,NULL,NULL);}</code></pre><p>可以看到上面代码中第<code>#10</code>行调用了一个名为<code>tryObjectEncoding</code>的函数，也就是尝试对数据编码，这个函数的源码在<a href="https://github.com/redis/redis/blob/unstable/src/object.c" target="_blank">object.c</a>中，如下：</p><pre><code class="language-c">/*尝试将字符串对象编码以节省空间*/robj *tryObjectEncoding(robj *o) {long value;sds s = o-&gt;ptr;size_t len;    /* 确保这是一个字符串对象，因为我们只在这个函数中编码字符串对象。     * 其他类型使用编码内存效率高的表示形式，但由其类型的命令来处理。*/serverAssertWithInfo(NULL,o,o-&gt;type == OBJ_STRING);    /* 只为仍由实际字符数组表示的RAW或EMBSTR编码的对象尝试一些专门的编码。*/if (!sdsEncodedObject(o)) return o;    /* 编码共享对象是不安全的：共享对象可以在Redis的“对象空间”中的任何地方共享，并可能出现在未处理的地方。     * 我们只将它们作为键空间中的值来处理。*/    if (o-&gt;refcount &gt; 1) return o;    /* 检查是否可以将此字符串表示为长整数。请注意，我们确信长于20个字符的字符串不能表示为32位或64位整数。*/    len = sdslen(s);    if (len &lt;= 20 &amp;&amp; string2l(s,len,&amp;value)) {        /* 表示这个对象可以被编码为long并尝试使用共享对象。* 请注意，在使用maxmemory时，我们避免使用共享整数，* 这是因为每个对象都需要一个私有的LRU字段，以便LRU算法能够正常工作。*/        if ((server.maxmemory == 0 ||            !(server.maxmemory_policy &amp; MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &amp;&amp;            value &gt;= 0 &amp;&amp;            value &lt; OBJ_SHARED_INTEGERS){            decrRefCount(o);            return shared.integers[value];        } else {            if (o-&gt;encoding == OBJ_ENCODING_RAW) {                sdsfree(o-&gt;ptr);                o-&gt;encoding = OBJ_ENCODING_INT;                o-&gt;ptr = (void*) value;                return o;            } else if (o-&gt;encoding == OBJ_ENCODING_EMBSTR) {                decrRefCount(o);                return createStringObjectFromLongLongForValue(value);            }        }    }    /* 如果字符串很小且仍以RAW编码，则尝试更高效的EMBSTR编码。     * 在此表示中，对象和SDS字符串在同一块内存中分配，以节省空间和缓存未命中。 */    if (len &lt;= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {        robj *emb;        if (o-&gt;encoding == OBJ_ENCODING_EMBSTR) return o;        emb = createEmbeddedStringObject(s,sdslen(s));        decrRefCount(o);        return emb;    }    /* 不能编码这个对象，最后尝试一下，至少优化SDS字符串内部 */    trimStringObjectIfNeeded(o, 0);    /* 返回原始对象。 */    return o;}</code></pre><h3 id="int%E7%BC%96%E7%A0%81" tabindex="-1">INT编码</h3><p>当字符串键值的内容可以用一个64位有符号的整形来表示的时候，Redis会将键值转换为<code>long</code>型来存储，此时对应的就是<code>OBJ_ENCODING_INT</code>编码类型。</p><p>在源码<a href="https://github.com/redis/redis/blob/unstable/src/server.h" target="_blank">server.h</a>中可以看到Redis启动的时候会预先建立10000个分别存储[0,9999]的<code>redisObject</code>作为共享对象，也就是说当我们使用<code>SET</code>命令的时候，如果值在[0,9999]之间的话，<mark>则可以直接指向共享对象而不需要建立新对象，此时的键值不占空间</mark>。</p><pre><code class="language-c">/* Static server configuration */#define OBJ_SHARED_INTEGERS 10000</code></pre><h3 id="embstr%E7%BC%96%E7%A0%81" tabindex="-1">EMBSTR编码</h3><p>可以看到上面源码中的第<code>#40</code>行，对于长度小于<code>OBJ_ENCODING_EMBSTR_SIZE_LIMIT</code>（44），Redis会采用<code>OBJ_ENCODING_EMBSTR</code>的方式，<mark>embedded string</mark>表示嵌入式String：从内存结构上来讲，就是<strong>SDS</strong>结构体和它对应的<code>redisObject</code>对象分配在同一块连续的内存空间，就像字符串<strong>SDS</strong>嵌入在<code>redisObject</code>对象之中一样。</p><p>然后在上面的第<code>#43</code>调用了一个函数<code>createEmbeddedStringObject</code>来创建一个<code>EmbStr</code>对象，其源码也是在<a href="https://github.com/redis/redis/blob/unstable/src/object.c" target="_blank">object.c</a>中如下：</p><pre><code class="language-c">/* 如果字符串对象比OBJ_ENCODING_EMBSTR_SIZE_LIMIT小，则使用EMBSTR编码创建字符串对象，否则使用RAW编码。 * 当前的限制为44，是为了使我们分配的最大字符串对象作为EMBSTR仍适合于jemalloc的64字节arena。*/#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44/* 创建一个字符串对象，其编码为OBJ_ENCODING_EMBSTR，即sds字符串实际上是一个不可修改的字符串，分配在与对象本身相同的块中。*/robj *createEmbeddedStringObject(const char *ptr, size_t len) {    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);    struct sdshdr8 *sh = (void*)(o+1);    o-&gt;type = OBJ_STRING;    o-&gt;encoding = OBJ_ENCODING_EMBSTR;    o-&gt;ptr = sh+1;    o-&gt;refcount = 1;    if (server.maxmemory_policy &amp; MAXMEMORY_FLAG_LFU) {        o-&gt;lru = (LFUGetTimeInMinutes()&lt;&lt;8) | LFU_INIT_VAL;    } else {        o-&gt;lru = LRU_CLOCK();    }    sh-&gt;len = len;    sh-&gt;alloc = len;    sh-&gt;flags = SDS_TYPE_8;    if (ptr == SDS_NOINIT)        sh-&gt;buf[len] = &#39;\0&#39;;    else if (ptr) {        memcpy(sh-&gt;buf,ptr,len);        sh-&gt;buf[len] = &#39;\0&#39;;    } else {        memset(sh-&gt;buf,0,len+1);    }    return o;}</code></pre><h3 id="raw%E7%BC%96%E7%A0%81" tabindex="-1">RAW编码</h3><p>当字符串的长度大于<code>OBJ_ENCODING_EMBSTR_SIZE_LIMIT</code>的时候，Redis则会将编码方式改为<code>OBJ_ENCODING_RAW</code>格式，这和<code>OBJ_ENCODING_EMBSTR</code>编码方式的不同之处在于：此时<strong>SDS</strong>的内存和其依赖的<code>redisObject</code>的内存就不再连续了。</p><blockquote><p>注意这里存在一个问题：在没有超过<code>OBJ_ENCODING_EMBSTR_SIZE_LIMIT</code>的时候，还是变成了<code>RAW</code>编码。</p><pre><code class="language-shell">127.0.0.1:6379&gt; SET k1 abcOK127.0.0.1:6379&gt; OBJECT ENCODING k1&quot;embstr&quot;127.0.0.1:6379&gt; APPEND k1 def(integer) 6127.0.0.1:6379&gt; get k1&quot;abcdef&quot;127.0.0.1:6379&gt; OBJECT ENCODING k1&quot;raw&quot;</code></pre><p>因为<code>embstr</code>的实现是只读的，在对<code>embstr</code>对象进行修改的时候（比如上面操作的<code>APPEND</code>），都会先转化成<code>RAW</code>然后在修改。所以只要是修改<code>embstr</code>编码的数据，修改后无论其长度是多少，其编码方式一定是<code>RAW</code>。</p></blockquote><h3 id="%E7%BC%96%E7%A0%81%E8%BD%AC%E5%8F%98%E7%9A%84%E6%B5%81%E7%A8%8B%E5%9B%BE" tabindex="-1">编码转变的流程图</h3><div class="mermaid"><svg id="render2748360606" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="1008.5078125" style="max-width: 770px;" viewBox="0 0 770 1008.5078125"><style>#render2748360606 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render2748360606 .error-icon{fill:#a5a7bb;}#render2748360606 .error-text{fill:#5a5844;stroke:#5a5844;}#render2748360606 .edge-thickness-normal{stroke-width:2px;}#render2748360606 .edge-thickness-thick{stroke-width:3.5px;}#render2748360606 .edge-pattern-solid{stroke-dasharray:0;}#render2748360606 .edge-pattern-dashed{stroke-dasharray:3;}#render2748360606 .edge-pattern-dotted{stroke-dasharray:2;}#render2748360606 .marker{fill:#F8B229;stroke:#F8B229;}#render2748360606 .marker.cross{stroke:#F8B229;}#render2748360606 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render2748360606 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render2748360606 .cluster-label text{fill:#5a5844;}#render2748360606 .cluster-label span{color:#5a5844;}#render2748360606 .label text,#render2748360606 span{fill:#fff;color:#fff;}#render2748360606 .node rect,#render2748360606 .node circle,#render2748360606 .node ellipse,#render2748360606 .node polygon,#render2748360606 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render2748360606 .node .label{text-align:center;}#render2748360606 .node.clickable{cursor:pointer;}#render2748360606 .arrowheadPath{fill:undefined;}#render2748360606 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render2748360606 .flowchart-link{stroke:#F8B229;fill:none;}#render2748360606 .edgeLabel{background-color:#006100;text-align:center;}#render2748360606 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render2748360606 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render2748360606 .cluster text{fill:#5a5844;}#render2748360606 .cluster span{color:#5a5844;}#render2748360606 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render2748360606 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 8)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path d="M417.005859375,42L417.005859375,46.166666666666664C417.005859375,50.333333333333336,417.005859375,58.666666666666664,417.0891927083333,67.08333333333333C417.1725260416667,75.5,417.3391927083333,84,417.4225260416667,88.25L417.505859375,92.5" id="L-start-ifEmbORRaw-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-start LE-ifEmbORRaw" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M389.56422431835745,213.30055244335745L383.2179082861312,223.6241582861312C376.87159225390496,233.94776412890496,364.1789601894525,254.59497581445248,357.91597749055956,270.75191499055956C351.6529947916667,286.9088541666667,351.8196614583333,298.5755208333333,351.9029947916667,304.4088541666667L351.986328125,310.2421875" id="L-ifEmbORRaw-iflen20-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-ifEmbORRaw LE-iflen20" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M305.963370079323,456.297354454323L293.53099850360246,469.63451412860246C281.098626927882,482.971673802882,256.233883776441,509.64599315144096,243.88484553405382,528.8164861590539C231.53580729166666,547.9869791666666,231.70247395833334,559.6536458333334,231.78580729166666,565.4869791666666L231.869140625,571.3203125" id="L-iflen20-ifMaxmemory-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-iflen20 LE-ifMaxmemory" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M181.89836617279295,756.037038047793L171.3729874356608,770.0321671231608C160.84760869852863,784.0272961985287,139.79685122426432,812.0175543492643,129.27147248713217,831.7626834246321C118.74609375,851.5078125,118.74609375,863.0078125,118.74609375,868.7578125L118.74609375,874.5078125" id="L-ifMaxmemory-embstrORRaw-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-ifMaxmemory LE-embstrORRaw" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M118.74609375,908.5078125L118.74609375,912.6744791666666C118.74609375,916.8411458333334,118.74609375,925.1744791666666,180.140625,935.8343684730611C241.53515625,946.4942577794554,364.32421875,959.4807030589109,425.71875,965.9739256986386L487.11328125,972.4671483383663" id="L-embstrORRaw-last-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-embstrORRaw LE-last" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M537.151475204571,748.161240829571L533.6034428788092,763.4690027746425C530.0554105530474,778.7767647197139,522.9593459015236,809.392288609857,519.4113135757618,830.4500505549286C515.86328125,851.5078125,515.86328125,863.0078125,515.86328125,868.7578125L515.86328125,874.5078125" id="L-iflen44-encodeToEmbstr-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-iflen44 LE-encodeToEmbstr" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M515.86328125,908.5078125L515.86328125,912.6744791666666C515.86328125,916.8411458333334,515.86328125,925.1744791666666,515.86328125,933.5078125C515.86328125,941.8411458333334,515.86328125,950.1744791666666,515.86328125,954.3411458333334L515.86328125,958.5078125" id="L-encodeToEmbstr-last-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-encodeToEmbstr LE-last" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M409.93077358948625,444.37586703551375L433.28313163707185,459.69994127959484C456.63548968465756,475.0240155236758,503.34020577982875,505.6721640118379,526.7758971607477,534.2384257559189C550.2115885416666,562.8046875,550.3782552083334,589.2890625,550.4615885416666,602.53125L550.544921875,615.7734375" id="L-iflen20-iflen44-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-iflen20 LE-iflen44" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M281.839915077207,756.037038047793L292.19862714767254,770.0321671231608C302.557339218138,784.0272961985287,323.274763359069,812.0175543492643,333.6334754295345,831.7626834246321C343.9921875,851.5078125,343.9921875,863.0078125,343.9921875,868.7578125L343.9921875,874.5078125" id="L-ifMaxmemory-getFromShared-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-ifMaxmemory LE-getFromShared" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M343.9921875,908.5078125L343.9921875,912.6744791666666C343.9921875,916.8411458333334,343.9921875,925.1744791666666,367.845703125,935.1702101302491C391.69921875,945.1659410938317,439.40625,956.8240696876634,463.259765625,962.6531339845792L487.11328125,968.482198281495" id="L-getFromShared-last-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-getFromShared LE-last" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M576.0918086236111,736.0078007513889L585.4065853113426,753.3411360428241C594.721361999074,770.6744713342592,613.3509153745371,805.3411419171297,629.9809795319287,828.4244772085649C646.6110436893204,851.5078125,661.2416186286408,863.0078125,668.556906098301,868.7578125L675.8721935679612,874.5078125" id="L-iflen44-returnOringnal-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-iflen44 LE-returnOringnal" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M661.1923828125,908.5078125L652.29345703125,912.6744791666666C643.39453125,916.8411458333334,625.5966796875,925.1744791666666,606.1668294270834,934.152112245774C586.7369791666666,943.1297453248816,565.6751302083334,952.751678149763,555.1442057291666,957.5626445622038L544.61328125,962.3736109746446" id="L-returnOringnal-last-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-returnOringnal LE-last" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M471.73149160576617,187.01655526923386L511.4520242548051,201.7208273076949C551.1725569038441,216.42509934615592,630.6136222019221,245.83364342307797,670.334154850961,282.29442587820563C710.0546875,318.7552083333333,710.0546875,362.2682291666667,710.0546875,405.78125C710.0546875,449.2942708333333,710.0546875,492.8072916666667,710.0546875,539.87109375C710.0546875,586.9348958333334,710.0546875,637.5494791666666,710.0546875,688.1640625C710.0546875,738.7786458333334,710.0546875,789.3932291666666,708.6529505461166,820.4505208333334C707.2512135922331,851.5078125,704.447739684466,863.0078125,703.0460027305826,868.7578125L701.6442657766991,874.5078125" id="L-ifEmbORRaw-returnOringnal-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-ifEmbORRaw LE-returnOringnal" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M701.547619047619,908.5078125L702.5396825396825,912.6744791666666C703.531746031746,916.8411458333334,705.5158730158731,925.1744791666666,679.3601500496032,935.2909817451946C653.2044270833334,945.4074843237225,598.9088541666666,957.307156147445,571.7610677083334,963.2569920593063L544.61328125,969.2068279711674" id="L-returnOringnal-last-1" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-returnOringnal LE-last" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(351.486328125, 275.2421875)"><g class="label" transform="translate(-5.5, -9.5)"><rect rx="0" ry="0" width="11.00008487701416" height="19.000146865844727"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Y</tspan></text></g></g><g class="edgeLabel" transform="translate(231.369140625, 536.3203125)"><g class="label" transform="translate(-5.5, -9.5)"><rect rx="0" ry="0" width="11.00008487701416" height="19.000146865844727"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Y</tspan></text></g></g><g class="edgeLabel" transform="translate(118.74609375, 840.0078125)"><g class="label" transform="translate(-5.5, -9.5)"><rect rx="0" ry="0" width="11.00008487701416" height="19.000146865844727"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Y</tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(515.86328125, 840.0078125)"><g class="label" transform="translate(-5.5, -9.5)"><rect rx="0" ry="0" width="11.00008487701416" height="19.000146865844727"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Y</tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(550.044921875, 536.3203125)"><g class="label" transform="translate(-5.109375, -9.5)"><rect rx="0" ry="0" width="10.218829154968262" height="19.000146865844727"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">N</tspan></text></g></g><g class="edgeLabel" transform="translate(343.9921875, 840.0078125)"><g class="label" transform="translate(-5.109375, -9.5)"><rect rx="0" ry="0" width="10.218829154968262" height="19.000146865844727"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">N</tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(631.98046875, 840.0078125)"><g class="label" transform="translate(-5.109375, -9.5)"><rect rx="0" ry="0" width="10.218829154968262" height="19.000146865844727"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">N</tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(710.0546875, 536.3203125)"><g class="label" transform="translate(-5.109375, -9.5)"><rect rx="0" ry="0" width="10.218829154968262" height="19.000146865844727"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">N</tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-start-150" transform="translate(417.005859375, 25)"><rect style="" rx="17" ry="17" x="-28.75" y="-17" width="57.5" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">开始</tspan></text></g></g><g class="node default default" id="flowchart-ifEmbORRaw-151" transform="translate(417.005859375, 166.37109375)"><polygon points="74.37109375,0 148.7421875,-74.37109375 74.37109375,-148.7421875 0,-74.37109375" class="label-container" transform="translate(-74.37109375,74.37109375)" style=""></polygon><g class="label" style="" transform="translate(-41.87109375, -17.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">是否embstr</tspan><tspan xml:space="preserve" dy="1em" x="0" class="row">或者raw</tspan></text></g></g><g class="node default default" id="flowchart-iflen20-152" transform="translate(351.486328125, 405.78125)"><polygon points="96.0390625,0 192.078125,-96.0390625 96.0390625,-192.078125 0,-96.0390625" class="label-container" transform="translate(-96.0390625,96.0390625)" style=""></polygon><g class="label" style="" transform="translate(-63.5390625, -17.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">长度小于20？</tspan><tspan xml:space="preserve" dy="1em" x="0" class="row">且可以转换为long</tspan></text></g></g><g class="node default default" id="flowchart-iflen44-153" transform="translate(550.044921875, 688.1640625)"><polygon points="72.890625,0 145.78125,-72.890625 72.890625,-145.78125 0,-72.890625" class="label-container" transform="translate(-72.890625,72.890625)" style=""></polygon><g class="label" style="" transform="translate(-48.390625, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">长度小于44？</tspan></text></g></g><g class="node default default" id="flowchart-ifMaxmemory-154" transform="translate(231.369140625, 688.1640625)"><polygon points="117.34375,0 234.6875,-117.34375 117.34375,-234.6875 0,-117.34375" class="label-container" transform="translate(-117.34375,117.34375)" style=""></polygon><g class="label" style="" transform="translate(-84.84375, -17.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">使用了maxmemory配置</tspan><tspan xml:space="preserve" dy="1em" x="0" class="row">或者不在[0,9999]内</tspan></text></g></g><g class="node default default" id="flowchart-getFromShared-155" transform="translate(343.9921875, 891.5078125)"><rect class="basic label-container" style="" rx="0" ry="0" x="-64.5" y="-17" width="129" height="34"></rect><g class="label" style="" transform="translate(-57, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">从共享对象获取</tspan></text></g></g><g class="node default default" id="flowchart-encodeToEmbstr-156" transform="translate(515.86328125, 891.5078125)"><rect class="basic label-container" style="" rx="0" ry="0" x="-57.37109375" y="-17" width="114.7421875" height="34"></rect><g class="label" style="" transform="translate(-49.87109375, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">编码为embstr</tspan></text></g></g><g class="node default default" id="flowchart-returnOringnal-157" transform="translate(697.5, 891.5078125)"><rect class="basic label-container" style="" rx="0" ry="0" x="-56.5" y="-17" width="113" height="34"></rect><g class="label" style="" transform="translate(-49, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">返回原始对象</tspan></text></g></g><g class="node default default" id="flowchart-embstrORRaw-158" transform="translate(118.74609375, 891.5078125)"><rect class="basic label-container" style="" rx="0" ry="0" x="-110.74609375" y="-17" width="221.4921875" height="34"></rect><g class="label" style="" transform="translate(-103.24609375, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">根据embstr或者raw分别处理</tspan></text></g></g><g class="node default default" id="flowchart-last-159" transform="translate(515.86328125, 975.5078125)"><rect style="" rx="17" ry="17" x="-28.75" y="-17" width="57.5" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">结束</tspan></text></g></g></g></g></g></svg></div><h2 id="%E7%BB%93%E8%AE%BA" tabindex="-1">结论</h2><p>在Redis的String类型的底层数据结构中只有整数才会使用<code>int</code>编码方式，对于浮点数Redis内部实际上会先把浮点数转化成字符串之后再保存。</p><p>而<code>embstr</code>和<code>raw</code>编码其实都是<strong>SDS</strong>（<strong>Simple Dynamic String-简单动态字符串</strong>），这是Redis内部自己定义的一种结构。</p><p>这三种编码方式的区别是：</p><table><thead><tr><th style="text-align:center"><code>INT</code></th><th style="text-align:center"><code>EMBSTR</code></th><th style="text-align:center"><code>RAW</code></th></tr></thead><tbody><tr><td style="text-align:center">当为Long类型的整数时，<code>redisObject</code>中的<code>ptr</code>指针直接赋值为整数数据，不再额外地指向整数了，节省了指针的空间开销。</td><td style="text-align:center">当保存的是字符串数据且长度小于44字节的时候，<code>embstr</code>会调用内存分配函数，<mark>只分配一块连续的内存空间</mark>，空间中一次包含<code>redisObject</code>和<code>SDS</code>两个数据结构，使元数据、指针和<code>SDS</code>是一块连续的内存区域，从而避免内存岁碎片。</td><td style="text-align:center">当保存的是字符串数据且长度大于44字节的时候，<code>redisObject</code>的和<code>SDS</code>不再是连续的内存区域，会重新给<code>SDS</code>分配内存空间并用指针指向它。<code>RAW</code>会<mark>调用两次内存分配函数</mark>，分配两块内存空间，一块用来包含<code>redisObject</code>，另外一块用来包含<code>SDS</code>。</td></tr></tbody></table><ul><li><p><code>INT</code></p><pre><code class="language-">#  redisObject# +------------+# |    type    |=====&gt;(REDIS_STRING)# +------------+# |  encoding  |=====&gt;(OBJ_ENCODING_INT)# +------------+# |    lru     |# +------------+# |  refcount  |# +------------+# |  ptr(123)  |# +------------+</code></pre></li><li><p><code>EMB</code></p><pre><code class="language-">#  redisObject# +------------+# |    type    |=====&gt;(REDIS_STRING)# +------------+# |  encoding  |=====&gt;(OBJ_ENCODING_EMBSTR)# +------------+# |    lru     |# +------------+# |  refcount  |# +------------+# |    ptr     |# +------------+# |    len     |# +------------+# |   alloc    |# +------------+# |   flags    |# +------------+# | buf(&quot;abc&quot;) |# +------------+</code></pre></li><li><p><code>RAW</code></p><pre><code class="language-">#  redisObject# +------------+# |    type    |=====&gt;(REDIS_STRING)# +------------+# |  encoding  |=====&gt;(OBJ_ENCODING_RAW)# +------------+# |    lru     |# +------------+# |  refcount  |            SDS# +------------+      +-------------+# |    ptr     |=====&gt;|     len     |# +------------+      +-------------+#                     |    alloc    |#                     +-------------+#                     |    flags    |#                     +-------------+#                     |buf(&quot;xyz...&quot;)|#                     +-------------+</code></pre></li></ul>]]>
                    </description>
                    <pubDate>Sat, 11 Mar 2023 21:40:22 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Redis数据结构]]>
                    </title>
                    <link>https://zygzyg.com/archives/656e671f7923431489632abbab88acc1</link>
                    <description>
                            <![CDATA[<h1 id="redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84" tabindex="-1">Redis数据结构</h1><blockquote><p>通常我们都说Redis快，都是说它是基于内存的数据库。但是仅仅是这样吗？</p></blockquote><p>实际上Redis快的原因除了它本身是基于内存的数据库之外，实际上还有一个重要因素那就是它实现的数据结构。<strong>注意</strong>：【这里说到的数据结构并不是指Redis的数据类型（String、List、Hash、Set、Zset），而是这些数据类型底层依赖的数据结构。】</p><h2 id="redis%E7%9A%84%E4%BA%94%E4%B8%AA%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E5%92%8C%E5%BA%95%E5%B1%82%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E7%9A%84%E5%85%B3%E7%B3%BB" tabindex="-1">Redis的五个基本数据类型和底层数据结构的关系</h2><blockquote><p>Redis经过多个版本的迭代更新，底层数据结构也发生了变化。</p></blockquote><p>在<strong>Redis3.0</strong>的时候，5个基本数据类型对应的底层数据结构分别是：</p><table>    <tr>        <th style="text-align: center;">String</th>        <th style="text-align: center;">Set</th>        <th style="text-align: center;">List</th>        <th style="text-align: center;">Sorted Set</th>        <th style="text-align: center;">Hash</th>    </tr>    <tr>        <td style="text-align: center;">sds（动态字符串）</td>        <td style="text-align: center;">hashTable（哈希表）</td>        <td style="text-align: center;">quickList（双端链表）</td>        <td style="text-align: center;">skipList（跳表）</td>        <td style="text-align: center;">hashTable（哈希表）</td>    </tr>    <tr>        <td></td>        <td style="text-align: center;">intSet（整数数组）</td>        <td colspan="3" style="text-align: center;">zipList（压缩列表）</td>    </tr></table><p>在数据结构发生变化之后：</p><table>    <tr>        <th style="text-align: center;">String</th>        <th style="text-align: center;">Set</th>        <th style="text-align: center;">List</th>        <th style="text-align: center;">Sorted Set</th>        <th style="text-align: center;">Hash</th>    </tr>    <tr>        <td style="text-align: center;">sds（动态字符串）</td>        <td style="text-align: center;">hashTable（哈希表）</td>        <td style="text-align: center;">quickList（双端链表）</td>        <td style="text-align: center;">skipList（跳表）</td>        <td style="text-align: center;">hashTable（哈希表）</td>    </tr>    <tr>        <td style="text-align: center;"></td>        <td style="text-align: center;">intSet（整数数组）</td>        <td style="text-align: center;" colspan="3">listPack（紧凑列表）</td>    </tr></table><blockquote><p>可以看到，有很多底层使用了两种数据结构，比如<strong>Set</strong>在元素较少，且都是数字的情况下会使用整数列表，数据增加会变成哈希表。</p></blockquote><h2 id="redis%E7%9A%84%E9%94%AE%E5%80%BC%E5%AF%B9%E6%95%B0%E6%8D%AE%E5%BA%93%E6%98%AF%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0%E7%9A%84" tabindex="-1">Redis的键值对数据库是怎么实现的</h2><p>在Redis中key就是字符串对象，但是value可以是字符串对象，也可以是集合数据类型的对象，比如List对象、Hash对象、Set对象和Zset对象。比如这里新增几个key然后使用<code>type</code>命令即可查看value的类型，可以发现这些类型分别是string、hash以及list。</p><pre><code class="language-shell">127.0.0.1:6379&gt; SET k1 v1OK127.0.0.1:6379&gt; HSET student name jack age 13(integer) 2127.0.0.1:6379&gt; RPUSH list v1 v2 v3 v4(integer) 4127.0.0.1:6379&gt; type k1string127.0.0.1:6379&gt; type studenthash127.0.0.1:6379&gt; type listlist</code></pre><h3 id="%E5%85%A8%E5%B1%80%E5%93%88%E5%B8%8C%E8%A1%A8" tabindex="-1">全局哈希表</h3><h4 id="redis%E4%B8%ADkey%E6%98%AF%E5%A6%82%E4%BD%95%E4%BF%9D%E5%AD%98%E7%9A%84%E5%91%A2%EF%BC%9F" tabindex="-1">Redis中key是如何保存的呢？</h4><p>在<strong>Redis</strong>中，其实是使用了一个哈希表保存所有键值对，每一个key经过Redis内部的hash算法之后都会计算出一个地址，这个地址就对应一个哈希桶了，使用哈希表的最大的好处就是查找的时间复杂度只有O(1)。哈希表其实就是一个数组，数组中的元素叫做哈希桶。</p><pre><code class="language-">#+--------+--------+--------+--------+--------+--------+--------+--------+--------+#|  Hash  |  Hash  |  Hash  |  Hash  |  Hash  |  Hash  |  Hash  |  Hash  |  Hash  |#| Bucket | Bucket | Bucket | Bucket | Bucket | Bucket | Bucket | Bucket | Bucket |#+--------+--------+--------+--------+--------+--------+--------+--------+--------+</code></pre><p>但是说到hash算法必然有哈希冲突存在，那怎么解决呢？Redis使用了链表来解决这个问题（有点类似Java中的HashMap）。</p><pre><code class="language-">#   +------------+#   | HashBucket |#   +------------+         +------------+         +------------+#   | HashBucket |--------&gt;|  KeyValue  |--------&gt;|   ......   |#   +------------+         +------------+         +------------+#   | HashBucket |#   +------------+#   | HashBucket |#   +------------+         +------------+         +------------+         +------------+#   | HashBucket |--------&gt;|  KeyValue  |--------&gt;|  KeyValue  |--------&gt;|   ......   |#   +------------+         +------------+         +------------+         +------------+#   | HashBucket |#   +------------+#   | HashBucket |#   +------------+#   | HashBucket |#   +------------+</code></pre><p>当这个链表中的元素越来越多之后，一个哈希桶所对应的链表会越来越长，一个链表上的遍历时间复杂度是O(n)，会严重影响性能。Redis为了处理这个问题会有一个<code>rehash</code>操作。大概就是会在内部重新建一个长度为原始长度2倍的空哈希表，然后原哈希表上的元素重新<code>rehash</code>到新的哈希表中去，然后我们使用新的哈希表即可。但是Redis大部分操作还是单线程的，这个过程可能会阻塞其他操作，如果是在数据量很大的时候，那可能会花很长时间来完成。所以Redis采用了渐进式<code>rehash</code>操作，简单说就是在每次操作的时候顺便copy到新的哈希表，而且Redis本身也有定时任务来处理。</p><h4 id="%E5%93%88%E5%B8%8C%E6%A1%B6%E5%8F%88%E6%98%AF%E5%A6%82%E4%BD%95%E4%BF%9D%E5%AD%98key%E7%9A%84%E5%91%A2%EF%BC%9F" tabindex="-1">哈希桶又是如何保存key的呢？</h4><p>哈希桶存放的是指向键值对数据的指针（<code>dictEntry</code>），这样通过指针就能找到键值对数据，然后因为键值对的值可以保存字符串对象和集合数据类型的对象，所以键值对的数据结构中并不是直接保存值本身，而是保存了<code>*key</code>和<code>*val</code>指针，分别指向了实际的key对象和value对象，这样一来，即使值是集合数据，也可以通过<code>*val</code>指针找到。<code>dictEntry</code>源码结构如下：</p><pre><code class="language-c">struct dictEntry {    void *key;    union {        void *val;        uint64_t u64;        int64_t s64;        double d;    } v;    struct dictEntry *next;     /* 哈希桶里面的下一个entry */    void *metadata[];                                           };</code></pre><h2 id="redisobject" tabindex="-1">RedisObject</h2><blockquote><p>上面说到了<code>dictEntry</code>，<code>dictEntry</code>中的<code>*key</code>和<code>*val</code>指针指向的是Redis对象（<code>redisObject</code>），Redis中key和value对象都是<code>redisObject</code>。</p></blockquote><pre><code class="language-c">struct redisObject {    unsigned type:4;        /* 对象的类型，包括：OBJ_STRING、OBJ_LIST、OBJ_HASH、OBJ_SET、OBJ_ZSET等*/    unsigned encoding:4;    /* 具体对应的数据结构*/    unsigned lru:LRU_BITS;  /* 淘汰策略LRU和LFU会用到*/    int refcount;           /* 引用计数，当refcount为0的时候，表示该对象已经不被任何对象引用，可以被淘汰了*/    void *ptr;              /* 指向对象实际的数据结构的指针*/};</code></pre><p>为了便于操作，Redis采用<code>redisObject</code>结构来统一五种不同的数据类型，这样所有的数据类型就都可以以相同的形式在函数间传递而不用使用特定的类型结构。同时，为了识别不同的数据类型，<code>redisObject</code>中定义了<code>type</code>和<code>encoding</code>两个字段来对不同的数据类型加以区分。</p><p>RedisObject中每个字段的含义：</p><ol><li><p><code>type</code>表示具体的数据类型</p></li><li><p><code>encoding</code>表示该类型的物理编码方式，同一种数据类型可能会有不同的编码方式（比如String类型就有三种编码方式，分别有：<code>int</code>、<code>embstr</code>、<code>raw</code>）。</p><pre><code class="language-c">/* Objects encoding. Some kind of objects like Strings and Hashes can be * internally represented in multiple ways. The &#39;encoding&#39; field of the object * is set to one of this fields for this object. */#define OBJ_ENCODING_RAW 0     /* Raw representation */#define OBJ_ENCODING_INT 1     /* Encoded as integer */#define OBJ_ENCODING_HT 2      /* Encoded as hash table */#define OBJ_ENCODING_ZIPMAP 3  /* No longer used: old hash encoding. */#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */#define OBJ_ENCODING_ZIPLIST 5 /* No longer used: old list/hash/zset encoding. */#define OBJ_ENCODING_INTSET 6  /* Encoded as intset */#define OBJ_ENCODING_SKIPLIST 7  /* Encoded as skiplist */#define OBJ_ENCODING_EMBSTR 8  /* Embedded sds string encoding */#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of listpacks */#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */</code></pre></li><li><p><code>lru</code>则是用于淘汰策略的字段，具体可以看<a href="/archives/redis%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5" target="_blank">这里</a>。</p></li><li><p><code>refcount</code>则表示对象的引用计数。</p></li><li><p><code>ptr</code>则是指向对象实际的数据结构的指针。</p></li></ol><h3 id="debug-obeject" tabindex="-1">DEBUG OBEJECT</h3><blockquote><p>上面我们说到了<mark>同一种数据类型可能会有不同的编码方式</mark>。那怎么确定使用的是哪种编码方式呢？</p></blockquote><p>Redis提供了<code>DEBUG OBEJECT</code>命令，<code>DEBUG OBEJECT</code>是一个调试命令，可以查看key的详细信息，其不应该在客户端使用。直接使用会返回error。</p><pre><code class="language-shell">127.0.0.1:6379&gt; DEBUG OBJECT key(error) ERR DEBUG command not allowed. If the enable-debug-command option is set to &quot;local&quot;, you can run it from a local connection, otherwise you need to set this option in the configuration file, and then restart the server.</code></pre><p>这里根据报错信息可以看到，想要使用这个命令还需要修改一个配置<code>enable-debug-command</code>为<code>local</code>，修改需重启Redis。</p><p>然后开始测试，首先创建两个key分别使用命令：</p><pre><code class="language-shell">127.0.0.1:6379&gt; SET k1 v1OK127.0.0.1:6379&gt; SET COUNT 100OK127.0.0.1:6379&gt; type k1string127.0.0.1:6379&gt; type COUNTstring</code></pre><p>可以看到这里个key的类型都是String，然后我们再使用<code>DEBUG OBEJECT</code>查看key的详细信息：</p><pre><code class="language-shell">127.0.0.1:6379&gt; DEBUG OBJECT k1Value at:0xffff87135d38 refcount:1 encoding:embstr serializedlength:3 lru:2460675 lru_seconds_idle:113127.0.0.1:6379&gt; DEBUG OBJECT COUNTValue at:0xffff871147b0 refcount:2147483647 encoding:int serializedlength:2 lru:2460514 lru_seconds_idle:279</code></pre><p>可以这两个key的类型虽然都是String，但是它们的编码方式却是不同的，分别是<code>int</code>和<code>embstr</code>。</p><h2 id="redis%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E8%A7%A3%E6%9E%90" tabindex="-1">Redis数据结构解析</h2><blockquote><p>更多关于Redis的底层的数据结构解析可以看这里：</p><ul class="contains-task-list"><li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"> <a href="/archives/48f61eeb78ab477e83e3892ca0b4d67e">《Redis数据结构-SDS》</a></li><li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"> <a href="/archives/7ef23e66bf0a4b6a9db2f0a9c7626319">《Redis数据结构-HashTable》</a></li><li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"> <a href="/archives/ec31e437eaab44d195729c2e8f548722">《Redis数据结构-ZipList与ListPack》</a></li><li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"> <a href="/archives/c59a970681a64c8780d7a1a7eca628d2">《Redis数据结构-QuickList》</a></li><li class="task-list-item"><input class="task-list-item-checkbox" checked="" disabled="" type="checkbox"> <a href="/archives/05d7b23a1de74880a68d7525f9f5a9d1">《Redis数据结构-IntSet》</a></li><li class="task-list-item"><input class="task-list-item-checkbox" disabled="" type="checkbox"> <a href="/archives/a167b8335bb44ab79a8f1915687f4737">《Redis数据结构-SkipList》</a></li></ul></blockquote>]]>
                    </description>
                    <pubDate>Sat, 11 Mar 2023 20:38:36 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Redis7新特性]]>
                    </title>
                    <link>https://zygzyg.com/archives/440261d60cae4d8dac38d9a1a2933523</link>
                    <description>
                            <![CDATA[<blockquote><ul><li><h1 id="redis7-%E6%96%B0%E7%89%B9%E6%80%A7" tabindex="-1">Redis7 新特性</h1><p><a href="https://redis.io/docs/" target="_blank">Redis7</a>和之前的redis版本保持一致稳定。一部分新特性如下：</p><table><thead><tr><th>新特性</th><th>描述</th></tr></thead><tbody><tr><td>多<strong>AOF</strong>文件支持</td><td><strong>7.0</strong>版本中的一个比较大的变化就是<strong>aof</strong>文件由一个变成了多个，主要分为两种类型：基本文件（base files）。增量文件（incr files）。在此之外还引入了一个清单文件（mainfest）用于跟踪文件以及文件的创建和应用顺序（恢复）。</td></tr><tr><td><strong>config</strong>命令增强</td><td>对于<strong>Config Set</strong>和<strong>Get</strong>命令，支持在一次调用过程中传递多个配置参数。例如现在可以在执行一次<strong>Config Set</strong>命令中更改多个参数：<code>config set maxmemory 10000001 maxmemory-clients 50% port 6399</code></td></tr><tr><td>限制客户端内存使用<strong>Client-eviction</strong></td><td>一旦<strong>Redis</strong>连接较多，再加上每个连接的内存占用都比较大的时候，<strong>Redis</strong>总连接内存占用可能会达到<strong>maxmemory</strong>的上限，可以增加允许限制所有客户端的总内存使用量配置项，配置文件中对应的配置项：<code>maxmemory-clients 1g``maxmemory-clients 10%</code>可以使用两种配置方式，分别是指定具体内存大小和基于maxmemory的百分比。</td></tr><tr><td><strong>listpack</strong>紧凑列表调整</td><td><strong>listpack</strong>是用来替代<strong>ziplist</strong>的新数据结构，在7.0版本已经没有ziplist的配置了（6.0版本仅部分数据类型作为过渡阶段在使用）<strong>listpack</strong>已经替换了<strong>ziplist</strong>类似<code>hash-max-ziplist-entries</code>的配置</td></tr><tr><td>访问安全性增强<strong>ACLV2</strong></td><td>在<strong>redis.conf</strong>配置文件中，<code>protected-mode</code>默认为<strong>yes</strong>，只有当希望客户端在没有授权的情况下可以连接到<strong>Redis Server</strong>的时候可以修改为<code>protected-mode no</code></td></tr><tr><td><strong>Redis Functions</strong></td><td><strong>Redis</strong>函数，一种新的通过服务端脚本扩展Redis的方式，函数与数据本身一起存储。</td></tr><tr><td><strong>RDB</strong>保存时间调整</td><td>持久化文件<strong>RDB</strong>的保存规则发生改变，尤其是时间记录频度变化。</td></tr><tr><td>命令新增和改动</td><td><code>Zset</code>（有序集合）增加<code>ZMPOP</code>，<code>BZMPOP</code>，<code>ZINTERCARD</code>等命令。<code>SET</code>（集合）增加<code>SINTERCARD</code>命令。<code>LIST</code>（列表）增加<code>LMPOP</code>，<code>BLMPOP</code>，从提供的键名列表中的第一个非空列表键中弹出一个或多个元素。</td></tr><tr><td>性能资源利用率、安全等改进</td><td>自身底层部分优化改动，<strong>Redis</strong>核心在许多方面进行了重构和改进。主动碎片整理<strong>V2</strong>：增强版主动碎片整理，配合<strong>Jemalloc</strong>版本更新，更快更智能，延时更低。<strong>HyperLogLog</strong>改进：在<strong>Redis5.0</strong>中，<strong>HyperLogLog</strong>算法得到改进，优化了计数统计时的内存使用效率，<strong>7.0</strong>更加优秀，更好的内存统计报告。</td></tr></tbody></table></li></ul></blockquote>]]>
                    </description>
                    <pubDate>Thu, 23 Feb 2023 18:02:56 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[MySQL-索引的数据结构]]>
                    </title>
                    <link>https://zygzyg.com/archives/1e22f823dc214bfab3fe3afad169b3bd</link>
                    <description>
                            <![CDATA[<h1 id="mysql-%E7%B4%A2%E5%BC%95%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84" tabindex="-1">MySQL-索引的数据结构</h1><h2 id="%E4%B8%BA%E4%BB%80%E4%B9%88%E4%BD%BF%E7%94%A8%E7%B4%A2%E5%BC%95" tabindex="-1">为什么使用索引</h2><p>索引是存储引擎用于快速找到数据记录的一种数据结构，就好比一本字典的目录，通过目录中找到对应的页码，就可以快速定位想要查找的内容。在MySQL中也是类似的原理，在进行数据查找的时候，首先查看查询条件是否命中某条索引，符合则使用索引查找相关数据，如果不符合则需要<mark>全表扫描</mark>，也就是一条条地查找记录，直到找到与条件符合的记录。</p><h2 id="%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BC%98%E7%82%B9%E5%92%8C%E7%BC%BA%E7%82%B9" tabindex="-1">索引的优点和缺点</h2><h3 id="%E7%B4%A2%E5%BC%95%E7%9A%84%E6%A6%82%E8%BF%B0" tabindex="-1">索引的概述</h3><blockquote><p>MySQL官方对索引的定义是：索引（Index）是帮助MySQL高效获取数据的数据结构。</p></blockquote><p><strong>索引的本质</strong>：索引是数据结构。可以简单理解为：<mark>排好序的快速查找数据结构</mark>，满足特定的查找算法。这些数据结构以某种方式指向数据，这样就可以在这些数据结构的基础上实现高级查找算法。</p><p>索引实在存储引擎实现的，因此每种存储引擎的索引不一定完全相同，并且每种存储引擎不一定支持所有索引类型。同时存储引擎可以定义每个表的<mark>最大索引数</mark>和<mark>最大索引长度</mark>。所有存储引擎支持每个表至少16个索引，总索引的长度至少为256字节。有些存储引擎支持更多的索引数和更大的索引长度。</p><h3 id="%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BC%98%E7%82%B9" tabindex="-1">索引的优点</h3><ol><li>可以提高查找的速度，降低IO成本，这也是使用索引的主要原因。</li><li>通过创建唯一索引，可以保证数据库表中每一行的<mark>数据的唯一性</mark>。</li><li>在实现数据的参考完整性方面，可以<mark>加速表和表之间的连接</mark>。也就是说，对于有依赖关系的子表和父表的联合查询时，可以提高查询速度。</li><li>在使用分组和排序子句进行数据查询的时候，可以显著<mark>减少查询中分组和排序的时间</mark>，降低CPU消耗。</li></ol><h3 id="%E7%B4%A2%E5%BC%95%E7%9A%84%E7%BC%BA%E7%82%B9" tabindex="-1">索引的缺点</h3><blockquote><p>上面说到了索引有很多优点，主要就是提高查询速度，那么索引有哪些缺点呢？</p></blockquote><p>索引的缺点主要体现在下面几个方面：</p><ol><li>创建索引和维护索引需要耗费时间，并且随着数据量的增加，对应耗费的时间也会增加。</li><li>索引需要占用磁盘空间，除了数据表占数据空间之外，每一个索引还要占用一定的物理空间，存储在磁盘上，如果存在大量索引，索引文件可能比数据文件更快到达最大尺寸。</li><li>虽然索引大大提高了查询速度，但是同时也会降低更新表的速度。比如当对表中的数据进行增加、修改、删除的时候，索引也要动态地维护，这样也就是降低了数据地维护速度。</li></ol><p>所以在使用索引的时候，需要综合考虑索引的优点以及缺点。</p><blockquote><p>注意：索引可以提高查询速度，但是会影响插入、修改的速度。在这种情况下可以考虑先把索引删除，然后更新数据，最后再创建索引。</p></blockquote><h2 id="innodb%E4%B8%AD%E7%B4%A2%E5%BC%95%E7%9A%84%E6%8E%A8%E6%BC%94" tabindex="-1">InnoDB中索引的推演</h2><h3 id="%E5%9C%A8%E4%BD%BF%E7%94%A8%E7%B4%A2%E5%BC%95%E4%B9%8B%E5%89%8D%E7%9A%84%E6%9F%A5%E6%89%BE" tabindex="-1">在使用索引之前的查找</h3><p>假设有一个查询的SQL语句，如下：</p><pre><code class="language-sql">SELECT 字段列表 FROM 表名 WHERE 字段名 = xxx;</code></pre><h4 id="%E5%9C%A8%E4%B8%80%E4%B8%AA%E9%A1%B5%E4%B8%AD%E7%9A%84%E6%9F%A5%E6%89%BE" tabindex="-1">在一个页中的查找</h4><p>假设目前表中的记录比较少，所有的记录都可以被放到一个页中，在查找记录的时候可以根据搜索条件的不同分为两种情况：</p><ul><li><strong>以主键为搜索条件</strong>：可以在目录中使用<mark>二分法</mark>快速定位到对应的槽，然后再遍历该槽对应分组中的记录即可快速找到指定的记录。</li><li><strong>以其他列</strong>（<strong>字段</strong>）<strong>作为搜索条件</strong>：因为在数据页中并没有对非主键列建立所谓的页目录，所以我们无法通过二分法快速定位相应的槽。这种情况下只能从<mark>最小记录</mark>开始<mark>依次遍历</mark>单链表中的每条记录，然后对比每条记录是不是符合搜索条件。很显然，这种方式的效率是非常低的。</li></ul><h4 id="%E5%9C%A8%E5%BE%88%E5%A4%9A%E9%A1%B5%E4%B8%AD%E6%9F%A5%E6%89%BE" tabindex="-1">在很多页中查找</h4><p>大部分情况下我们表中存放的记录是非常多的，需要很多数据页来存储这些记录。在很多页中查找记录的话大致可以分为两个步骤：</p><ol><li>定位到记录所在的页。</li><li>从所在的页内查找相应的记录。</li></ol><p>在没有索引的情况下，不论是根据主键列或者其他非主键列查找，由于我们并不能快速的定位到记录所在的页，所以只能从第一个页沿着双向链表一直往下找，然后根据上面说到的<strong>在一个页中查找</strong>的方式来查找记录。</p><p>因为要遍历所有的数据页，所以这种方式显然是非常耗时的。如果这个表的数据量非常大呢，比如有1亿条记录，那么这个时候<mark>索引</mark>就应运而生了。</p><h3 id="%E8%AE%BE%E8%AE%A1%E7%B4%A2%E5%BC%95" tabindex="-1">设计索引</h3><p>首先我们创建一个表<code>index_demo</code>：</p><pre><code class="language-mysql">mysql&gt; CREATE TABLE index_demo(  a INT,  b INT,  c CHAR(1),  PRIMARY KEY (&#96;a&#96;)) ROW_FORMAT=COMPACT;Query OK, 0 rows affected (1.15 sec)mysql&gt; DESCRIBE index_demo;+-------+---------+------+-----+---------+-------+| Field | Type    | Null | Key | Default | Extra |+-------+---------+------+-----+---------+-------+| a     | int     | NO   | PRI | NULL    |       || b     | int     | YES  |     | NULL    |       || c     | char(1) | YES  |     | NULL    |       |+-------+---------+------+-----+---------+-------+3 rows in set (0.17 sec)</code></pre><p>这个表中有2个<code>INT</code>类型的列，1个<code>CHAR(1)</code>类型的列，规定字段<code>a</code>为主键，最后这个表使用<code>COMPACT</code>行格式来实际存储记录。那么这个表<code>index_demo</code>的行格式的示意图可以是：</p><p><img src="/upload/2023/04/%E8%A1%8C%E6%A0%BC%E5%BC%8F%E7%A4%BA%E6%84%8F%E5%9B%BE.png" alt="行格式示意图" /></p><p>这些分别代表：</p><ul><li><code>record_type</code>：表示记录的类型，比如<code>0</code>表示普通记录、<code>2</code>表示最小记录、<code>3</code>表示最大记录、<code>1</code>暂时没用，后面会说到。</li><li><code>next_record</code>：表示下一条记录地址相对于本条记录地址的偏移量，我们用箭头来表明下一条记录。</li><li><code>filed</code>：这里只记录了在<code>index_demo</code>这张表中的三个列，分别是<code>a</code>、<code>b</code>、<code>c</code>。</li><li><code>Extra</code>：除了上面的3种信息以外的所有信息，包括其他隐藏列的值以及记录的额外信息。</li></ul><p>那么对于有一些记录的示意图可以是下面这样：</p><p><img src="/upload/2023/04/%E6%9C%89%E4%B8%80%E4%BA%9B%E8%AE%B0%E5%BD%95%E7%9A%84%E7%A4%BA%E6%84%8F%E5%9B%BE.png" alt="有一些记录的示意图" /></p><h4 id="%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E7%B4%A2%E5%BC%95%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%96%B9%E6%A1%88" tabindex="-1">一个简单的索引的设计方案</h4><blockquote><p>为什么根据一些搜索条件查询时需要遍历所有数据页呢？这是因为每个页中的记录并没有规律，所以并不知道搜索条件会匹配哪些页中的记录，所以不得不依次遍历所有数据页。</p><p>那么如果我们想要快速定位到记录在哪些数据页怎么做呢？我们可以建立一个目录来快速定位记录所在的数据页。</p></blockquote><p>建立这个目录必须要完成下面这些事：</p><ol><li><p><strong>下一个数据页中数据记录的主键值必须大于上一个页中数据记录的主键值</strong>：</p><p>这里我们假设每个数据页最多可以存放3条记录（实际上一个数据页很大可以存放很多记录）。此时我们根据这个假设在上面创建的表<code>index_demo</code>中插入3条记录：</p><pre><code class="language-mysql">mysql&gt; INSERT INTO index_demo VALUES(1,4,&#39;u&#39;),(3,9,&#39;d&#39;),(5,3,&#39;y&#39;);Query OK, 3 rows affected (1.29 sec)Records: 3  Duplicates: 0  Warnings: 0</code></pre><p>那么这些记录已经按照主键值的大小串联成了一个单向链表了，那么上面的示意图就可以是：</p><p><img src="/upload/2023/04/%E6%8F%92%E5%85%A53%E6%9D%A1%E8%AE%B0%E5%BD%95%E5%90%8E%E7%9A%84%E7%A4%BA%E6%84%8F%E5%9B%BE.png" alt="插入3条记录后的示意图" /></p><p>此时我们再来插入一条记录：</p><pre><code class="language-mysql">mysql&gt; INSERT INTO index_demo VALUES(4,4,&#39;a&#39;);Query OK, 1 row affected (1.12 sec)</code></pre><p>因为上面我们假设了一个页中最多只能存放3条记录，那么这个时候就不得不再分配一个新的页：</p><p><img src="/upload/2023/04/%E5%86%8D%E6%96%B0%E5%A2%9E%E4%B8%80%E6%9D%A1%E8%AE%B0%E5%BD%95.png" alt="再新增一条记录" /></p><blockquote><p>这里注意一下，新分配的数据页的编号可能并不是连续的。它们只是通过维护着上一个页和下一个页的编号而建立了<mark>链表</mark>关系。</p></blockquote><p>另外这里<mark>页9</mark>中的记录最大主键值是<code>5</code>，而我们新插入的主键值是<code>4</code>，因为<code>4&lt;5</code>，所以这里并不符合下一个数据页中记录的主键值必须大于上一个页的主键值的要求，所以在上面我们插入了一个主键值为<code>4</code>的新纪录的时候需要有一个<mark>记录的移动</mark>。也就是说要把<mark>页9</mark>中主键值为<code>5</code>的记录移动到<mark>页30</mark>中去，然后再把主键值为<code>4</code>的记录插入到<mark>页9</mark>中去，过程如下：</p><p><img src="/upload/2023/04/%E8%AE%B0%E5%BD%95%E7%A7%BB%E5%8A%A8%E7%9A%84%E8%BF%87%E7%A8%8B%E6%AD%A5%E9%AA%A4.png" alt="记录移动的过程步骤" /></p><p>这个过程表明了在对页中的记录进行增删改操作的过程中，我们必须通过一些比如<mark>记录移动</mark>的操作来始终保持这个状态一直成立，这个状态也就是：下一个数据页中记录的主键值必须大于上一个页中记录的主键值。这个过程称为<code>页分裂</code>。</p></li><li><p><strong>给所有页建立一个目录项</strong>：</p></li></ol><p>由于数据也的编号可能是不连续的，所以在向<code>index_demo</code>表中插入许多条记录后，可能是这样的效果：</p><p><img src="/upload/2023/04/%E6%8F%92%E5%85%A5%E5%BE%88%E5%A4%9A%E6%9D%A1%E8%AE%B0%E5%BD%95%E5%90%8E.png" alt="插入很多条记录后" /></p><p>因为这些16kb的页在物理存储上是不连续的，所以如果想从这么多页中根据主键值快速定位某些记录所在的页，那么就需要给他做个目录，每个页都对应一个目录项，每个目录项又包括下面两个部分：</p><ul><li>页的记录中最小的主键值，用<code>key</code>来表示。</li><li>用<code>page_no</code>来表示页号。</li></ul><p>所以上面几个页的目录就像这样：</p><p><img src="/upload/2023/04/%E5%A2%9E%E5%8A%A0%E7%9B%AE%E5%BD%95%E9%A1%B9.png" alt="增加目录项" /></p><p>比如这里的<mark>页5</mark>，对应第三个目录项，在这个目录项中包含这个这个页的页号<code>5</code>还有这个页中最小的主键值<code>12</code>。接下来我们只需要把这几个目录项在物理存储器上连续存储（比如：数组），就可以实现根据主键快速查找某条记录的功能了。比如这里我们想要查找主键值为<code>20</code>的记录，可以分为下面两步：</p><ol><li>先在目录项中根据二分法快速确定出这个记录在第三个目录项，（因为<code>12&lt;20&lt;209</code>），那么它对应的页是<mark>页5</mark>。</li><li>再根据上面说到的在页中查找记录的方式在页<code>5</code>中定位具体的记录。</li></ol><h4 id="innodb%E4%B8%AD%E7%9A%84%E7%B4%A2%E5%BC%95%E6%96%B9%E6%A1%88" tabindex="-1">InnoDB中的索引方案</h4><h5 id="%E8%BF%AD%E4%BB%A31%E6%AC%A1%EF%BC%9A%E7%9B%AE%E5%BD%95%E9%A1%B9%E8%AE%B0%E5%BD%95%E7%9A%84%E9%A1%B5" tabindex="-1">迭代1次：目录项记录的页</h5><p>上面说到了一个简易的索引方案，是因为我们为了根据主键值进行查找时使用<mark>二分法</mark>快速定位具体的目录项而假设所有目录项都可以在物理存储器上连续存储，但是这样做存在几个问题：</p><ul><li>InnoDB是使用页来作为管理存储空间的基本单位，最多能保证<code>16kb</code>的连续存储空间，而随着表中记录数量的增多，需要非常大的连续存储空间才能把所有的目录项都放下，这样对于记录数量非常多的表不太现实。</li><li>我们常常会对表中的记录进行增删，假如我们把上面页30中的记录都删除了，那也就是说第二个目录项也就没有存在的必要了，这样就需要把后面的目录项全部往前面移动一下，这样做的效率将会非常差。</li></ul><p>所以我们需要一种可以灵活管理所有目录项的方式。我们发现目录项其实和记录长的差不多，只不过目录项中的两个页是主键和页号而已，为了和记录做一下区分，我们把这些用来表示目录项的记录称为<mark>目录项的记录</mark>。</p><blockquote><p>那么InnoDB是怎么区分一条记录是<mark>数据记录</mark>还是<mark>目录项记录</mark>的呢？</p></blockquote><p>可以使用记录头信息里的<code>record_type</code>属性，当表示目录项记录的时候，它的每个值表示：</p><ul><li><code>0</code>：普通数据记录。</li><li><code>1</code>：目录项记录。</li><li><code>2</code>：最小记录。</li><li><code>3</code>：最大记录。</li></ul><p>那么上面的目录项以及数据记录的结构会变成这样：</p><p><img src="/upload/2023/04/%E7%9B%AE%E5%BD%95%E9%A1%B9%E8%AE%B0%E5%BD%95%E7%9A%84%E9%A1%B5.png" alt="目录项记录的页" /></p><p>从图中可以看出来，我们新分配了一个编号为40的页来专门存储目录项记录。这里再次强调<mark>目录项记录</mark>和普通的<mark>数据记录</mark>的<strong>不同点</strong>：</p><ul><li>目录项记录的<code>record_type=1</code>，而普通记录的<code>record_typ=0</code>。</li><li>目录项记录只有<mark>主键值和页的编号</mark>两个列，而普通的记录的列是自己定义的，可能存在很多列，另外还有InnoDB自己添加的隐藏列。</li></ul><blockquote><p>记录头信息里还有一个叫<code>min_rec_mask</code>的属性，只有在存储目录项记录的页中的主键值最小的目录项记录的<code>min_rec_mask</code>值为1，其他别的记录的<code>min_rec_mask</code>值都是0。</p></blockquote><p><strong>相同点</strong>：两者用的是一样的数据页，都会为主键值生成<code>Page Directory</code>（页目录），从而在按照主键值进行查找时可以使用<mark>二分法</mark>来加快查询速度。</p><p>比如现在以查找主键为<code>20</code>的记录为例，根据某个主键值去查找记录的步骤就可以大致拆分成下边两步：</p><ol><li>先找到存储目录项记录的页，也就是在<mark>页40</mark>中通过二分法快速定位到对应目录项，因为<code>12&lt;20&lt;209</code>，所以可以定位到对应的记录所在的页就是页<code>5</code>。</li><li>再到存储记录的<mark>页5</mark>中根据二分法快速定位到主键值为<code>20</code>的记录。</li></ol><h5 id="%E8%BF%AD%E4%BB%A32%E6%AC%A1%EF%BC%9A%E5%A4%9A%E4%B8%AA%E7%9B%AE%E5%BD%95%E9%A1%B9%E8%AE%B0%E5%BD%95%E7%9A%84%E9%A1%B5" tabindex="-1">迭代2次：多个目录项记录的页</h5><blockquote><p>虽然说目录项记录中只存储主键值和对应的页号，比数据记录需要的存储空间小了很多了，但是不管怎么说一个页只有<code>16kb</code>的大小，能存放的目录项记录也是有限的，那如果表中的数据太多，以至于一个数据页不足以存放所有的<mark>目录项记录</mark>呢？</p></blockquote><p>这里我们假设一个存储目录项记录的页最多只能存放<code>4</code>条目录项记录，所以如果再向上图中插入一条主键值为<code>350</code>的记录的话，那就需要分配一个新的存储目录项记录的页：</p><p><img src="/upload/2023/04/%E5%A4%9A%E4%B8%AA%E7%9B%AE%E5%BD%95%E9%A1%B9%E8%AE%B0%E5%BD%95%E7%9A%84%E9%A1%B5.png" alt="多个目录项记录的页" />从图中可以看出，我们插入了一条主键值为<code>350</code>的记录之后需要两个新的页：</p><ul><li>为存储该数据记录而新生成了<mark>页44</mark>。</li><li>因为原先存储目录项记录的<mark>页40</mark>的容量已满（前边假设只能存储4条目录项记录），所以不得不需要一个新的<mark>页50</mark>来存放<mark>页44</mark>对应的目录项。</li></ul><p>现在因为存储目录项记录的页不止一个，所以如果我们想根据主键值查找一条数据记录大致需要3个步骤，还是以查找主键值为20的记录为例：</p><ol><li>确定目录项记录页：我们现在的存储目录项记录的页有两个，即<mark>页40</mark>和<mark>页50</mark>，又因为<mark>页40</mark>表示的目录项的主键值的范围是<code>[1,350)</code>，<mark>页50</mark>表示的目录项的主键值不小于<code>350</code>，所以主键值为20的记录对应的目录项记录在<mark>页5</mark>中。</li><li>通过目录项记录页<mark>确定数据记录真实所在的页</mark>。在一个存储目录项记录的页中通过主键值定位一条目录项记录的方式说过了。</li><li>在真实存储数据记录的页中定位到具体的记录。</li></ol><h5 id="%E8%BF%AD%E4%BB%A33%E6%AC%A1%EF%BC%9A%E7%9B%AE%E5%BD%95%E9%A1%B9%E8%AE%B0%E5%BD%95%E9%A1%B5%E7%9A%84%E7%9B%AE%E5%BD%95%E9%A1%B5" tabindex="-1">迭代3次：目录项记录页的目录页</h5><blockquote><p>问题来了，在这个查询步骤的第1步中我们需要定位存储目录项记录的页，但是这些页是不连续的，如果我们表中的数据非常多，则会产生很多存储目录项记录的页，那么我们怎么根据主键值快速定位一个存储目录项记录的页呢？</p></blockquote><p>我们可以为这些存储目录项记录的页在生成一个<mark>更高级的目录</mark>（开始套娃），就像是一个多级目录一样，大目录里面嵌套小目录，小目录里面才是实际的数据，所以现在每个页的示意图可以是这样的：、</p><p><img src="/upload/2023/04/%E7%9B%AE%E5%BD%95%E9%A1%B9%E8%AE%B0%E5%BD%95%E9%A1%B5%E7%9A%84%E7%9B%AE%E5%BD%95%E9%A1%B5.png" alt="目录项记录页的目录页" /></p><p>如图，我们生成了一个存储更高级目录项的<mark>页70</mark>，这个页中的两条记录分别代表<mark>页40</mark>和<mark>页50</mark>，如果数据记录的主键值在<code>[1,350)</code>之间，则到页40中查找更详细的目录项记录，如果<code>主键值&gt;=350</code>的话，就到页50中查找更详细的目录项记录。</p><p>随着表中记录的增加，这个目录的层级会继续增加，如果简化一下，那么我们可以用下边这个图来描述它：</p><div class="mermaid"><svg id="render1619403269" width="100%" xmlns="http://www.w3.org/2000/svg"><style>#render1619403269 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render1619403269 .error-icon{fill:#a5a7bb;}#render1619403269 .error-text{fill:#5a5844;stroke:#5a5844;}#render1619403269 .edge-thickness-normal{stroke-width:2px;}#render1619403269 .edge-thickness-thick{stroke-width:3.5px;}#render1619403269 .edge-pattern-solid{stroke-dasharray:0;}#render1619403269 .edge-pattern-dashed{stroke-dasharray:3;}#render1619403269 .edge-pattern-dotted{stroke-dasharray:2;}#render1619403269 .marker{fill:#F8B229;stroke:#F8B229;}#render1619403269 .marker.cross{stroke:#F8B229;}#render1619403269 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render1619403269 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render1619403269 .cluster-label text{fill:#5a5844;}#render1619403269 .cluster-label span{color:#5a5844;}#render1619403269 .label text,#render1619403269 span{fill:#fff;color:#fff;}#render1619403269 .node rect,#render1619403269 .node circle,#render1619403269 .node ellipse,#render1619403269 .node polygon,#render1619403269 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render1619403269 .node .label{text-align:center;}#render1619403269 .node.clickable{cursor:pointer;}#render1619403269 .arrowheadPath{fill:undefined;}#render1619403269 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render1619403269 .flowchart-link{stroke:#F8B229;fill:none;}#render1619403269 .edgeLabel{background-color:#006100;text-align:center;}#render1619403269 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render1619403269 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render1619403269 .cluster text{fill:#5a5844;}#render1619403269 .cluster span{color:#5a5844;}#render1619403269 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render1619403269 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g></g></svg></div><p>这个数据结构，它的名称是<code>B+树</code>。</p><h5 id="b%2Btree" tabindex="-1">B+Tree</h5><p>不论是存放<mark>数据记录</mark>的数据页们还是<mark>目录项记录</mark>的数据页，我们都把它们存放到B+数这个数据结构中了，所以我们页称这些数据页为<mark>节点</mark>。从上面的图中可以看出，我们的实际数据记录，其实都存放在B+数的最底层的节点上，这些节点也被称为<mark>叶子节点</mark>，其余用来存放目录项的节点称为<mark>非叶子节点</mark>或者<mark>内节点</mark>，其中<code>B+树</code>最上层的节点称为<mark>根节点</mark>。</p><p>一个<code>B+树</code>的节点其实可以分成很多层，规定最下边的那层，也就是存放我们数据记录的那层为<mark>第0层</mark>，之后依次往上加。之前我们做了一个非常极端的假设：存放数据记录的页最多存放<mark>3条记录</mark>，存放目录项记录的页最多存放<mark>4条记录</mark>。但是实际上真实环境中一个页存放的记录数量是非常大的，假设所有存放数据记录的<mark>叶子节点</mark>代表的数据页可以存放100条数据记录 ，所有存放目录项记录的<mark>内节点</mark>代表的数据页可以存放1000条目录项记录，那么：</p><ul><li><strong>如果B+树有1层</strong>：也就是只有1个用于存放数据记录的节点，最多能存放<code>100</code>条记录。</li><li><strong>如果B+树有2层</strong>：最多能存放<code>1000×100=10,0000</code>条记录。</li><li><strong>如果B+树有3层</strong>：最多能存放<code>1000×1000×100=1,0000,0000</code>条记录。</li><li><strong>如果B+树有4层</strong>：最多能存放<code>1000×1000×1000×100=1000,0000,0000</code>条记录。相当多的记录！</li></ul><blockquote><p>你的表里能存放<code>100000000000</code>条记录吗？所以一般情况下，我们用到的<code>B+树</code>都不会超过4层，所以我们通过主键值去查找某条记录最多只需要<mark>做4个页面内的查找</mark>（查找3个目录项页和一个数据记录页），又因为在每个页面内有所谓的<code>Page Directory（页目录）</code>，所以在页面内也可以通过二分法实现快速定位记录。</p></blockquote><h3 id="%E5%B8%B8%E8%A7%81%E7%9A%84%E7%B4%A2%E5%BC%95%E6%A6%82%E5%BF%B5" tabindex="-1">常见的索引概念</h3><blockquote><p>按照物理实现方式，索引可以分为2种，聚簇（聚集）和非聚簇（非聚集）索引。非聚集索引也可以称为二级索引或者辅助索引。</p></blockquote><h4 id="%E8%81%9A%E7%B0%87%E7%B4%A2%E5%BC%95" tabindex="-1">聚簇索引</h4><p><mark>聚簇索引</mark>并不是一种单独的索引类型，而是<mark>一种数据存储方式</mark>（所有的数据记录都存储在了叶子节点），也就是所谓的<code>索引即数据，数据即索引</code>。</p><blockquote><p>术语“聚簇”表示数据行和相邻的键值聚簇地存储在一起。</p></blockquote><h5 id="%E7%89%B9%E7%82%B9" tabindex="-1">特点</h5><ol><li>使用记录主键值的大小进行记录和页的排序，这包括三个方面的含义：<ol><li><mark>页内</mark>的记录是按照主键大小的顺序排成一个<mark>单向链表</mark>。</li><li>每个存放<mark>数据记录的页</mark>也是根据页中数据记录的主键大小顺序排成一个<mark>双向链表</mark>。</li><li>存放<mark>目录项记录的页</mark>分为不同的层次，在同一层次中的页也是根据页中目录项记录的主键大小顺序排成一个双向链表。</li></ol></li><li><code>B+树</code>的<mark>叶子节点</mark>存储的是完整的数据记录，所谓完整的数据记录，就是指这个记录中存储了所有列的值（包括隐藏列）。</li></ol><p>我们把具有这两种特性的<code>B+树</code>称为<mark>聚簇索引</mark>，所有完整的数据记录都存放在这个<mark>聚簇索引</mark>的<mark>叶子节点</mark>处。这种聚簇索引并不需要我们在MySQL语句中显式地使用<code>INDEX</code>语句去创建，<code>InnoDB</code>存储引擎会<mark>自动地</mark>为我们创建<mark>聚簇索引</mark>。</p><h5 id="%E4%BC%98%E7%82%B9" tabindex="-1">优点</h5><ul><li>数据访问更快，因为<mark>聚簇索引</mark>将索引和数据保存在同一个<code>B+树</code>中，因此从<mark>聚簇索引</mark>中获取数据比<mark>非聚簇索引</mark>更加的快。</li><li>聚簇索引对于主键的<mark>排序查找</mark>和<mark>范围查找</mark>速度非常快。</li><li>按照聚簇索引排列顺序，查询显示一定范围数据的时候，由于数据都是紧密相连，数据库不用从多个数据块中提取数据，所以<mark>节省了大量的io操作</mark>。</li></ul><h5 id="%E7%BC%BA%E7%82%B9" tabindex="-1">缺点</h5><ul><li><mark>插入速度严重依赖于插入顺序</mark>，按照主键的顺序插入是最快的方式，否则将会出现页分裂，严重影响性能。因此，对于<code>InnoDB</code>表，我们一般都会定义一个<mark>自增的ID列为主键</mark>。</li><li><mark>更新主键的代价很高</mark>，因为将会导致被更新的行移动。因此，对于InnoDB表，我们一般<mark>定义主键为不可更新</mark>。</li><li><mark>二级索引访问需要两次索引查找</mark> ，第一次找到主键值，第二次根据主键值找到行数据。</li></ul><h5 id="%E9%99%90%E5%88%B6" tabindex="-1">限制</h5><ul><li>对于MySQL数据库来说，目前只有<code>InnoDB</code>存储引擎支持聚簇索引，而<code>MyISAM</code>并不支持聚簇索引。</li><li>由于数据物理存储排序方式只能有一种，所以每个MySQL的表<mark>只能有一个聚簇索引</mark>。一般情况下就是该表的主键。</li><li>如果一个表没有定义主键，<code>InnoDB</code>会选择<mark>非空的唯一索引</mark>代替。如果没有这样的索引，<code>InnoDB</code>会隐式的定义一个主键来作为<mark>聚簇索引</mark>。</li><li>为了充分利用聚簇索引聚簇的特性，所以<code>InnoDB</code>表的主键列尽量<mark>选用有序的顺序id</mark>，而不建议用无序的id。比如<code>UUID</code>、<code>MD5</code>、<code>HASH</code>、<code>字符串列</code>作为主键从而无法保证数据的顺序增长。</li></ul><h4 id="%E4%BA%8C%E7%BA%A7%E7%B4%A2%E5%BC%95%EF%BC%88%E8%BE%85%E5%8A%A9%E7%B4%A2%E5%BC%95%E3%80%81%E9%9D%9E%E8%81%9A%E7%B0%87%E7%B4%A2%E5%BC%95%EF%BC%89" tabindex="-1">二级索引（辅助索引、非聚簇索引）</h4><blockquote><p>上面说到的<mark>聚簇索引</mark>只能在搜索条件是<mark>主键</mark>的时候才能发挥作用，因为<code>B+树</code>中的数据都是按照主键进行排序的。那如果想要以别的列作为搜索条件怎么办呢？</p></blockquote><p>我们可以多建几个B+树，不同的B+树中的数据采用不同的排序规则。比如用<code>b</code>列的大小作为数据页、页中记录的排序规则，再建立一个<code>B+树</code>，如下：</p><p><img src="/upload/2023/05/%E4%BB%A5b%E5%88%97%E4%BD%9C%E4%B8%BA%E7%B4%A2%E5%BC%95%E7%9A%84B%2B%E6%A0%91.png" alt="以b列作为索引的B+树" /></p><p>这个<code>B+树</code>和上面说到的<mark>聚簇索引</mark>有几个不一样的地方：</p><ol><li>使用记录<code>b</code>列的大小进行记录和页的排序，这包括三个方面的含义：<ol><li>页内的记录是按照<code>b</code>列的大小顺序排成一个<mark>单向链表</mark>。</li><li>每个存放<mark>数据记录的页</mark>也是根据页中记录的<code>b</code>列的大小顺序排列成一个<mark>双向链表</mark>。</li><li>存放<mark>目录项记录的页</mark>分为不同的层次，在同一层次中的页也是根据页中目录项记录的<code>b</code>列大小顺序排成一个<mark>双向链表</mark>。</li></ol></li><li><code>B+树</code>的叶子节点存放的不是完整的数据记录，而只是<code>b列+主键</code>这两个列的值。</li><li>目录项记录不再是<code>主键+页号</code>的搭配，而是变成了<code>b列+页号</code>。</li></ol><p>所以如果现在想要通过<code>b</code>列的值查找某些记录的话就可以使用上面刚刚建好的这个<code>B+树</code>了。这里以查找<code>b</code>列的值为4的记录为例，其查找过程如下：</p><ol><li><strong>确定目录项记录页</strong>：根据<code>根页面</code>，也就是<mark>页33</mark>，可以快速定位到目录项记录所在的页为<mark>页44</mark>（因为<code>2&lt;4&lt;9</code>）。</li><li><strong>通过目录项记录页确定数据记录真实所在的页</strong>：在<mark>页44</mark>中可以快速定位到实际存储数据记录的页，但是由于<code>b</code>列并没有唯一性约束，所以<code>b</code>列值为<code>4</code>的记录可能分布在多个数据页中，又因为<code>2&lt;4&lt;=4</code>，所以确定了实际的记录在<mark>页10</mark>和<mark>页47</mark>中.</li><li><strong>在实际存储数据记录的页中定位到具体的记录</strong>：在<mark>页10</mark>和<mark>页47</mark>中定位到具体的记录。</li><li><strong>回表</strong>：但是这个B+树的叶子节点中的记录只存储了<code>b</code>和<code>a</code>（主键）两个列，所以必须还要再根据主键值去聚簇索引中再查找一遍完整的数据记录。</li></ol><h5 id="%E5%9B%9E%E8%A1%A8" tabindex="-1">回表</h5><p>根据上面这个以<code>b</code>列大小排序的<code>B+树</code>只能确定要查找记录的主键值，所以如果想根据<code>b</code>列的值查找到完整的数据记录的话，仍然需要到<mark>聚簇索引</mark>中再查一遍，这个过程称为<mark>回表</mark>。也就是根据<code>b</code>列的值查询一条完整的数据记录需要使用到<mark>2个<code>B+树</code></mark>。</p><blockquote><p>为什么还需要一次回表操作呢？直接把完整的数据记录放到叶子节点不行吗？</p><p>如果把完整的数据记录放到叶子节点确实不用回表了。但是太耗费空间了，相当于每建立一个<code>B+</code>树都需要把所有的数据记录再都拷贝一遍。</p></blockquote><p>因为这种按照<code>非主键列</code>建立的<code>B+树</code>需要一次回表操作才能定位到完整的数据记录，所以这样的<code>B+树</code>才会被称为<mark>二级索引</mark>，或者<mark>辅助索引</mark>。由于使用的是<code>b</code>列的大小作为<code>B+树</code>的排序规则，所以也称这个<code>B+树</code>是为<code>b</code>列建立的索引。</p><p>非聚簇索引的存在不影响数据在聚簇索引中的组织，所以一张表可以有多个非聚簇索引。</p><h4 id="%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95" tabindex="-1">联合索引</h4><p>也可以同时以多个列的大小作为排序规则，也就是同时为多个列建立索引，比如想让<code>B+树</code>按照<code>b</code>和<code>c</code>列的大小进行排序，这个包含两层含义：</p><ul><li>先把每个记录和页按照<code>b</code>列进行排序。</li><li>在记录的<code>b</code>列相同的情况下，采用<code>c</code>列进行排序。</li></ul><p>具体可以看下面的示意图：</p><p><img src="/upload/2023/05/%E8%81%94%E5%90%88%E7%B4%A2%E5%BC%95.png" alt="联合索引" /></p><p>如图所示，需要注意下面几点：</p><ol><li>每条目录项记录都是由<code>b</code>，<code>c</code>和<code>页号</code>这三个部分组成，每条记录先按照<code>b</code>列的值排序，如果记录的<code>b</code>列相同，则按照<code>c</code>列排序。</li><li><code>B+树</code>叶子节点处的数据记录是由<code>b</code>，<code>c</code>，和主键<code>a</code>列组成的。</li></ol><blockquote><p>注意一点：以<code>b</code>和<code>c</code>列的大小为排序规则建立的<code>B+树</code>称为<mark>联合索引</mark>，本质上也是一个<mark>二级索引</mark>。但是和分别为<code>b</code>，<code>c</code>列建立一个二级索引是不同的，具体如下：</p><ul><li>建立<mark>联合索引</mark>只会建立如上图一样的<code>1个B+树</code>。</li><li>为<code>b</code>和<code>c</code>列分别建立<mark>二级索引</mark>会分别以<code>b</code>和<code>c</code>列的大小为排序规则建立<code>2个B+树</code>。</li></ul></blockquote><h3 id="innodb%E7%9A%84b%2B%E6%A0%91%E7%B4%A2%E5%BC%95%E7%9A%84%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A1%B9" tabindex="-1">InnoDB的B+树索引的注意事项</h3><h4 id="%E6%A0%B9%E9%A1%B5%E9%9D%A2%E4%B8%87%E5%B9%B4%E4%B8%8D%E5%8A%A8" tabindex="-1">根页面万年不动</h4><p>在上面介绍<code>B+树</code>索引的时候，是先把数据记录的叶子节点画出来，然后接着往上推画出存储目录项记录的内节点，但是实际上<code>B+树</code>的形成过程是下面这样的：</p><ol><li>每当为某个表创建一个<code>B+树</code>索引（聚簇索引不是人为创建的，是默认就有的）的时候，都会为这个索引创建一个<mark>根节点</mark>页面。一开始表中没有数据的时候，每个<code>B+树</code>对应的<mark>根节点</mark>中既没有数据记录，也没有目录项记录。</li><li>随后向表中插入数据记录的时候，先把数据记录存储到这个<mark>根节点</mark>中。</li><li>当<mark>根节点</mark>中的可用空间使用完之后继续插入记录，此时会将<mark>根节点</mark>中的所有记录复制到一个新分配的页，比如<mark>页a</mark>中，然后对这个新页进行<mark>页分裂</mark>操作，得到另外一个新页<mark>页b</mark>，这个时候新插入的记录根据键值（也就是聚簇索引中的主键值，二级索引中对应的索引列的值）的大小就会被分配到<mark>页a</mark>或者<mark>页b</mark>中，而<mark>根节点</mark>便升级为存储目录项记录的页。</li></ol><blockquote><p>特别注意：一个<code>B+树</code>索引的根节点从诞生开始，便不会再移动。这样只要对某个表建立一个索引，那么它的根节点的页号便会被记录到某个地方，然后凡是<code>InnoDB</code>存储引擎需要用到这个索引的时候，都会从这个固定的地方取出<mark>根节点</mark>的页号，从而使用这个索引。</p></blockquote><h4 id="%E5%86%85%E8%8A%82%E7%82%B9%E4%B8%AD%E7%9B%AE%E5%BD%95%E9%A1%B9%E8%AE%B0%E5%BD%95%E7%9A%84%E5%94%AF%E4%B8%80%E6%80%A7" tabindex="-1">内节点中目录项记录的唯一性</h4><p>上面说到了<code>B+树</code>索引的内节点中目录项记录的内容是<code>索引列+页号</code>的搭配，但是这个搭配对于二级索引来说不太严谨，还是以上面的<code>index_demo</code>表为例，假设这个表中的数据是这样的：</p><table><thead><tr><th style="text-align:center">a</th><th style="text-align:center">b</th><th style="text-align:center">c</th></tr></thead><tbody><tr><td style="text-align:center">1</td><td style="text-align:center">1</td><td style="text-align:center">‘u’</td></tr><tr><td style="text-align:center">3</td><td style="text-align:center">1</td><td style="text-align:center">‘d’</td></tr><tr><td style="text-align:center">5</td><td style="text-align:center">1</td><td style="text-align:center">‘y’</td></tr><tr><td style="text-align:center">7</td><td style="text-align:center">1</td><td style="text-align:center">‘a’</td></tr></tbody></table><p>如果二级索引中目录项记录的内容只是<code>索引列+页号</code>的话，那么为<code>b</code>列建立索引后B+树应该是如下这样：</p><p><img src="/upload/2023/04/%E5%86%85%E8%8A%82%E7%82%B9%E4%B8%AD%E7%9B%AE%E5%BD%95%E9%A1%B9%E8%AE%B0%E5%BD%95%E7%9A%84%E5%94%AF%E4%B8%80%E6%80%A7.png" alt="内节点中目录项记录的唯一性" /></p><blockquote><p>如果这个时候想插入一个记录的话，比如列<code>a</code>、<code>b</code>、<code>c</code>的值分别为<code>9</code>、<code>1</code>、<code>c</code>，那么在修改这个为<code>b</code>列建立的<code>B+树</code>的时候会出现一个大问题：由于<mark>页5</mark>中存储的目录项记录是由<code>b列+页号</code>组成的，<mark>页5</mark>中的两条目录项记录对应的<code>b</code>列的值都是<code>1</code>，而新插入的这个数据的<code>b</code>列也是<code>1</code>，那么这个数据应该放到<mark>页11</mark>还是<mark>页29</mark>中呢？</p></blockquote><p>为了解决这个问题，也就是能确定新插入的记录应该插入到哪个页中，则需要保证在<code>B+树</code>的同一层内节点的目录项记录除了页号这个字段以外是唯一的。所以对于二级索引的内节点的目录项记录的内容实际上是由三个部分构成的：</p><ol><li>索引列的值</li><li>主键值</li><li>页号</li></ol><p>也就是把<code>主键值</code>也添加到二级索引内节点的目录项记录了，这样就可以保证B+树每一层节点中每条目录项记录除页号这个字段以外是唯一的，所以为<code>b</code>列建立的二级索引实际上应该是这样子的：</p><p><img src="/upload/2023/04/%E4%BA%8C%E7%BA%A7%E7%B4%A2%E5%BC%95%E7%9B%AE%E5%BD%95%E9%A1%B9%E8%AE%B0%E5%BD%95%E5%8A%A0%E4%B8%8A%E4%B8%BB%E9%94%AE.png" alt="二级索引目录项记录加上主键" /></p><p>这样的话再插入记录（<code>9</code>，<code>1</code>，<code>'c'</code>）的时候，由于<mark>页5</mark>中存储的目录项记录都是由<code>b列+主键+页号</code>的值构成的，可以先把新纪录的<code>b</code>列的值和<mark>页5</mark>中每个目录项记录的<code>b</code>列值做比较，相同的话，接着再比较主键值，因为<code>B+树</code>同一层中不同目录项记录<code>b列+主键</code>值肯定是不一样的，所以最后肯定能定位唯一的一条目录项记录。最后，上面说到的问题，这个新加入的记录应该被插入到<mark>页29</mark>中。</p><h4 id="%E4%B8%80%E4%B8%AA%E9%A1%B5%E6%9C%80%E5%B0%91%E5%AD%98%E5%82%A82%E6%9D%A1%E8%AE%B0%E5%BD%95" tabindex="-1">一个页最少存储2条记录</h4><blockquote><p>一个<code>B+树</code>只需要很少的层级就可以轻松存储数亿条记录，查询速度相当不错，这是因为<code>B+树</code>本质上就是一个大的多层级目录，每经过一个目录时都会过滤掉许多无效的子目录，直到最后访问到存储真实数据的目录。那如果一个大的目录中只存放一个子目录是什么样的效果呢？</p></blockquote><p>也就是目录层级会非常之多，而且最后的那个存放真实数据的目录中只能存放一条记录。所以<code>InnoDB</code>的一个数据页至少可以存放两条记录。</p><h2 id="myisam%E4%B8%AD%E7%9A%84%E7%B4%A2%E5%BC%95%E6%96%B9%E6%A1%88" tabindex="-1">MyISAM中的索引方案</h2><blockquote><p><code>B树</code>索引适用的存储引擎如下：</p><table><thead><tr><th style="text-align:center">索引/存储引擎</th><th style="text-align:center">MyISAM</th><th style="text-align:center">InnoDB</th><th style="text-align:center">Memory</th></tr></thead><tbody><tr><td style="text-align:center">B-Tree索引</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr></tbody></table><p>即使多个存储引擎支持同一种类型的索引，但是它们的实现原理也是不一样的。<code>InnoDB</code>和<code>MyISAM</code>默认索引是<code>B树</code>索引，而<code>Memory</code>默认的索引是Hash索引。<code>MyISAM</code>引擎适用<code>B+树</code>作为索引的结构，叶子节点的<code>data域</code>存放的是数据记录的地址。</p></blockquote><h3 id="myisam%E7%B4%A2%E5%BC%95%E7%9A%84%E5%8E%9F%E7%90%86" tabindex="-1">MyISAM索引的原理</h3><p>上面我们说到了<code>InnoDB</code>中<mark>索引即数据</mark>，也就是说聚簇索引的那个<code>B+树</code>的叶子节点中已经把所有完整的数据记录都包含了，而<code>MyISAM</code>的索引方案虽然也是使用的树形结构，但是其<mark>索引和数据却是分开存储</mark>的：</p><ul><li>将表中的记录按照<mark>记录的插入顺序</mark>单独存储在一个文件中，其被称为数据文件。这个文件并不划分为若干个数据页，而是有多少记录就往这个文件中存多少记录就可以了。由于插入数据的时候并<mark>没有刻意地按照主键大小排序</mark>，所以并不能在这些数据上使用二分法进行查找。</li><li>使用<code>MyISAM</code>存储引擎地表会把索引信息另外存储到一个称为索引文件的另外一个文件中。<code>MyISAM</code>会单独为表的主键创建一个索引，只不过在索引的叶子节点中存储的不是完整的数据记录，而是<code>主键值+数据记录地址</code>的组合。</li></ul><p>下面是MyISAM索引的原理图：</p><p><img src="/upload/2023/04/MyISAM%E7%B4%A2%E5%BC%95%E5%8E%9F%E7%90%86.jpg" alt="MyISAM索引原理" /></p><p>这里假设表一共由三列，假设<code>Col1</code>是主键，上图就是<code>MyISAM</code>表的主索引，从图中可以看出<code>MyISAM</code>的索引文件仅仅保存数据记录的地址。在<code>MyISAM</code>中，<mark>主键索引</mark>和<mark>二级索引</mark>在结构上没有任何的区别，只是<mark>主键索引</mark>要求<code>key是唯一的</code>，而<mark>二级索引</mark>的<code>key则可以重复</code>。如果在<code>Col2</code>上建立一个二级索引，则结构如下：</p><p><img src="/upload/2023/04/MyISAM%E4%BA%8C%E7%BA%A7%E7%B4%A2%E5%BC%95.jpg" alt="MyISAM二级索引" /></p><p>同样都是一个<code>B+树</code>，<code>data域</code>保存数据记录的地址。因此<code>MyISAM</code>中索引检索的算法是：</p><ol><li>首先按照<code>B+树</code>搜索算法搜索索引，如果指定的<code>Key</code>存在，则取出其<code>data域</code>的值，然后用<code>data域</code>的值为地址，读取相应的数据记录。</li></ol><h3 id="myisam%E5%92%8Cinnodb%E5%AF%B9%E6%AF%94" tabindex="-1">MyISAM和InnoDB对比</h3><p><code>MyISAM</code>的索引方式都是<mark>非聚簇</mark>的，和<code>InnoDB</code>包含1个<mark>聚簇索引</mark>是不同的。两种引擎中索引的区别：</p><ol><li>在<code>InnoDB</code>存储引擎中，我们只需要根据主键值对<mark>聚簇索引</mark>进行一次查找就能找到对应的记录，而在<code>MyISAM</code>中却需要进行一次<mark>回表</mark>操作，意味着<code>MyISAM</code>中建立的索引相当于全部都是<mark>二级索引</mark>。</li><li><code>InnoDB</code>的数据文件本身就是索引文件，而<code>MyISAM</code>索引文件和数据文件是<mark>分离的</mark> ，索引文件仅保存数据记录的地址。</li><li><code>InnoDB</code>的<mark>非聚簇索引</mark>的<code>data域</code>存储相应记录主键的值，而<code>MyISAM</code>索引记录的是<mark>地址</mark>。换句话说，<code>InnoDB</code>的所有<mark>非聚簇索引</mark>都引用主键作为<code>data域</code>。</li><li><code>MyISAM</code>的<mark>回表</mark>操作是十分快速的，因为是拿着地址偏移量直接到文件中取数据的，反观<code>InnoDB</code>是通过获取主键之后再去聚簇索引里找记录，虽然说也不慢，但还是比不上直接用地址去访问。</li><li><code>InnoDB</code>要求表必须有主键，而<code>MyISAM</code>可以没有。如果没有显式指定，则<strong>MySQL</strong>系统会自动选择一个<mark>可以非空且唯一标识数据记录的列作为主键</mark>。如果不存在这种列，则<strong>MySQL</strong>自动为<code>InnoDB</code>表生成一个隐含字段作为主键，这个字段长度为<code>6个字节，类型为长整型</code>。</li></ol><h3 id="%E6%80%BB%E7%BB%93" tabindex="-1">总结</h3><p>通过上面的了解，我们知道了InnoDB的索引实现之后：</p><ol><li><strong>举例1</strong>：很容易明白<mark>为什么不建议使用过长的字段作为主键</mark>，因为所有二级索引都引用主键索引，过长的主键索引会使二级索引变得非常大。</li><li><strong>举例2</strong>：用非单调的字段作为主键在<code>InnoDB</code>中不是个好主意，因为<code>InnoDB</code>数据文件本身是一个<code>B+树</code>，非单调的主键会造成在插入新纪录的时候，数据文件为了维持<code>B+树</code>的特性而频繁的分裂调整，十分低效，所以<mark>使用自增字段作为主键却是一个很好的选择</mark>。</li></ol><h2 id="%E7%B4%A2%E5%BC%95%E7%9A%84%E4%BB%A3%E4%BB%B7" tabindex="-1">索引的代价</h2><p>索引虽然是个好东西，但是并不能随便建，它在空间和时间上都有一定的消耗：</p><ol><li><strong>空间上的代价</strong>：每建立一个索引都要为它建立一个<code>B+树</code>，每一个<code>B+树</code>的每一个节点都是一个数据页，一个页默认会占用<code>16kb</code>的存储空间，一个很大的<code>B+树</code>由许多数据页组成，也就是很大的一片存储空间。</li><li><strong>时间上的代价</strong>：每次对表中的数据进行<code>增</code>、<code>删</code>、<code>改</code>操作时。都需要修改每个<code>B+树</code>索引。且上面说到了<code>B+树</code>的每一层节点都是按照索引列值<mark>从小到大顺序排序</mark>组成的<mark>双向链表</mark>。不论是叶子节点中的记录，还是内节点中的记录都是按照索引列的值从小到大的顺序形成的<mark>单向链表</mark>。而<code>增</code>、<code>删</code>、<code>改</code>操作可能会对节点和记录的排序造成破坏，所以存储引擎需要额外的时间进行一些<code>记录移位</code>、<code>页面分裂</code>、<code>页面回收</code>等操作来维护好节点和记录的排序。如果我们建立了许多索引，每个索引对应的<code>B+树</code>都要进行相关的维护操作，会给性能拖后腿。</li></ol><h2 id="mysql%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E9%80%89%E6%8B%A9%E7%9A%84%E5%90%88%E7%90%86%E6%80%A7" tabindex="-1">MySQL数据结构选择的合理性</h2><p>从MySQL的角度来说，不得不考虑的一个现实问题就是<code>磁盘I/O</code>。如果能让索引的数据结构尽量减少硬盘的I/O操作，所消耗的时间也就越小。可以说磁盘的I/O操作次数对索引的使用效率至关重要。</p><p>查找都是索引操作，一般来说索引非常大，尤其是关系型数据库，当数据量比较大的时候，索引的大小有可能几个G甚至更多，为了减少索引在内存中的占用，<mark>数据库索引是存储在外部磁盘上的</mark>。当利用索引查询的时候，把索引全部加载到内存是不现实的，所以只能逐一加载，那么MySQL衡量查询效率的标准就是<code>磁盘IO次数</code>。</p><h3 id="hash%E7%BB%93%E6%9E%84" tabindex="-1">Hash结构</h3><p>Hash本身是一个函数，又被称为散列函数，它可以帮助我们大幅提升检索数据的效率。Hash算法是通过某种确定性的算法（比如<code>MD5</code>、<code>SHA1</code>、<code>SHA2</code>、<code>SHA3</code>）将输入变成输出。<mark>相同的输入永远可以得到相同的输出</mark>，假设输入内容有微小偏差。在输出中通常会有不同的结果。比如想要验证两个文件是否相同，那么不需要把两份文件拿来对比，只需要用Hash函数计算出对应的结果，然后对比两个文件的计算结果即可。</p><h4 id="%E5%8A%A0%E9%80%9F%E6%9F%A5%E8%AF%A2%E9%80%9F%E5%BA%A6%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%EF%BC%8C%E5%B8%B8%E8%A7%81%E7%9A%84%E6%9C%89%E4%B8%A4%E7%B1%BB%EF%BC%9A" tabindex="-1">加速查询速度的数据结构，常见的有两类：</h4><ul><li>树：比如平衡二叉搜索树。查询、插入、修改、删除的平均时间复杂度都是<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>l</mi><mi>o</mi><msubsup><mi>g</mi><mn>2</mn><mi>N</mi></msubsup><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(log_2^N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0913309999999998em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">o</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8413309999999999em;"><span style="top:-2.4518920000000004em;margin-left:-0.03588em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.24810799999999997em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span>。</li><li>哈希：比如HashMap，查询、插入、修改、删除的平均时间复杂度都是<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mn>1</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(1)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord">1</span><span class="mclose">)</span></span></span></span>。</li></ul><p>采用<code>Hash</code>进行查询的效率非常高，基本上一次查询就可以找到数据，而<code>B+树</code>需要自顶向下一次查找，多次访问节点才能找到数据，中间需要多次IO操作，从效率来说<code>Hash</code>比<code>B+树</code>更快。</p><blockquote><p>既然<code>Hash</code>结构的效率这么高，那为什么MySQL的索引结构还要设计成树形的呢？</p></blockquote><ol><li><code>Hash</code>索引仅能满足（<code>=</code>、<code>&lt;</code>、<code>&gt;</code>、<code>IN</code>）查询。如果进行范围查询，哈希型的索引，时间复杂度会退化为<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>N</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.10903em;">N</span><span class="mclose">)</span></span></span></span>，而树形的<mark>有序</mark>特性，依然可以保持<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>O</mi><mo stretchy="false">(</mo><mi>l</mi><mi>o</mi><msubsup><mi>g</mi><mn>2</mn><mi>N</mi></msubsup><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">O(log_2^N)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.0913309999999998em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.02778em;">O</span><span class="mopen">(</span><span class="mord mathnormal" style="margin-right:0.01968em;">l</span><span class="mord mathnormal">o</span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">g</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.8413309999999999em;"><span style="top:-2.4518920000000004em;margin-left:-0.03588em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight" style="margin-right:0.10903em;">N</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.24810799999999997em;"><span></span></span></span></span></span></span><span class="mclose">)</span></span></span></span>的高效率。</li><li><code>Hash</code>索引还有一个缺陷，数据的存储是没有顺序的，在<code>ORDER BY</code>的情况下，使用Hash索引还需要对数据重新排序。</li><li>对于联合索引的情况，<code>Hash</code>值是将联合索引键合并之后一起计算的，无法对单独的一个键或者几个索引键进行查询。</li><li>对于等值查询来说，通常<code>Hash</code>索引的效率更高，不过也存在一种情况，就是索引列的重复值如果很多（Hash冲突），效率就会降低。这是因为遇到Hash冲突时，需要遍历桶中的行指针来进行比较，找到查询的关键字，非常耗时。所以Hash索引通常不会用到重复值多的列上，比如列为性别、年龄的情况等。</li></ol><h4 id="hash%E7%B4%A2%E5%BC%95%E9%80%82%E7%94%A8%E7%9A%84%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E" tabindex="-1">Hash索引适用的存储引擎</h4><table><thead><tr><th style="text-align:center">索引/存储引擎</th><th style="text-align:center">MyISAM</th><th style="text-align:center">InnoDB</th><th style="text-align:center">Memory</th></tr></thead><tbody><tr><td style="text-align:center">Hash索引</td><td style="text-align:center">❌</td><td style="text-align:center">❌</td><td style="text-align:center">✅</td></tr></tbody></table><h4 id="hash%E7%B4%A2%E5%BC%95%E7%9A%84%E9%80%82%E7%94%A8%E6%80%A7" tabindex="-1">Hash索引的适用性</h4><p><code>Hash</code>索引存在着很多的限制，相比之下在数据库中<code>B+树</code>索引的使用面会更广，不过也有一些场景采用<code>Hash</code>索引效率更高，比如在键值型（<code>key-value</code>）数据库中（<code>Redis</code>存储的核心就是<code>Hash</code>表）。</p><p>MySQL中的Memory存储引擎支持Hash索引，如果我需要用到查询临时表时，就可以选择Memory存储引擎，把某个字段设置为Hash索引，比如字符串类型的字段，进行Hash计算后长度可以缩短到几个字节。当字段的重复度低，而且经常需要进行等值查询的时候，采用Hash索引是个不错的选择。</p><p>另外<code>InnoDB</code>本身不支持<code>Hash</code>索引，但是提供<code>自适应Hash索引</code>（<code>Adaptive Hash Index</code>）。如果一个数据经常被访问，当满足一定条件时候，就会将这个数据页的地址存放到<code>Hash</code>表中。这样下次查询的时候，就可以直接找到这个页面所在的位置。这样让<code>B+树</code>也具备了一定的<code>Hash</code>索引的优点。</p><p>采用自适应Hash索引的目的是方便根据SQL的查询条件加速定位到叶子节点，特别是B+树比较深的时候，通过自适应Hash索引可以明显提高数据的检索效率。</p><p>可以通过<code>innodb_adaptive_hash_index</code>变量来查看是否开启了自适应Hash索引，比如：</p><pre><code class="language-mysql">mysql&gt; SHOW variables LIKE &#39;%adaptive_hash_index&#39;;+----------------------------+-------+| Variable_name              | Value |+----------------------------+-------+| innodb_adaptive_hash_index | ON    |+----------------------------+-------+1 row in set (0.01 sec)</code></pre><h3 id="%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91" tabindex="-1">二叉搜索树</h3><p>//TODO</p>]]>
                    </description>
                    <pubDate>Sun, 21 Aug 2022 00:06:14 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[MySQL-存储引擎]]>
                    </title>
                    <link>https://zygzyg.com/archives/81ec4eee933741e0962c3b448b4d1955</link>
                    <description>
                            <![CDATA[<h1 id="mysql-%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E" tabindex="-1">MySQL-存储引擎</h1><blockquote><p>在<a href="/archives/634afbb608bc4c348ee1c1a66ad73922">《MySQL-逻辑架构》</a>这篇文章中我们知道，MySQL服务端主要有查询缓存、语法解析、连接池、优化器这写模块。执行SQL时，是按照优化器生成的执行计划来执行的。实际上是根据这个计划调用<strong>存储引擎</strong>的API来执行的，最后返回结果就行了。</p></blockquote><p>MySQL中有存储引擎这样一个概念，简单来说就是指表的类型。存储引擎在以前都是叫做表处理器，只是后来才改名叫做存储引擎。其主要功能就是接收命令，然后对表进行读写操作。这里以<code>MySQL8.0.31</code>为例，我们可以使用<code>SHOW engines;</code>来查看MySQL中的存储引擎。</p><pre><code class="language-mysql">mysql&gt; SHOW engines;+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+| ndbcluster         | NO      | Clustered, fault-tolerant tables                               | NULL         | NULL | NULL       || FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       || MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         || InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        || PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         || MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         || ndbinfo            | NO      | MySQL Cluster system information storage engine                | NULL         | NULL | NULL       || MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         || BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         || CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         || ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+11 rows in set (0.00 sec)</code></pre><h2 id="%E8%AE%BE%E7%BD%AE%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E" tabindex="-1">设置存储引擎</h2><h3 id="%E8%AE%BE%E7%BD%AE%E7%B3%BB%E7%BB%9F%E9%BB%98%E8%AE%A4%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E" tabindex="-1">设置系统默认存储引擎</h3><p>我们可以使用下面两个命令来查看默认的存储引擎：</p><ul><li><p><code>SHOW variables LIKE '%storage_engine%';</code>：</p><pre><code class="language-mysql">mysql&gt; SHOW variables LIKE &#39;%storage_engine%&#39;;+---------------------------------+-----------+| Variable_name                   | Value     |+---------------------------------+-----------+| default_storage_engine          | InnoDB    || default_tmp_storage_engine      | InnoDB    || disabled_storage_engines        |           || internal_tmp_mem_storage_engine | TempTable |+---------------------------------+-----------+4 rows in set (0.00 sec)</code></pre></li><li><p><code>SELECT @@default_storage_engine;</code>：</p><pre><code class="language-mysql">mysql&gt; SELECT @@default_storage_engine;+--------------------------+| @@default_storage_engine |+--------------------------+| InnoDB                   |+--------------------------+1 row in set (0.00 sec)</code></pre></li></ul><p>如果在创建表的语句中没有显式指定表的存储引擎的话，那就会默认使用<strong>InnoDB</strong>作为表的存储引擎。 如果我们想改变表的默认存储引擎的话，可以使用下面的命令：</p><pre><code class="language-mysql">mysql&gt; SET DEFAULT_STORAGE_ENGINE=MyISAM;Query OK, 0 rows affected (0.00 sec)mysql&gt; SELECT @@default_storage_engine;+--------------------------+| @@default_storage_engine |+--------------------------+| MyISAM                   |+--------------------------+1 row in set (0.00 sec)</code></pre><p>或者修改配置文件：</p><pre><code class="language-ini">default-storage-engine=MyISAM</code></pre><blockquote><p>修改配置文件后需要重启MySQL。</p></blockquote><h3 id="%E8%AE%BE%E7%BD%AE%E8%A1%A8%E7%9A%84%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E" tabindex="-1">设置表的存储引擎</h3><blockquote><p>存储引擎是负责对表中的数据进行提取和写入工作的，我们可以为不同的表设置不同的存储引擎 ，也就是说不同的表可以有不同的物理存储结构，不同的提取和写入方式。</p></blockquote><h4 id="%E5%88%9B%E5%BB%BA%E8%A1%A8%E6%97%B6%E6%8C%87%E5%AE%9A%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E" tabindex="-1">创建表时指定存储引擎</h4><p>当我们创建表的时候没有指定表的存储引擎，那么MySQL的表默认会使用<strong>InnoDB</strong>。但是如果想要指定存储引擎则可以这样写：</p><pre><code class="language-sql">CREATE TABLE 表名(建表语句;) ENGINE = 存储引擎名称;</code></pre><h4 id="%E4%BF%AE%E6%94%B9%E8%A1%A8%E7%9A%84%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E" tabindex="-1">修改表的存储引擎</h4><p>对于已经创建好的表，则可以这样修改表的存储引擎：</p><pre><code class="language-sql">ALTER TABLE 表名 ENGINE = 存储引擎名称;</code></pre><h2 id="%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E%E7%AE%80%E4%BB%8B" tabindex="-1">存储引擎简介</h2><blockquote><p>前面我们看到了一堆的存储引擎，它们分别有什么作用呢？这里简单介绍一下。</p></blockquote><ol><li><code>InnoDB</code>引擎：具备外键支持功能的事务存储引擎。</li><li><code>MyISAM</code>引擎：主要的非事务处理存储引擎。</li><li><code>Archive</code>引擎：用于数据存档。</li><li><code>Blackhole</code>引擎：丢弃写操作，读操作会返回空内容。</li><li><code>CSV</code>引擎：存储数据时，以逗号分隔各个数据项。</li><li><code>Memory</code>引擎：置于内存的表。</li><li><code>Federated</code>引擎：访问远程表。</li><li><code>Merge</code>引擎：管理多个<code>MyISAM</code>表构成的表集合。</li><li><code>NDB</code>引擎：MySQL集群专用存储引擎。</li></ol><h3 id="%E5%BC%95%E6%93%8E%E5%AF%B9%E6%AF%94" tabindex="-1">引擎对比</h3><blockquote><p>MySQL中同一个数据库，不同的表可以选择不同的存储引擎。如下表对常用存储引擎做出了对比：</p></blockquote><table><thead><tr><th style="text-align:center">特点</th><th style="text-align:center">MyISAM</th><th style="text-align:center">InnoDB</th><th style="text-align:center">MEMORY</th><th style="text-align:center">MERGE</th><th style="text-align:center">NDB</th></tr></thead><tbody><tr><td style="text-align:center">存储限制</td><td style="text-align:center">✅</td><td style="text-align:center"><code>64TB</code></td><td style="text-align:center">✅</td><td style="text-align:center">❌</td><td style="text-align:center">✅</td></tr><tr><td style="text-align:center">事务安全</td><td style="text-align:center"></td><td style="text-align:center">✅</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">锁机制</td><td style="text-align:center"><mark>表锁</mark></td><td style="text-align:center"><mark>行锁</mark></td><td style="text-align:center"><mark>表锁</mark></td><td style="text-align:center"><mark>表锁</mark></td><td style="text-align:center"><mark>行锁</mark></td></tr><tr><td style="text-align:center">B树索引</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr><tr><td style="text-align:center">哈希索引</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center">✅</td><td style="text-align:center"></td><td style="text-align:center">✅</td></tr><tr><td style="text-align:center">全文索引</td><td style="text-align:center">✅</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">集群索引</td><td style="text-align:center"></td><td style="text-align:center">✅</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">数据缓存</td><td style="text-align:center"></td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center"></td><td style="text-align:center">✅</td></tr><tr><td style="text-align:center">索引缓存</td><td style="text-align:center">只缓存索引，<br>不缓存真实数据</td><td style="text-align:center">不仅缓存索引还要缓存真实数据，<br>对内存要求较高，<br/>且内存大小对性能有决定性的影响</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr><tr><td style="text-align:center">数据可压缩</td><td style="text-align:center">✅</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr><tr><td style="text-align:center">空间使用</td><td style="text-align:center">低</td><td style="text-align:center">高</td><td style="text-align:center"></td><td style="text-align:center">低</td><td style="text-align:center">低</td></tr><tr><td style="text-align:center">内存使用</td><td style="text-align:center">低</td><td style="text-align:center">高</td><td style="text-align:center">中</td><td style="text-align:center">低</td><td style="text-align:center">高</td></tr><tr><td style="text-align:center">批量插入的速度</td><td style="text-align:center">高</td><td style="text-align:center">低</td><td style="text-align:center">高</td><td style="text-align:center">高</td><td style="text-align:center">高</td></tr><tr><td style="text-align:center">外 键</td><td style="text-align:center"></td><td style="text-align:center">✅</td><td style="text-align:center"></td><td style="text-align:center"></td><td style="text-align:center"></td></tr></tbody></table><h3 id="myisam%E5%92%8Cinnodb" tabindex="-1">MyISAM和InnoDB</h3><blockquote><p>MyISAM和InnoDB到底选择哪个比较好呢？</p></blockquote><p>MySQL5.5之前的默认存储引擎是MyISAM，之后改为了InnoDB。下面让我们对比下二者：</p><table><thead><tr><th style="text-align:center"></th><th style="text-align:center">MyISAM</th><th style="text-align:center">InnoDB</th></tr></thead><tbody><tr><td style="text-align:center">外键</td><td style="text-align:center">❌</td><td style="text-align:center">✅</td></tr><tr><td style="text-align:center">事务</td><td style="text-align:center">❌</td><td style="text-align:center">✅</td></tr><tr><td style="text-align:center">行表锁</td><td style="text-align:center">表锁：<br>即使操作一条记录也会锁住 整个表，不适合高并发的操作</td><td style="text-align:center">行锁：<br/>操作时只锁某一行，不对其它行有影响， 适合高并发的操作</td></tr><tr><td style="text-align:center">缓存</td><td style="text-align:center">只缓存索引，不缓存真实数据</td><td style="text-align:center">不仅缓存索引还要缓存真实数据，对内存要求较高，而且内存大小对性能有决定性的影响</td></tr><tr><td style="text-align:center">自带系统表使用</td><td style="text-align:center">✅</td><td style="text-align:center">❌</td></tr><tr><td style="text-align:center">特点</td><td style="text-align:center">性能：节省资源、消耗少、简单业务</td><td style="text-align:center">事务：并发写、事务、更大资源</td></tr><tr><td style="text-align:center">默认安装</td><td style="text-align:center">✅</td><td style="text-align:center">✅</td></tr><tr><td style="text-align:center">默认使用</td><td style="text-align:center">❌</td><td style="text-align:center">✅</td></tr></tbody></table>]]>
                    </description>
                    <pubDate>Tue, 16 Aug 2022 23:25:29 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[MySQL-SQL执行流程]]>
                    </title>
                    <link>https://zygzyg.com/archives/993fa080c9f4496997b84956337c3d63</link>
                    <description>
                            <![CDATA[<h1 id="mysql-sql%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B" tabindex="-1">MySQL-SQL执行流程</h1><blockquote><p>在<a href="/archives/634afbb608bc4c348ee1c1a66ad73922">《MySQL-逻辑架构》</a>这篇文章中说到了MySQL的逻辑架构，那么MySQL的SQL执行流程是怎么样的呢？</p></blockquote><p>从MySQL的逻辑架构可以看出MySQL中的SQL执行流程可以是这样的：</p><div class="mermaid"><svg id="render755929009" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="1208" style="max-width: 709.25px;" viewBox="0 0 709.25 1208"><style>#render755929009 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render755929009 .error-icon{fill:#a5a7bb;}#render755929009 .error-text{fill:#5a5844;stroke:#5a5844;}#render755929009 .edge-thickness-normal{stroke-width:2px;}#render755929009 .edge-thickness-thick{stroke-width:3.5px;}#render755929009 .edge-pattern-solid{stroke-dasharray:0;}#render755929009 .edge-pattern-dashed{stroke-dasharray:3;}#render755929009 .edge-pattern-dotted{stroke-dasharray:2;}#render755929009 .marker{fill:#F8B229;stroke:#F8B229;}#render755929009 .marker.cross{stroke:#F8B229;}#render755929009 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render755929009 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render755929009 .cluster-label text{fill:#5a5844;}#render755929009 .cluster-label span{color:#5a5844;}#render755929009 .label text,#render755929009 span{fill:#fff;color:#fff;}#render755929009 .node rect,#render755929009 .node circle,#render755929009 .node ellipse,#render755929009 .node polygon,#render755929009 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render755929009 .node .label{text-align:center;}#render755929009 .node.clickable{cursor:pointer;}#render755929009 .arrowheadPath{fill:undefined;}#render755929009 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render755929009 .flowchart-link{stroke:#F8B229;fill:none;}#render755929009 .edgeLabel{background-color:#006100;text-align:center;}#render755929009 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render755929009 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render755929009 .cluster text{fill:#5a5844;}#render755929009 .cluster span{color:#5a5844;}#render755929009 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render755929009 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 8)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"><g class="cluster default" id="Cache"><rect style="" rx="0" ry="0" x="8" y="111" width="460.5" height="195"></rect><g class="cluster-label" transform="translate(205.25, 116)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">查询缓存</tspan></text></g></g></g><g class="edgePaths"><path d="M198.54247104247105,236.29247104247105L179.07705920205922,247.91039253539256C159.61164736164736,259.528314028314,120.68082368082368,282.76415701415704,101.21541184041185,300.1320785070785C81.75,317.5,81.75,329,81.75,349.1666666666667C81.75,369.3333333333333,81.75,398.1666666666667,81.75,425.4166666666667C81.75,452.6666666666667,81.75,478.3333333333333,81.75,498.1666666666667C81.75,518,81.75,532,81.75,546C81.75,560,81.75,574,81.75,593.8333333333334C81.75,613.6666666666666,81.75,639.3333333333334,81.75,666.5833333333334C81.75,693.8333333333334,81.75,722.6666666666666,81.75,755.6666666666666C81.75,788.6666666666666,81.75,825.8333333333334,81.75,861.4166666666666C81.75,897,81.75,931,81.75,955C81.75,979,81.75,993,81.75,1007C81.75,1021,81.75,1035,101.91666666666667,1047.2445820433436C122.08333333333333,1059.4891640866872,162.41666666666666,1069.9783281733746,182.58333333333334,1075.2229102167182L202.75,1080.4674922600618" id="L-IfCache-返回结果-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-IfCache LE-返回结果" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M404.75,1024L404.75,1028.1666666666667C404.75,1032.3333333333333,404.75,1040.6666666666667,384.5833333333333,1050.0779153766769C364.4166666666667,1059.4891640866872,324.0833333333333,1069.9783281733746,303.9166666666667,1075.2229102167182L283.75,1080.4674922600618" id="L-缓存结果-返回结果-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-缓存结果 LE-返回结果" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M243.25,1108L243.25,1112.1666666666667C243.25,1116.3333333333333,243.25,1124.6666666666667,243.25,1133C243.25,1141.3333333333333,243.25,1149.6666666666667,243.25,1153.8333333333333L243.25,1158" id="L-返回结果-End-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-返回结果 LE-End" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M243.25,42L243.25,47.75C243.25,53.5,243.25,65,243.25,76.5C243.25,88,243.25,99.5,243.25,105.25L243.25,111" id="L-Start-Cache-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Start LE-Cache" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M315.75,252.26934984520125L330.5833333333333,261.2244582043344C345.4166666666667,270.1795665634675,375.0833333333333,288.0897832817338,389.9166666666667,302.79489164086687C404.75,317.5,404.75,329,404.75,340.5C404.75,352,404.75,363.5,404.75,369.25L404.75,375" id="L-IfCache-解析器-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-IfCache LE-解析器" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M404.75,479L404.75,483.1666666666667C404.75,487.3333333333333,404.75,495.6666666666667,404.75,504C404.75,512.3333333333334,404.75,520.6666666666666,404.75,524.8333333333334L404.75,529" id="L-解析器-查询优化器-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-解析器 LE-查询优化器" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M404.75,563L404.75,567.1666666666666C404.75,571.3333333333334,404.75,579.6666666666666,404.75,588C404.75,596.3333333333334,404.75,604.6666666666666,404.75,608.8333333333334L404.75,613" id="L-查询优化器-执行-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-查询优化器 LE-执行" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M404.75,717L404.75,722.75C404.75,728.5,404.75,740,404.75,751.5C404.75,763,404.75,774.5,404.75,780.25L404.75,786" id="L-执行-engine-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-执行 LE-engine" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M404.75,940L404.75,944.1666666666666C404.75,948.3333333333334,404.75,956.6666666666666,404.75,965C404.75,973.3333333333334,404.75,981.6666666666666,404.75,985.8333333333334L404.75,990" id="L-engine-缓存结果-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-engine LE-缓存结果" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel" transform="translate(81.75, 665)"><g class="label" transform="translate(-16.5, -9.5)"><rect rx="0" ry="0" width="33" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">存在</tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(243.25, 76.5)"><g class="label" transform="translate(-13.75390625, -9.5)"><rect rx="0" ry="0" width="27.5078125" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">SQL</tspan></text></g></g><g class="edgeLabel" transform="translate(404.75, 340.5)"><g class="label" transform="translate(-24.5, -9.5)"><rect rx="0" ry="0" width="49" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">不存在</tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(404.75, 751.5)"><g class="label" transform="translate(-43.90625, -9.5)"><rect rx="0" ry="0" width="87.8125" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">API接口查询</tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="root" transform="translate(154.828125, 778)"><g class="clusters"><g class="cluster default" id="engine"><rect style="" rx="0" ry="0" x="8" y="8" width="484.84375" height="154"></rect><g class="cluster-label" transform="translate(217.421875, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">存储引擎</tspan></text></g></g></g><g class="edgePaths"><path d="M368.84375,85L373.0104166666667,85C377.1770833333333,85,385.5104166666667,85,393.84375,85C402.1770833333333,85,410.5104166666667,85,414.6770833333333,85L418.84375,85" id="L-engines-数据-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-engines LE-数据" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="root" transform="translate(25.5, 35)"><g class="clusters"><g class="cluster default" id="engines"><rect style="" rx="0" ry="0" x="8" y="8" width="335.84375" height="84"></rect><g class="cluster-label" transform="translate(175.921875, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="edgePaths"></g><g class="edgeLabels"></g><g class="nodes"><g class="node default default" id="flowchart-MyISAM-419" transform="translate(77.4140625, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-34.4140625" y="-17" width="68.828125" height="34"></rect><g class="label" style="" transform="translate(-26.9140625, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">MyISAM</tspan></text></g></g><g class="node default default" id="flowchart-InnoDB-420" transform="translate(194.0234375, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-32.1953125" y="-17" width="64.390625" height="34"></rect><g class="label" style="" transform="translate(-24.6953125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">InnoDB</tspan></text></g></g><g class="node default default" id="flowchart-other-421" transform="translate(292.53125, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-16.3125" y="-17" width="32.625" height="34"></rect><g class="label" style="" transform="translate(-8.8125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">...</tspan></text></g></g></g></g><g class="node default default" id="flowchart-数据-423" label-offset-y="7.040229885057471" transform="translate(443.34375, 85)"><path style="" d="M 0,7.040229885057471 a 24.5,7.040229885057471 0,0,0 49 0 a 24.5,7.040229885057471 0,0,0 -49 0 l 0,41.04022988505747 a 24.5,7.040229885057471 0,0,0 49 0 l 0,-41.04022988505747" transform="translate(-24.5,-27.560344827586206)"></path><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">数据</tspan></text></g></g></g></g><g class="root" transform="translate(233.75, 605)"><g class="clusters"><g class="cluster default" id="执行"><rect style="" rx="0" ry="0" x="8" y="8" width="327" height="104"></rect><g class="cluster-label" transform="translate(154.5, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">执行</tspan></text></g></g></g><g class="edgePaths"><path d="M130.5,60.5L137.41666666666666,60.416666666666664C144.33333333333334,60.333333333333336,158.16666666666666,60.166666666666664,169.25,60.083333333333336C180.33333333333334,60,188.66666666666666,60,192.83333333333334,60L197,60" id="L-执行计划-查询执行引擎-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-执行计划 LE-查询执行引擎" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-查询执行引擎-416" transform="translate(253.5, 60)"><rect class="basic label-container" style="" rx="0" ry="0" x="-56.5" y="-17" width="113" height="34"></rect><g class="label" style="" transform="translate(-49, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">查询执行引擎</tspan></text></g></g><g class="node default default" id="flowchart-执行计划-415" transform="translate(90, 60)"><polygon points="-17,0 80,0 80,-34 -17,-34 0,-17" class="label-container" transform="translate(-40,17)" style=""></polygon><g class="label" style="" transform="translate(-32.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">执行计划</tspan></text></g></g></g></g><g class="root" transform="translate(109.25, 367)"><g class="clusters"><g class="cluster default" id="解析器"><rect style="" rx="0" ry="0" x="8" y="8" width="576" height="104"></rect><g class="cluster-label" transform="translate(271, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">解析器</tspan></text></g></g></g><g class="edgePaths"><path d="M114,60L118.16666666666667,60C122.33333333333333,60,130.66666666666666,60,141.91666666666666,60.083333333333336C153.16666666666666,60.166666666666664,167.33333333333334,60.333333333333336,174.41666666666666,60.416666666666664L181.5,60.5" id="L-语法解析-解析树-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-语法解析 LE-解析树" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M246.5,60.5L253.41666666666666,60.416666666666664C260.3333333333333,60.333333333333336,274.1666666666667,60.166666666666664,285.25,60.083333333333336C296.3333333333333,60,304.6666666666667,60,308.8333333333333,60L313,60" id="L-解析树-预处理器-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-解析树 LE-预处理器" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M394,60L398.1666666666667,60C402.3333333333333,60,410.6666666666667,60,421.9166666666667,60.083333333333336C433.1666666666667,60.166666666666664,447.3333333333333,60.333333333333336,454.4166666666667,60.416666666666664L461.5,60.5" id="L-预处理器-新解析树-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-预处理器 LE-新解析树" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-新解析树-411" transform="translate(501.5, 60)"><polygon points="-17,0 81,0 81,-34 -17,-34 0,-17" class="label-container" transform="translate(-40.5,17)" style=""></polygon><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">新解析树</tspan></text></g></g><g class="node default default" id="flowchart-语法解析-408" transform="translate(73.5, 60)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">语法解析</tspan></text></g></g><g class="node default default" id="flowchart-解析树-409" transform="translate(213.5, 60)"><polygon points="-17,0 65,0 65,-34 -17,-34 0,-17" class="label-container" transform="translate(-32.5,17)" style=""></polygon><g class="label" style="" transform="translate(-25, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">解析树</tspan></text></g></g><g class="node default default" id="flowchart-预处理器-410" transform="translate(353.5, 60)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">预处理器</tspan></text></g></g></g></g><g class="node default default" id="flowchart-IfCache-405" transform="translate(243.25, 208.5)"><polygon points="72.5,0 145,-72.5 72.5,-145 0,-72.5" class="label-container" transform="translate(-72.5,72.5)" style=""></polygon><g class="label" style="" transform="translate(-48, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">缓存中存在？</tspan></text></g></g><g class="node default default" id="flowchart-Start-403" transform="translate(243.25, 25)"><rect style="" rx="17" ry="17" x="-28.75" y="-17" width="57.5" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">开始</tspan></text></g></g><g class="node default default" id="flowchart-查询优化器-413" transform="translate(404.75, 546)"><rect class="basic label-container" style="" rx="0" ry="0" x="-48.5" y="-17" width="97" height="34"></rect><g class="label" style="" transform="translate(-41, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">查询优化器</tspan></text></g></g><g class="node default default" id="flowchart-返回结果-425" transform="translate(243.25, 1091)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">返回结果</tspan></text></g></g><g class="node default default" id="flowchart-缓存结果-427" transform="translate(404.75, 1007)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40" y="-17" width="80" height="34"></rect><g class="label" style="" transform="translate(-32.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">缓存结果</tspan></text></g></g><g class="node default default" id="flowchart-End-431" transform="translate(243.25, 1175)"><rect style="" rx="17" ry="17" x="-28.75" y="-17" width="57.5" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">结束</tspan></text></g></g></g></g></g></svg></div><h2 id="mysql%E6%9F%A5%E8%AF%A2%E7%9A%84%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B" tabindex="-1">MySQL查询的执行流程</h2><h3 id="1.%E6%9F%A5%E8%AF%A2%E7%BC%93%E5%AD%98" tabindex="-1">1.查询缓存</h3><blockquote><p>因为查询缓存的效率并不高，所以在后面的版本（<code>MySQL8.0</code>）之后就抛弃了这个功能。</p></blockquote><p>MySQL收到一个查询请求之后，会先查询缓存看看之前是否执行过这条语句。相当于将之前执行的SQL以及结果以<code>key-value</code>的形式缓存在内存中，<code>key</code>就是查询SQL，<code>value</code>就是查询的结果。</p><ul><li>如果在缓存中查询到了这个SQL，那么缓存中的结果就会直接返回给客户端。</li><li>如果在缓存中没有查询到这个SQL，那么就会继续后面的执行，执行完成后，本次查询也会被保存在缓存中去。</li></ul><h4 id="%E4%B8%BA%E4%BB%80%E4%B9%88mysql8.0%E4%B9%8B%E5%90%8E%E8%A6%81%E6%8A%9B%E5%BC%83%E7%BC%93%E5%AD%98%E5%91%A2%EF%BC%9F" tabindex="-1">为什么MySQL8.0之后要抛弃缓存呢？</h4><ol><li><p>在MySQL中缓存的不是查询计划，而是将查询的结果缓存。也就是说只有完全相同的查询操作才会命中缓存。比如两个查询SQL在任何字符上的不同（空格、注释、大小写等），都会导致缓存不能命中。所以<mark>MySQL中的缓存命中率并不高</mark>。比如下面两个SQL就是不同的，第二个SQL比第一个多出了一个空格：</p><pre><code class="language-sql">SELECT id FROM t_user;SELECT id FROM  t_user;</code></pre></li><li><p>如果查询SQL中包含某些系统函数、用户自定义变量以及函数、系统表（比如<code>mysql</code>、<code>information_schema</code>、<code>performance_schema</code>库中的表），那这个SQL是不会被缓存的。</p><blockquote><p>比如系统函数<code>NOW()</code>，每次调用都是获取最新的当前时间，想象一下，如果缓存了这个结果，那么第二次命中的时候，那应该返回什么呢？</p></blockquote></li><li><p>既然是缓存，那缓存都是由失效时间的。MySQL的缓存系统会检测涉及到的每一张表，只要该表的结构或者数据被修改，比如使用了<code>INSERT</code>、<code>UPDATE</code>、<code>DELETE</code>、<code>TRUNCATE</code>、<code>ALTER TABLE</code>、<code>DROP TABLE</code>或者<code>DROP DATABASE</code>等语句，那么涉及到该表的所有缓存都将变为无效并从缓存中删除。对于更新修改较多的数据库来说，这样的<mark>缓存命中率将会非常低</mark>。</p></li></ol><h4 id="%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E4%BD%BF%E7%94%A8%E7%BC%93%E5%AD%98%E6%AF%94%E8%BE%83%E5%A5%BD%EF%BC%9F" tabindex="-1">什么时候使用缓存比较好？</h4><p>一般建议在静态表使用缓存，静态表一般指极少更新的表，比如系统的配置表、字典表。MySQL提供了选择<mark>按需使用</mark>缓存的方式，可以将<code>my.cnf</code>（配置文件）中的配置<code>query_cache_type</code>设置为<code>DEMAND</code>，表示当SQL语句中有<code>SQL_CACHE</code>关键词的时候才会缓存。配置如下：</p><pre><code class="language-ini"># 0：关闭查询缓存# 1：表示开启查询缓存# 2：表示按需使用即上面说到的DEMANDquery_cache_type=2</code></pre><p>在SQL中则这样使用：</p><pre><code class="language-sql">SELECT SQL_CACHE * FROM dict WHERE id = 5</code></pre><h3 id="2.%E8%A7%A3%E6%9E%90%E5%99%A8" tabindex="-1">2.解析器</h3><blockquote><p>在解析中对SQL语句进行语法分析、语义分析。</p></blockquote><ol><li><p><strong>词法分析</strong>：首先MySQL需要识别出来SQL语句中的字符串分别是什么，代表什么。比如MySQL从<code>SELECT</code>这个关键字识别出来，这是一个查询SQL。</p></li><li><p><strong>语法分析</strong>：根据词法分析的结果，语法解析器（比如Bison）会根据语法规则，判断这个SQL语句是否满足MySQL的语法。</p><ol><li><p>如果语句有问题，那么就会收到<code>You have an error in your SQL syntax</code>错误，比如下面这样：</p><pre><code class="language-mysql">mysql&gt; SELECT * FRO test;ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near &#39;FRO test&#39; at line 1</code></pre></li><li><p>如果语句是没有问题的，则会生成这样的一个语法树：</p><div class="mermaid"><svg id="render3021305029" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="386" style="max-width: 879.89453125px;" viewBox="0 0 879.89453125 386"><style>#render3021305029 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render3021305029 .error-icon{fill:#a5a7bb;}#render3021305029 .error-text{fill:#5a5844;stroke:#5a5844;}#render3021305029 .edge-thickness-normal{stroke-width:2px;}#render3021305029 .edge-thickness-thick{stroke-width:3.5px;}#render3021305029 .edge-pattern-solid{stroke-dasharray:0;}#render3021305029 .edge-pattern-dashed{stroke-dasharray:3;}#render3021305029 .edge-pattern-dotted{stroke-dasharray:2;}#render3021305029 .marker{fill:#F8B229;stroke:#F8B229;}#render3021305029 .marker.cross{stroke:#F8B229;}#render3021305029 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render3021305029 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render3021305029 .cluster-label text{fill:#5a5844;}#render3021305029 .cluster-label span{color:#5a5844;}#render3021305029 .label text,#render3021305029 span{fill:#fff;color:#fff;}#render3021305029 .node rect,#render3021305029 .node circle,#render3021305029 .node ellipse,#render3021305029 .node polygon,#render3021305029 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render3021305029 .node .label{text-align:center;}#render3021305029 .node.clickable{cursor:pointer;}#render3021305029 .arrowheadPath{fill:undefined;}#render3021305029 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render3021305029 .flowchart-link{stroke:#F8B229;fill:none;}#render3021305029 .edgeLabel{background-color:#006100;text-align:center;}#render3021305029 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render3021305029 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render3021305029 .cluster text{fill:#5a5844;}#render3021305029 .cluster span{color:#5a5844;}#render3021305029 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render3021305029 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 0)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path d="M222.0234375,34.3847866419295L193.41080729166666,39.82065553494125C164.79817708333334,45.256524427953,107.57291666666667,56.128262213976505,78.96028645833333,65.73079777365491C50.34765625,75.33333333333333,50.34765625,83.66666666666667,50.34765625,87.83333333333333L50.34765625,92" id="L-SQL-Select-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-SQL LE-Select" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M227.4403831845238,42L216.66060577876985,46.166666666666664C205.88082837301587,50.333333333333336,184.32127356150795,58.666666666666664,173.54149615575398,67C162.76171875,75.33333333333333,162.76171875,83.66666666666667,162.76171875,87.83333333333333L162.76171875,92" id="L-SQL-Fields-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-SQL LE-Fields" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M293.67271205357144,42L299.1263485863096,46.166666666666664C304.57998511904765,50.333333333333336,315.4872581845238,58.666666666666664,320.9408947172619,67C326.39453125,75.33333333333333,326.39453125,83.66666666666667,326.39453125,87.83333333333333L326.39453125,92" id="L-SQL-From-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-SQL LE-From" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M320.8203125,37.70893950995406L339.7955729166667,42.590782924961715C358.7708333333333,47.47262633996937,396.7213541666667,57.23631316998469,415.6966145833333,66.284823251659C434.671875,75.33333333333333,434.671875,83.66666666666667,434.671875,87.83333333333333L434.671875,92" id="L-SQL-Tables-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-SQL LE-Tables" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M320.8203125,32.48262939900256L358.7994791666667,38.235524499168804C396.7786458333333,43.98841959933504,472.7369791666667,55.49420979966752,510.7161458333333,65.41377156650043C548.6953125,75.33333333333333,548.6953125,83.66666666666667,548.6953125,87.83333333333333L548.6953125,92" id="L-SQL-Where-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-SQL LE-Where" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M320.8203125,30.109298343498086L380.265625,36.25774861958174C439.7109375,42.40619889566539,558.6015625,54.7030994478327,618.046875,65.01821639058302C677.4921875,75.33333333333333,677.4921875,83.66666666666667,677.4921875,87.83333333333333L677.4921875,92" id="L-SQL-Conditions-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-SQL LE-Conditions" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M134.1015625,119.70313639679067L120.13411458333333,124.91928033065888C106.16666666666667,130.13542426452713,78.23177083333333,140.56771213226355,64.26432291666667,149.95052273279845C50.296875,159.33333333333334,50.296875,167.66666666666666,50.296875,171.83333333333334L50.296875,176" id="L-Fields-username-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Fields LE-username" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M162.7822730654762,126L162.78731088789684,130.16666666666666C162.79234871031747,134.33333333333334,162.80242435515873,142.66666666666666,162.80746217757937,151C162.8125,159.33333333333334,162.8125,167.66666666666666,162.8125,171.83333333333334L162.8125,176" id="L-Fields-age-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Fields LE-age" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M191.421875,120.70486572720021L203.78515625,125.75405477266685C216.1484375,130.8032438181335,240.875,140.90162190906673,253.23828125,150.11747762120004C265.6015625,159.33333333333334,265.6015625,167.66666666666666,265.6015625,171.83333333333334L265.6015625,176" id="L-Fields-gender-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Fields LE-gender" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M434.671875,126L434.671875,130.16666666666666C434.671875,134.33333333333334,434.671875,142.66666666666666,434.671875,151C434.671875,159.33333333333334,434.671875,167.66666666666666,434.671875,171.83333333333334L434.671875,176" id="L-Tables-user_info-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Tables LE-user_info" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M677.4921875,126L677.4921875,130.16666666666666C677.4921875,134.33333333333334,677.4921875,142.66666666666666,677.4921875,151C677.4921875,159.33333333333334,677.4921875,167.66666666666666,677.4921875,171.83333333333334L677.4921875,176" id="L-Conditions-and-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Conditions LE-and" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M656.91796875,195.6928058429702L606.8639322916666,202.24400486914183C556.8098958333334,208.79520389531345,456.7018229166667,221.89760194765674,406.6477864583333,232.61546764049504C356.59375,243.33333333333334,356.59375,251.66666666666666,356.59375,255.83333333333334L356.59375,260" id="L-and-gt-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-and LE-gt" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M656.91796875,200.91959187326592L642.1617838541666,206.59965989438828C627.4055989583334,212.27972791551062,597.8932291666666,223.63986395775532,583.1370442708334,233.48659864554432C568.380859375,243.33333333333334,568.380859375,251.66666666666666,568.380859375,255.83333333333334L568.380859375,260" id="L-and-like-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-and LE-like" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M698.06640625,200.49369918699188L713.8561197916666,206.24474932249322C729.6458333333334,211.9957994579946,761.2252604166666,223.49789972899728,777.0149739583334,233.41561653116528C792.8046875,243.33333333333334,792.8046875,251.66666666666666,792.8046875,255.83333333333334L792.8046875,260" id="L-and-eq-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-and LE-eq" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M341.578125,291.5961486303228L336.8795572916667,296.1634571919356C332.1809895833333,300.7307657535485,322.7838541666667,309.8653828767743,318.0852864583333,318.5993581050538C313.38671875,327.3333333333333,313.38671875,335.6666666666667,313.38671875,339.8333333333333L313.38671875,344" id="L-gt-age1-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-gt LE-age1" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M371.609375,291.5961486303228L376.3079427083333,296.1634571919356C381.0065104166667,300.7307657535485,390.4036458333333,309.8653828767743,395.1022135416667,318.5993581050538C399.80078125,327.3333333333333,399.80078125,335.6666666666667,399.80078125,339.8333333333333L399.80078125,344" id="L-gt-20-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-gt LE-20" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M776.44140625,289.75375135918813L770.1875,294.62812613265675C763.93359375,299.5025009061254,751.42578125,309.2512504530627,745.171875,318.292291893198C738.91796875,327.3333333333333,738.91796875,335.6666666666667,738.91796875,339.8333333333333L738.91796875,344" id="L-eq-gender1-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-eq LE-gender1" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M809.16796875,289.75375135918813L815.421875,294.62812613265675C821.67578125,299.5025009061254,834.18359375,309.2512504530627,840.4375,318.292291893198C846.69140625,327.3333333333333,846.69140625,335.6666666666667,846.69140625,339.8333333333333L846.69140625,344" id="L-eq-male-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-eq LE-male" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M547.705078125,291.4518771331058L541.1363932291666,296.04323094425484C534.5677083333334,300.63458475540386,521.4303385416666,309.81729237770196,514.8616536458334,318.57531285551767C508.29296875,327.3333333333333,508.29296875,335.6666666666667,508.29296875,339.8333333333333L508.29296875,344" id="L-like-username1-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-like LE-username1" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M589.056640625,291.4518771331058L595.6253255208334,296.04323094425484C602.1940104166666,300.63458475540386,615.3313802083334,309.81729237770196,621.9000651041666,318.57531285551767C628.46875,327.3333333333333,628.46875,335.6666666666667,628.46875,339.8333333333333L628.46875,344" id="L-like-zs-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-like LE-zs" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-20-510" transform="translate(399.80078125, 361)"><rect class="basic label-container" style="" rx="0" ry="0" x="-16.1953125" y="-17" width="32.390625" height="34"></rect><g class="label" style="" transform="translate(-8.6953125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">20</tspan></text></g></g><g class="node default default" id="flowchart-SQL-478" transform="translate(271.421875, 25)"><rect class="basic label-container" style="" rx="0" ry="0" x="-49.3984375" y="-17" width="98.796875" height="34"></rect><g class="label" style="" transform="translate(-41.8984375, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">SELECT语句</tspan></text></g></g><g class="node default default" id="flowchart-Select-480" transform="translate(50.34765625, 109)"><rect class="basic label-container" style="" rx="0" ry="0" x="-33.75390625" y="-17" width="67.5078125" height="34"></rect><g class="label" style="" transform="translate(-26.25390625, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">SELECT</tspan></text></g></g><g class="node default default" id="flowchart-Fields-482" transform="translate(162.76171875, 109)"><rect class="basic label-container" style="" rx="0" ry="0" x="-28.66015625" y="-17" width="57.3203125" height="34"></rect><g class="label" style="" transform="translate(-21.16015625, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Fields</tspan></text></g></g><g class="node default default" id="flowchart-From-484" transform="translate(326.39453125, 109)"><rect class="basic label-container" style="" rx="0" ry="0" x="-27.74609375" y="-17" width="55.4921875" height="34"></rect><g class="label" style="" transform="translate(-20.24609375, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">FROM</tspan></text></g></g><g class="node default default" id="flowchart-Tables-486" transform="translate(434.671875, 109)"><rect class="basic label-container" style="" rx="0" ry="0" x="-30.53125" y="-17" width="61.0625" height="34"></rect><g class="label" style="" transform="translate(-23.03125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Tables</tspan></text></g></g><g class="node default default" id="flowchart-Where-488" transform="translate(548.6953125, 109)"><rect class="basic label-container" style="" rx="0" ry="0" x="-33.4921875" y="-17" width="66.984375" height="34"></rect><g class="label" style="" transform="translate(-25.9921875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">WHERE</tspan></text></g></g><g class="node default default" id="flowchart-Conditions-490" transform="translate(677.4921875, 109)"><rect class="basic label-container" style="" rx="0" ry="0" x="-45.3046875" y="-17" width="90.609375" height="34"></rect><g class="label" style="" transform="translate(-37.8046875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Conditions</tspan></text></g></g><g class="node default default" id="flowchart-username-492" transform="translate(50.296875, 193)"><rect class="basic label-container" style="" rx="0" ry="0" x="-42.296875" y="-17" width="84.59375" height="34"></rect><g class="label" style="" transform="translate(-34.796875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">username</tspan></text></g></g><g class="node default default" id="flowchart-age-494" transform="translate(162.8125, 193)"><rect class="basic label-container" style="" rx="0" ry="0" x="-20.21875" y="-17" width="40.4375" height="34"></rect><g class="label" style="" transform="translate(-12.71875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">age</tspan></text></g></g><g class="node default default" id="flowchart-gender-496" transform="translate(265.6015625, 193)"><rect class="basic label-container" style="" rx="0" ry="0" x="-32.5703125" y="-17" width="65.140625" height="34"></rect><g class="label" style="" transform="translate(-25.0703125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">gender</tspan></text></g></g><g class="node default default" id="flowchart-user_info-498" transform="translate(434.671875, 193)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40.88671875" y="-17" width="81.7734375" height="34"></rect><g class="label" style="" transform="translate(-33.38671875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">user_info</tspan></text></g></g><g class="node default default" id="flowchart-and-500" transform="translate(677.4921875, 193)"><rect class="basic label-container" style="" rx="0" ry="0" x="-20.57421875" y="-17" width="41.1484375" height="34"></rect><g class="label" style="" transform="translate(-13.07421875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">and</tspan></text></g></g><g class="node default default" id="flowchart-gt-502" transform="translate(356.59375, 277)"><rect class="basic label-container" style="" rx="0" ry="0" x="-15.015625" y="-17" width="30.03125" height="34"></rect><g class="label" style="" transform="translate(-7.515625, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">gt</tspan></text></g></g><g class="node default default" id="flowchart-like-504" transform="translate(568.380859375, 277)"><rect class="basic label-container" style="" rx="0" ry="0" x="-20.67578125" y="-17" width="41.3515625" height="34"></rect><g class="label" style="" transform="translate(-13.17578125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">like</tspan></text></g></g><g class="node default default" id="flowchart-eq-506" transform="translate(792.8046875, 277)"><rect class="basic label-container" style="" rx="0" ry="0" x="-16.36328125" y="-17" width="32.7265625" height="34"></rect><g class="label" style="" transform="translate(-8.86328125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">eq</tspan></text></g></g><g class="node default default" id="flowchart-age1-508" transform="translate(313.38671875, 361)"><rect class="basic label-container" style="" rx="0" ry="0" x="-20.21875" y="-17" width="40.4375" height="34"></rect><g class="label" style="" transform="translate(-12.71875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">age</tspan></text></g></g><g class="node default default" id="flowchart-gender1-512" transform="translate(738.91796875, 361)"><rect class="basic label-container" style="" rx="0" ry="0" x="-32.5703125" y="-17" width="65.140625" height="34"></rect><g class="label" style="" transform="translate(-25.0703125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">gender</tspan></text></g></g><g class="node default default" id="flowchart-male-514" transform="translate(846.69140625, 361)"><rect class="basic label-container" style="" rx="0" ry="0" x="-25.203125" y="-17" width="50.40625" height="34"></rect><g class="label" style="" transform="translate(-17.703125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">male</tspan></text></g></g><g class="node default default" id="flowchart-username1-516" transform="translate(508.29296875, 361)"><rect class="basic label-container" style="" rx="0" ry="0" x="-42.296875" y="-17" width="84.59375" height="34"></rect><g class="label" style="" transform="translate(-34.796875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">username</tspan></text></g></g><g class="node default default" id="flowchart-zs-518" transform="translate(628.46875, 361)"><rect class="basic label-container" style="" rx="0" ry="0" x="-27.87890625" y="-17" width="55.7578125" height="34"></rect><g class="label" style="" transform="translate(-20.37890625, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">'%三%'</tspan></text></g></g></g></g></g></svg></div></li></ol></li></ol><p>下面则是解析器分析SQL的过程和步骤：</p><div class="mermaid"><svg id="render3588656073" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="566" style="max-width: 423.21875px;" viewBox="0 0 423.21875 566"><style>#render3588656073 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render3588656073 .error-icon{fill:#a5a7bb;}#render3588656073 .error-text{fill:#5a5844;stroke:#5a5844;}#render3588656073 .edge-thickness-normal{stroke-width:2px;}#render3588656073 .edge-thickness-thick{stroke-width:3.5px;}#render3588656073 .edge-pattern-solid{stroke-dasharray:0;}#render3588656073 .edge-pattern-dashed{stroke-dasharray:3;}#render3588656073 .edge-pattern-dotted{stroke-dasharray:2;}#render3588656073 .marker{fill:#F8B229;stroke:#F8B229;}#render3588656073 .marker.cross{stroke:#F8B229;}#render3588656073 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render3588656073 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render3588656073 .cluster-label text{fill:#5a5844;}#render3588656073 .cluster-label span{color:#5a5844;}#render3588656073 .label text,#render3588656073 span{fill:#fff;color:#fff;}#render3588656073 .node rect,#render3588656073 .node circle,#render3588656073 .node ellipse,#render3588656073 .node polygon,#render3588656073 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render3588656073 .node .label{text-align:center;}#render3588656073 .node.clickable{cursor:pointer;}#render3588656073 .arrowheadPath{fill:undefined;}#render3588656073 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render3588656073 .flowchart-link{stroke:#F8B229;fill:none;}#render3588656073 .edgeLabel{background-color:#006100;text-align:center;}#render3588656073 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render3588656073 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render3588656073 .cluster text{fill:#5a5844;}#render3588656073 .cluster span{color:#5a5844;}#render3588656073 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render3588656073 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 8)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"><g class="cluster default" id="A"><rect style="" rx="0" ry="0" x="-31.26953125" y="176" width="407.21875" height="187"></rect><g class="cluster-label" transform="translate(-31.26953125, 181)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">由独立的分析模块组成，相当于一个LIST，循环解析Token</tspan></text></g></g></g><g class="edgePaths"><path d="M166.509765625,42L166.509765625,46.166666666666664C166.509765625,50.333333333333336,166.509765625,58.666666666666664,166.509765625,67C166.509765625,75.33333333333333,166.509765625,83.66666666666667,166.509765625,87.83333333333333L166.509765625,92" id="L-START-词法分析-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-START LE-词法分析" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M166.509765625,126L166.509765625,130.16666666666666C166.509765625,134.33333333333334,166.509765625,142.66666666666666,166.509765625,151C166.509765625,159.33333333333334,166.509765625,167.66666666666666,166.509765625,176C166.509765625,184.33333333333334,166.509765625,192.66666666666666,166.509765625,196.83333333333334L166.509765625,201" id="L-词法分析-语法分析-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-词法分析 LE-语法分析" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M191.98333206917476,304L200.59939130764565,298.25C209.2154505461165,292.5,226.44756902305826,281,226.44756902305826,269.5C226.44756902305826,258,209.2154505461165,246.5,200.59939130764565,240.75L191.98333206917476,235" id="L-分析机-语法分析-0" class=" edge-thickness-normal edge-pattern-dotted flowchart-link LS-分析机 LE-语法分析" style="fill:none;stroke-width:2px;stroke-dasharray:3;" marker-end="url(#flowchart-pointEnd)"></path><path d="M141.03619918082524,235L132.42013994235435,240.75C123.80408070388349,246.5,106.57196222694175,258,106.57196222694175,269.5C106.57196222694175,281,123.80408070388349,292.5,132.42013994235435,298.25L141.03619918082524,304" id="L-语法分析-分析机-0" class=" edge-thickness-normal edge-pattern-dotted flowchart-link LS-语法分析 LE-分析机" style="fill:none;stroke-width:2px;stroke-dasharray:3;" marker-end="url(#flowchart-pointEnd)"></path><path d="M166.509765625,338L166.509765625,342.1666666666667C166.509765625,346.3333333333333,166.509765625,354.6666666666667,166.509765625,364.5833333333333C166.509765625,374.5,166.509765625,386,166.509765625,397.5C166.509765625,409,166.509765625,420.5,166.509765625,426.25L166.509765625,432" id="L-分析机-AstTree-0" class=" edge-thickness-normal edge-pattern-dotted flowchart-link LS-分析机 LE-AstTree" style="fill:none;stroke-width:2px;stroke-dasharray:3;" marker-end="url(#flowchart-pointEnd)"></path><path d="M166.509765625,466L166.509765625,470.1666666666667C166.509765625,474.3333333333333,166.509765625,482.6666666666667,166.509765625,491C166.509765625,499.3333333333333,166.509765625,507.6666666666667,166.509765625,511.8333333333333L166.509765625,516" id="L-AstTree-END-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-AstTree LE-END" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(243.6796875, 269.5)"><g class="label" transform="translate(-73, -9.5)"><rect rx="0" ry="0" width="146" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">没有分析出语法错误</tspan></text></g></g><g class="edgeLabel" transform="translate(89.33984375, 269.5)"><g class="label" transform="translate(-61.33984375, -9.5)"><rect rx="0" ry="0" width="122.6796875" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">分析机分析Token</tspan></text></g></g><g class="edgeLabel" transform="translate(166.509765625, 397.5)"><g class="label" transform="translate(-16.5, -9.5)"><rect rx="0" ry="0" width="33" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">添加</tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-语法分析-531" transform="translate(166.509765625, 218)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">语法分析</tspan></text></g></g><g class="node default default" id="flowchart-分析机-532" transform="translate(166.509765625, 321)"><rect class="basic label-container" style="" rx="0" ry="0" x="-32.5" y="-17" width="65" height="34"></rect><g class="label" style="" transform="translate(-25, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">分析机</tspan></text></g></g><g class="node default default" id="flowchart-START-529" transform="translate(166.509765625, 25)"><rect style="" rx="17" ry="17" x="-28.75" y="-17" width="57.5" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">开始</tspan></text></g></g><g class="node default default" id="flowchart-词法分析-530" transform="translate(166.509765625, 109)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">词法分析</tspan></text></g></g><g class="node default default" id="flowchart-AstTree-537" transform="translate(166.509765625, 449)"><rect class="basic label-container" style="" rx="0" ry="0" x="-34.8671875" y="-17" width="69.734375" height="34"></rect><g class="label" style="" transform="translate(-27.3671875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">AstTree</tspan></text></g></g><g class="node default default" id="flowchart-END-538" transform="translate(166.509765625, 533)"><rect style="" rx="17" ry="17" x="-28.75" y="-17" width="57.5" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">结束</tspan></text></g></g></g></g></g></svg></div><h3 id="3.%E4%BC%98%E5%8C%96%E5%99%A8" tabindex="-1">3.优化器</h3><p>在优化器中会确定SQL语句的执行路径，比如是根据<mark>全表检索</mark>还是根据<mark>索引检索</mark>等等。经过了上面的解析器，MySQL就已经知道这个SQL是做什么的了。在开始执行之前，还需要经过优化器的处理。</p><p>一条SQL可以有很多种执行方式，最后都会返回相同的结果。优化器的作用就是找到这些执行方式中最好的一个。比如：</p><ul><li>优化器在表里面有多个索引的时候，决定使用哪个索引。</li><li>在一个SQL有多表关联（<code>JOIN</code>）的时候决定每个表的连接顺序。</li><li>还有表达式简化、子查询转为连接、外连接转为内连接等等。</li></ul><p>比如下面的这个SQL中有两个表的<code>JOIN</code>：</p><pre><code class="language-sql">SELECT * FROM t_user JOIN t_order ON t_user.id = t_order.idWHERE t_user.username=&#39;张三&#39; and t_order.order_no=&#39;2022013000001&#39;;</code></pre><blockquote><ul><li><strong>方案1</strong>：可以先从<code>t_user</code>表里面查询出<code>username=='张三'</code>的记录的ID，然后再根据ID关联到表<code>t_order</code>，再判断<code>t_order</code>中的<code>order_no</code>是否等于<code>2022013000001</code>。</li><li><strong>方案2</strong>：可以先从<code>t_order</code>表里面查询出<code>order_no='2022013000001'</code>的记录的ID，然后再根据ID关联到表<code>t_user</code>，再判断<code>t_user</code>中的<code>username</code>是否等于<code>张三</code>。</li></ul></blockquote><p>上面两个执行方法的逻辑结果都是一样的，但是执行的效率会不同，而优化器的作用就是决定使用哪一种方案。优化器阶段完成后，这个SQL的执行方案也就确定下来了，最后进入执行器的阶段。</p><p>在优化器中又可以分为逻辑查询优化阶段以及物理查询优化阶段：</p><ol><li><strong>逻辑查询优化</strong>：就是通过改变SQL语句的内容来是SQL查询更加高效，同时为物理查询优化提供更多的候选执行计划。通常采用的方式则是对SQL语句进行<mark>等价变换</mark>，对查询进行<mark>重写</mark>，而查询重写的数学基础就是关系代数。对条件表达式进行等价谓词重写、条件简化，对视图进行重写，对子查询进行优化，对连接语义进行了外连接消除、嵌套连接消除等等。</li><li><strong>物理查询优化</strong>：是基于关系代数进行的查询重写，而关系代数的每一步都对应着物理计算，这些物理计算往往存在多种算法，因此需要计算各种物理路径的代价，从中选择代价最小的最为执行计划。在这个阶段里面，对于单表和多表连接的操作，需要高效地使用<mark>索引</mark>来提升查询效率。</li></ol><h3 id="4.%E6%89%A7%E8%A1%8C%E5%99%A8" tabindex="-1">4.执行器</h3><blockquote><p>截止到这里，还没实际地读写真实的表，仅仅只是产出了一个执行计划。所以这里就进入了执行器的阶段。</p></blockquote><p>在实际执行之前需要判断用户是否具备相应的权限。如果没有权限则返回错误，如果有权限则执行并返回结果（在MySQL8.0之前如果设置开启了缓存，这里还会将结果缓存起来）。</p><p>如果有权限，则打开表继续执行。打开表的时候，执行器就会根据表定义的引擎，调用存储引擎的<code>API</code>对表进行读写。存储引擎<code>API</code>只是抽象的接口，其下面还有一个存储引擎层，具体实现还是要看表选择的存储引擎。</p><p>比如在表<code>t_user</code>中，<code>id</code>字段没有索引，那么执行器的执行流程是：</p><ol><li>调用存储引擎的<code>API</code>获取这个表的第一行，判断<code>id</code>是不是1，如果不是则跳过，如果是则保存这行到结果集中。</li><li>调用存储引擎的<code>API</code>获取下一行，重复和第1步相同的判断逻辑，直到获取到最后一行。</li><li>执行器将上面所有满足条件的行组成结果集返回给客户端。</li></ol><h3 id="%E6%89%A7%E8%A1%8C%E6%B5%81%E7%A8%8B%E7%AE%80%E5%8D%95%E6%80%BB%E7%BB%93" tabindex="-1">执行流程简单总结</h3><p>根据上面可以得出，SQL语句在MySQL中的流程可以简单的表示为<code>SQL语句-&gt;查询缓存-&gt;解析器-&gt;优化器-&gt;执行器</code>：</p><div class="mermaid"><svg id="render226698591" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="192" style="max-width: 925.609375px;" viewBox="0 0 925.609375 192"><style>#render226698591 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render226698591 .error-icon{fill:#a5a7bb;}#render226698591 .error-text{fill:#5a5844;stroke:#5a5844;}#render226698591 .edge-thickness-normal{stroke-width:2px;}#render226698591 .edge-thickness-thick{stroke-width:3.5px;}#render226698591 .edge-pattern-solid{stroke-dasharray:0;}#render226698591 .edge-pattern-dashed{stroke-dasharray:3;}#render226698591 .edge-pattern-dotted{stroke-dasharray:2;}#render226698591 .marker{fill:#F8B229;stroke:#F8B229;}#render226698591 .marker.cross{stroke:#F8B229;}#render226698591 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render226698591 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render226698591 .cluster-label text{fill:#5a5844;}#render226698591 .cluster-label span{color:#5a5844;}#render226698591 .label text,#render226698591 span{fill:#fff;color:#fff;}#render226698591 .node rect,#render226698591 .node circle,#render226698591 .node ellipse,#render226698591 .node polygon,#render226698591 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render226698591 .node .label{text-align:center;}#render226698591 .node.clickable{cursor:pointer;}#render226698591 .arrowheadPath{fill:undefined;}#render226698591 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render226698591 .flowchart-link{stroke:#F8B229;fill:none;}#render226698591 .edgeLabel{background-color:#006100;text-align:center;}#render226698591 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render226698591 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render226698591 .cluster text{fill:#5a5844;}#render226698591 .cluster span{color:#5a5844;}#render226698591 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render226698591 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 8)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path d="M770.109375,92L774.2760416666666,92C778.4427083333334,92,786.7760416666666,92,795.109375,92C803.4427083333334,92,811.7760416666666,92,815.9427083333334,92L820.109375,92" id="L-执行器-返回结果-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-执行器 LE-返回结果" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M90.109375,92L94.27604166666667,92C98.44270833333333,92,106.77604166666667,92,115.109375,92C123.44270833333333,92,131.77604166666666,92,135.94270833333334,92L140.109375,92" id="L-SQL-分析器-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-SQL LE-分析器" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M291.109375,92L302.109375,92C313.109375,92,335.109375,92,357.109375,92C379.109375,92,401.109375,92,412.109375,92L423.109375,92" id="L-分析器-解析器-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-分析器 LE-解析器" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M574.109375,92L585.0260416666666,92C595.9427083333334,92,617.7760416666666,92,639.609375,92C661.4427083333334,92,683.2760416666666,92,694.1927083333334,92L705.109375,92" id="L-解析器-执行器-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-解析器 LE-执行器" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(357.109375, 92)"><g class="label" transform="translate(-41, -9.5)"><rect rx="0" ry="0" width="82" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">语法分析树</tspan></text></g></g><g class="edgeLabel" transform="translate(639.609375, 92)"><g class="label" transform="translate(-40.5, -17.5)"><rect rx="0" ry="0" width="81" height="35"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">查询计划</tspan><tspan xml:space="preserve" dy="1em" x="0" class="row">（查询树）</tspan></text></g></g></g><g class="nodes"><g class="root" transform="translate(415.609375, 0)"><g class="clusters"><g class="cluster default" id="解析器"><rect style="" rx="0" ry="0" x="8" y="8" width="151" height="168"></rect><g class="cluster-label" transform="translate(58.5, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">解析器</tspan></text></g></g></g><g class="edgePaths"><path d="M83.5,67L83.5,71.16666666666667C83.5,75.33333333333333,83.5,83.66666666666667,83.5,92C83.5,100.33333333333333,83.5,108.66666666666667,83.5,112.83333333333333L83.5,117" id="L-逻辑优化-物理优化-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-逻辑优化 LE-物理优化" style="fill:none;"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-物理优化-557" transform="translate(83.5, 134)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">物理优化</tspan></text></g></g><g class="node default default" id="flowchart-逻辑优化-556" transform="translate(83.5, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">逻辑优化</tspan></text></g></g></g></g><g class="root" transform="translate(132.609375, 0)"><g class="clusters"><g class="cluster default" id="分析器"><rect style="" rx="0" ry="0" x="8" y="8" width="151" height="168"></rect><g class="cluster-label" transform="translate(58.5, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">分析器</tspan></text></g></g></g><g class="edgePaths"><path d="M83.5,67L83.5,71.16666666666667C83.5,75.33333333333333,83.5,83.66666666666667,83.5,92C83.5,100.33333333333333,83.5,108.66666666666667,83.5,112.83333333333333L83.5,117" id="L-语法分析-语义检查-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-语法分析 LE-语义检查" style="fill:none;"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-语义检查-555" transform="translate(83.5, 134)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">语义检查</tspan></text></g></g><g class="node default default" id="flowchart-语法分析-554" transform="translate(83.5, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">语法分析</tspan></text></g></g></g></g><g class="node default default" id="flowchart-SQL-549" transform="translate(49.0546875, 92)"><rect style="" rx="17" ry="17" x="-41.0546875" y="-17" width="82.109375" height="34"></rect><g class="label" style="" transform="translate(-29.3046875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">SQL语句</tspan></text></g></g><g class="node default default" id="flowchart-执行器-552" transform="translate(737.609375, 92)"><rect class="basic label-container" style="" rx="0" ry="0" x="-32.5" y="-17" width="65" height="34"></rect><g class="label" style="" transform="translate(-25, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">执行器</tspan></text></g></g><g class="node default default" id="flowchart-返回结果-553" transform="translate(864.859375, 92)"><rect style="" rx="17" ry="17" x="-44.75" y="-17" width="89.5" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">返回结果</tspan></text></g></g></g></g></g></svg></div><h2 id="sql%E6%89%A7%E8%A1%8C%E5%8E%9F%E7%90%86" tabindex="-1">SQL执行原理</h2><blockquote><p>上面的结构图很复杂，我们只需要获取最核心的部分：SQL的执行原理。不同的DBMS的SQL执行原理是相通的，只是各有各的实现路径。下面将分别从<code>MySQL8.0</code>以及<code>MySQL5.7</code>来说明MySQL的SQL执行原理。</p></blockquote><h3 id="mysql8.0" tabindex="-1">MySQL8.0</h3><blockquote><p>既然一条SQL语句在MySQL服务中会经过不同的模块，那就让我们来看看在不同的模块中，SQL执行所使用的资源（时间）是怎么样的？如何在MySQL中对一条SQL语句的执行时间进行分析？</p></blockquote><h4 id="1.%E7%A1%AE%E8%AE%A4profiling%E6%98%AF%E5%90%A6%E5%BC%80%E5%90%AF" tabindex="-1">1.确认profiling是否开启</h4><p>使用<code>SELECT @@profiling;</code>或者<code>SHOW variables LIKE 'profiling';</code>查看是否开启计划。开启计划可以使MySQL收集在SQL执行时所使用的资源情况，比如下面这样可以查看：</p><pre><code class="language-mysql">mysql&gt; SELECT @@profiling;+-------------+| @@profiling |+-------------+|           0 |+-------------+1 row in set, 1 warning (0.00 sec)mysql&gt; SHOW variables LIKE &#39;profiling&#39;;+---------------+-------+| Variable_name | Value |+---------------+-------+| profiling     | OFF   |+---------------+-------+1 row in set (0.00 sec)</code></pre><p>可以看到默认<code>profiling</code>是等于0的，也就是关闭的，如果需要打开，那么修改为1即可：</p><pre><code class="language-mysql">mysql&gt; SET profiling=1;Query OK, 0 rows affected, 1 warning (0.00 sec)mysql&gt; SELECT @@profiling;+-------------+| @@profiling |+-------------+|           1 |+-------------+1 row in set, 1 warning (0.00 sec)mysql&gt; SHOW variables LIKE &#39;profiling&#39;;+---------------+-------+| Variable_name | Value |+---------------+-------+| profiling     | ON    |+---------------+-------+1 row in set (0.00 sec)</code></pre><h4 id="2.%E5%A4%9A%E6%AC%A1%E6%89%A7%E8%A1%8C%E7%9B%B8%E5%90%8Csql%E6%9F%A5%E8%AF%A2" tabindex="-1">2.多次执行相同SQL查询</h4><p>然后在这里任意执行一个SQL查询：</p><pre><code class="language-sql">SELECT * FROM user</code></pre><h4 id="3.%E6%9F%A5%E7%9C%8Bprofiles" tabindex="-1">3.查看profiles</h4><p>查看当前会话所产生的所有<code>profiles</code>：</p><pre><code class="language-mysql">mysql&gt; SHOW profiles;+----------+------------+-----------------------+| Query_ID | Duration   | Query                 |+----------+------------+-----------------------+|        1 | 0.00017025 | SELECT @@profiling    ||        2 | 0.00077750 | SHOW databases        ||        3 | 0.00014850 | SELECT DATABASE()     ||        4 | 0.00173225 | SHOW tables           ||        5 | 0.00050050 | SELECT * FROM user    ||        6 | 0.00034925 | SELECT * FROM user    |+----------+------------+-----------------------+</code></pre><h4 id="4.%E6%9F%A5%E7%9C%8Bprofile" tabindex="-1">4.查看profile</h4><p>显示执行计划，查看程序的执行步骤，使用<code>SHOW profile</code>查看：</p><pre><code class="language-mysql">mysql&gt; SHOW profile;+--------------------------------+----------+| Status                         | Duration |+--------------------------------+----------+| starting                       | 0.000075 || Executing hook on transaction  | 0.000005 || starting                       | 0.000007 || checking permissions           | 0.000005 |//权限检查| Opening tables                 | 0.000051 |//打开表| init                           | 0.000005 |//初始化| System lock                    | 0.000008 |//锁系统| optimizing                     | 0.000004 |//优化查询| statistics                     | 0.000015 |//统计| preparing                      | 0.000018 |//准备| executing                      | 0.000096 |//执行| end                            | 0.000004 || query end                      | 0.000003 || waiting for handler commit     | 0.000009 || closing tables                 | 0.000009 || freeing items                  | 0.000027 || cleaning up                    | 0.000010 |+--------------------------------+----------+17 rows in set, 1 warning (0.00 sec)</code></pre><p>也可以查询指定的<code>Query ID</code>，比如：</p><pre><code class="language-mysql">mysql&gt; SHOW profile for QUERY 6;+--------------------------------+----------+| Status                         | Duration |+--------------------------------+----------+| starting                       | 0.000075 || Executing hook on transaction  | 0.000005 || starting                       | 0.000007 || checking permissions           | 0.000005 || Opening tables                 | 0.000051 || init                           | 0.000005 || System lock                    | 0.000008 || optimizing                     | 0.000004 || statistics                     | 0.000015 || preparing                      | 0.000018 || executing                      | 0.000096 || end                            | 0.000004 || query end                      | 0.000003 || waiting for handler commit     | 0.000009 || closing tables                 | 0.000009 || freeing items                  | 0.000027 || cleaning up                    | 0.000010 |+--------------------------------+----------+17 rows in set, 1 warning (0.00 sec)</code></pre><p>查询 SQL 的执行时间结果和上面是一样的。 此外，还可以查询更丰富的内容：</p><pre><code class="language-mysql">mysql&gt; SHOW profile cpu,block io for QUERY 6;+--------------------------------+----------+----------+------------+--------------+---------------+| Status                         | Duration | CPU_user | CPU_system | Block_ops_in | Block_ops_out |+--------------------------------+----------+----------+------------+--------------+---------------+| starting                       | 0.000075 | 0.000050 |   0.000024 |            0 |             0 || Executing hook on transaction  | 0.000005 | 0.000004 |   0.000000 |            0 |             0 || starting                       | 0.000007 | 0.000001 |   0.000006 |            0 |             0 || checking permissions           | 0.000005 | 0.000005 |   0.000000 |            0 |             0 || Opening tables                 | 0.000051 | 0.000033 |   0.000018 |            0 |             0 || init                           | 0.000005 | 0.000000 |   0.000005 |            0 |             0 || System lock                    | 0.000008 | 0.000007 |   0.000001 |            0 |             0 || optimizing                     | 0.000004 | 0.000004 |   0.000000 |            0 |             0 || statistics                     | 0.000015 | 0.000009 |   0.000006 |            0 |             0 || preparing                      | 0.000018 | 0.000012 |   0.000006 |            0 |             0 || executing                      | 0.000096 | 0.000060 |   0.000036 |            0 |             0 || end                            | 0.000004 | 0.000004 |   0.000000 |            0 |             0 || query end                      | 0.000003 | 0.000000 |   0.000003 |            0 |             0 || waiting for handler commit     | 0.000009 | 0.000006 |   0.000003 |            0 |             0 || closing tables                 | 0.000009 | 0.000003 |   0.000006 |            0 |             0 || freeing items                  | 0.000027 | 0.000020 |   0.000006 |            0 |             0 || cleaning up                    | 0.000010 | 0.000004 |   0.000006 |            0 |             0 |+--------------------------------+----------+----------+------------+--------------+---------------+17 rows in set, 1 warning (0.00 sec)</code></pre><h3 id="mysql5.7" tabindex="-1">MySQL5.7</h3><blockquote><p>在Mysql5.7中重复上面的操作，发现前后两次相同的sql语句，其执行的查询过程仍然是相同的。并没有使用到之前说的缓存，这是怎么回事呢？</p></blockquote><p>这里我们需要<mark>显式开启查询缓存模式</mark>。在MySQL5.7中如下设置：</p><ol><li><p>修改配置文件，在<code>my.cnf</code>中新增一行：</p><pre><code class="language-ini"># 0：关闭查询缓存# 1：表示开启查询缓存# 2：表示按需使用即上面说到的DEMANDquery_cache_type=1</code></pre><p>这里我们直接设置全部开启即可。</p></li><li><p>重启mysql服务。</p></li></ol><p>然后我们重复上面8.0的操作，分别查看两个完全相同的SQL的<code>profile</code>，即可看见使用到了缓存。</p>]]>
                    </description>
                    <pubDate>Sat, 13 Aug 2022 20:26:25 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[MySQL-逻辑架构]]>
                    </title>
                    <link>https://zygzyg.com/archives/634afbb608bc4c348ee1c1a66ad73922</link>
                    <description>
                            <![CDATA[<h1 id="mysql-%E9%80%BB%E8%BE%91%E6%9E%B6%E6%9E%84" tabindex="-1">MySQL-逻辑架构</h1><blockquote><p>MySQL是一个典型的<code>C/S</code>架构，也就是<code>Client/Server</code>结构，服务端就是<code>mysqld</code>。不论客户端进程和服务器进程是采用的哪种方式通信，最后实现的效果都是：<strong>客户端进程向服务器进程发送一段文本</strong>（<strong>SQL语句</strong>），<strong>服务器进程处理后再向客户端进程发送一段文本</strong>（<strong>处理结果</strong>）。</p></blockquote><p>这里以一个查询请求作为实例，MySQL服务端处理请求的流程图如下：</p><div class="mermaid"><svg id="render1868697570" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="751.6907348632812" style="max-width: 455.0078125px;" viewBox="0 0 455.0078125 751.6907348632812"><style>#render1868697570 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render1868697570 .error-icon{fill:#a5a7bb;}#render1868697570 .error-text{fill:#5a5844;stroke:#5a5844;}#render1868697570 .edge-thickness-normal{stroke-width:2px;}#render1868697570 .edge-thickness-thick{stroke-width:3.5px;}#render1868697570 .edge-pattern-solid{stroke-dasharray:0;}#render1868697570 .edge-pattern-dashed{stroke-dasharray:3;}#render1868697570 .edge-pattern-dotted{stroke-dasharray:2;}#render1868697570 .marker{fill:#F8B229;stroke:#F8B229;}#render1868697570 .marker.cross{stroke:#F8B229;}#render1868697570 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render1868697570 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render1868697570 .cluster-label text{fill:#5a5844;}#render1868697570 .cluster-label span{color:#5a5844;}#render1868697570 .label text,#render1868697570 span{fill:#fff;color:#fff;}#render1868697570 .node rect,#render1868697570 .node circle,#render1868697570 .node ellipse,#render1868697570 .node polygon,#render1868697570 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render1868697570 .node .label{text-align:center;}#render1868697570 .node.clickable{cursor:pointer;}#render1868697570 .arrowheadPath{fill:undefined;}#render1868697570 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render1868697570 .flowchart-link{stroke:#F8B229;fill:none;}#render1868697570 .edgeLabel{background-color:#006100;text-align:center;}#render1868697570 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render1868697570 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render1868697570 .cluster text{fill:#5a5844;}#render1868697570 .cluster span{color:#5a5844;}#render1868697570 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render1868697570 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 0)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path d="M227.50390625,42L227.50390625,46.166666666666664C227.50390625,50.333333333333336,227.50390625,58.666666666666664,227.50390625,67C227.50390625,75.33333333333333,227.50390625,83.66666666666667,227.50390625,87.83333333333333L227.50390625,92" id="L-client-mannager-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-client LE-mannager" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M227.50390625,176L227.50390625,180.16666666666666C227.50390625,184.33333333333334,227.50390625,192.66666666666666,227.50390625,201C227.50390625,209.33333333333334,227.50390625,217.66666666666666,227.50390625,221.83333333333334L227.50390625,226" id="L-mannager-analysisAndOptimize-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-mannager LE-analysisAndOptimize" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M227.50390625,478L227.50390625,482.1666666666667C227.50390625,486.3333333333333,227.50390625,494.6666666666667,227.50390625,503C227.50390625,511.3333333333333,227.50390625,519.6666666666666,227.50390625,523.8333333333334L227.50390625,528" id="L-analysisAndOptimize-engine-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-analysisAndOptimize LE-engine" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M227.50390625,659.6907196044922L227.50390625,663.8573862711588C227.50390625,668.0240529378256,227.50390625,676.3573862711588,227.50390625,684.6907196044922C227.50390625,693.0240529378256,227.50390625,701.3573862711588,227.50390625,705.5240529378256L227.50390625,709.6907196044922" id="L-engine-fileSystem-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-engine LE-fileSystem" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="root" transform="translate(0.5, 520)"><g class="clusters"><g class="cluster default" id="engine"><rect style="" rx="0" ry="0" x="8" y="8" width="439.0078125" height="131.6907196044922"></rect><g class="cluster-label" transform="translate(194.50390625, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">存储引擎</tspan></text></g></g></g><g class="edgePaths"><path d="M101.828125,73.8453598022461L105.99479166666667,73.8453598022461C110.16145833333333,73.8453598022461,118.49479166666667,73.8453598022461,126.828125,73.8453598022461C135.16145833333334,73.8453598022461,143.49479166666666,73.8453598022461,147.66145833333334,73.8453598022461L151.828125,73.8453598022461" id="L-MyISAM-InnoDB-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-MyISAM LE-InnoDB" style="fill:none;"></path><path d="M216.21875,73.8453598022461L220.38541666666666,73.8453598022461C224.55208333333334,73.8453598022461,232.88541666666666,73.8453598022461,241.21875,73.8453598022461C249.55208333333334,73.8453598022461,257.8854166666667,73.8453598022461,262.0520833333333,73.8453598022461L266.21875,73.8453598022461" id="L-InnoDB-Memory-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-InnoDB LE-Memory" style="fill:none;"></path><path d="M339.3828125,73.8453598022461L343.5494791666667,73.8453598022461C347.7161458333333,73.8453598022461,356.0494791666667,73.8453598022461,364.3828125,73.8453598022461C372.7161458333333,73.8453598022461,381.0494791666667,73.8453598022461,385.2161458333333,73.8453598022461L389.3828125,73.8453598022461" id="L-Memory-Other-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Memory LE-Other" style="fill:none;"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-Other-463" label-offset-y="5.174464710547185" transform="translate(405.6953125, 73.8453598022461)"><path style="" d="M 0,5.174464710547185 a 16.3125,5.174464710547185 0,0,0 32.625 0 a 16.3125,5.174464710547185 0,0,0 -32.625 0 l 0,39.174464710547184 a 16.3125,5.174464710547185 0,0,0 32.625 0 l 0,-39.174464710547184" transform="translate(-16.3125,-24.761697065820776)"></path><g class="label" style="" transform="translate(-8.8125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">...</tspan></text></g></g><g class="node default default" id="flowchart-MyISAM-460" label-offset-y="8.877468762595727" transform="translate(67.4140625, 73.8453598022461)"><path style="" d="M 0,8.877468762595727 a 34.4140625,8.877468762595727 0,0,0 68.828125 0 a 34.4140625,8.877468762595727 0,0,0 -68.828125 0 l 0,42.877468762595726 a 34.4140625,8.877468762595727 0,0,0 68.828125 0 l 0,-42.877468762595726" transform="translate(-34.4140625,-30.31620314389359)"></path><g class="label" style="" transform="translate(-26.9140625, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">MyISAM</tspan></text></g></g><g class="node default default" id="flowchart-InnoDB-461" label-offset-y="8.499711244946786" transform="translate(184.0234375, 73.8453598022461)"><path style="" d="M 0,8.499711244946786 a 32.1953125,8.499711244946786 0,0,0 64.390625 0 a 32.1953125,8.499711244946786 0,0,0 -64.390625 0 l 0,42.49971124494679 a 32.1953125,8.499711244946786 0,0,0 64.390625 0 l 0,-42.49971124494679" transform="translate(-32.1953125,-29.749566867420178)"></path><g class="label" style="" transform="translate(-24.6953125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">InnoDB</tspan></text></g></g><g class="node default default" id="flowchart-Memory-462" label-offset-y="9.230238517642421" transform="translate(302.80078125, 73.8453598022461)"><path style="" d="M 0,9.230238517642421 a 36.58203125,9.230238517642421 0,0,0 73.1640625 0 a 36.58203125,9.230238517642421 0,0,0 -73.1640625 0 l 0,43.23023851764242 a 36.58203125,9.230238517642421 0,0,0 73.1640625 0 l 0,-43.23023851764242" transform="translate(-36.58203125,-30.84535777646363)"></path><g class="label" style="" transform="translate(-29.08203125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Memory</tspan></text></g></g></g></g><g class="root" transform="translate(144.50390625, 218)"><g class="clusters"><g class="cluster default" id="analysisAndOptimize"><rect style="" rx="0" ry="0" x="8" y="8" width="151" height="252"></rect><g class="cluster-label" transform="translate(34.5, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">解析以及优化</tspan></text></g></g></g><g class="edgePaths"><path d="M83.5,67L83.5,71.16666666666667C83.5,75.33333333333333,83.5,83.66666666666667,83.5,92C83.5,100.33333333333333,83.5,108.66666666666667,83.5,112.83333333333333L83.5,117" id="L-queryCache-analyticGrammar-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-queryCache LE-analyticGrammar" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M83.5,151L83.5,155.16666666666666C83.5,159.33333333333334,83.5,167.66666666666666,83.5,176C83.5,184.33333333333334,83.5,192.66666666666666,83.5,196.83333333333334L83.5,201" id="L-analyticGrammar-optimizedQuery-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-analyticGrammar LE-optimizedQuery" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-optimizedQuery-457" transform="translate(83.5, 218)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40" y="-17" width="80" height="34"></rect><g class="label" style="" transform="translate(-32.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">优化查询</tspan></text></g></g><g class="node default default" id="flowchart-queryCache-455" transform="translate(83.5, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">查询缓存</tspan></text></g></g><g class="node default default" id="flowchart-analyticGrammar-456" transform="translate(83.5, 134)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40" y="-17" width="80" height="34"></rect><g class="label" style="" transform="translate(-32.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">解析语法</tspan></text></g></g></g></g><g class="root" transform="translate(145.00390625, 84)"><g class="clusters"><g class="cluster default" id="mannager"><rect style="" rx="0" ry="0" x="8" y="8" width="150" height="84"></rect><g class="cluster-label" transform="translate(50, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">连接管理</tspan></text></g></g></g><g class="edgePaths"></g><g class="edgeLabels"></g><g class="nodes"><g class="node default default" id="flowchart-handleConnection-452" transform="translate(83, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40" y="-17" width="80" height="34"></rect><g class="label" style="" transform="translate(-32.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">处理连接</tspan></text></g></g></g></g><g class="node default default" id="flowchart-client-450" transform="translate(227.50390625, 25)"><rect style="" rx="17" ry="17" x="-36.75" y="-17" width="73.5" height="34"></rect><g class="label" style="" transform="translate(-25, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">客户端</tspan></text></g></g><g class="node default default" id="flowchart-fileSystem-465" transform="translate(227.50390625, 726.6907196044922)"><rect style="" rx="17" ry="17" x="-44.25" y="-17" width="88.5" height="34"></rect><g class="label" style="" transform="translate(-32.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">文件系统</tspan></text></g></g></g></g></g></svg></div><blockquote><p>上面我们列举了一个<strong>MySQL</strong>查询的流程图，那么<strong>MySQL</strong>的具体的逻辑架构图如下：</p></blockquote><p><img src="/upload/2023/04/MySQL%E9%80%BB%E8%BE%91%E6%9E%B6%E6%9E%84.png" alt="MySQL逻辑架构" /></p><h2 id="%E8%BF%9E%E6%8E%A5%E5%99%A8%EF%BC%88connectors)" tabindex="-1">连接器（Connectors)</h2><p><code>Connectors</code>指的就是不同编程语言中和<strong>SQL</strong>的交互。<strong>MySQL</strong>首先是一个网络应用程序，在<strong>TCP</strong>之上定义了自己的应用层协议。所以要使用<strong>MySQL</strong>，我们可以编写代码和<strong>MySQL</strong><mark>建立TCP连接</mark>，之后按照其定义好的协议进行交互。或者比较方便的办法就是调用<strong>SDK</strong>，比如<code>Native API</code>、<code>JDBC</code>、<code>PHP</code>等各个语言的<strong>MySQL Connector</strong>，或者通过<code>ODBC</code>。<mark>但是通过SDK来访问MySQL，本质上还是在TCP连接上通过MySQL的协议来和其交互</mark>。</p><h2 id="mysql%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%BB%93%E6%9E%84" tabindex="-1">MySQL服务端结构</h2><blockquote><p>从上面的图中我们可以看到<strong>MySQL</strong>服务端的结构大致可以分为连接层、服务层以及引擎层这三层。</p></blockquote><h3 id="%E8%BF%9E%E6%8E%A5%E5%B1%82" tabindex="-1">连接层</h3><p>客户端访问<strong>MySQL</strong>服务器之前，首先第一件事就是建立TCP连接，当经过三次握手连接建立成功后，<strong>MySQL</strong>服务器对TCP传输过来的账号密码做身份认证、权限获取。</p><ul><li><strong>用户名或密码不对</strong>：客户端会收到一个<code>Access denied for user</code>错误，客户端程序结束执行。</li><li><strong>用户名和密码正确</strong>：会从权限表查出账号拥有的权限和连接关联，之后的权限判断逻辑，都将依赖于此时读到的权限。</li></ul><blockquote><p>那么一个系统只能和<strong>MySQL</strong>服务建立一个连接吗？只能有一个系统和<strong>MySQL</strong>服务建立连接吗？</p></blockquote><p>答案是否定的，多个系统都可以和MySQL建立连接，每个系统建立的连接当然也不只是一个。所以为了解决TCP连接的无限创建和TCP频繁创建销毁带来的资源开销以及性能下降等问题，MySQL服务使用了<mark>TCP连接池</mark>来限制连接数量，采用长连接模式复用TCP连接。</p><div class="mermaid"><svg id="render3439694684" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="299" style="max-width: 1165.171875px;" viewBox="0 0 1165.171875 299"><style>#render3439694684 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render3439694684 .error-icon{fill:#a5a7bb;}#render3439694684 .error-text{fill:#5a5844;stroke:#5a5844;}#render3439694684 .edge-thickness-normal{stroke-width:2px;}#render3439694684 .edge-thickness-thick{stroke-width:3.5px;}#render3439694684 .edge-pattern-solid{stroke-dasharray:0;}#render3439694684 .edge-pattern-dashed{stroke-dasharray:3;}#render3439694684 .edge-pattern-dotted{stroke-dasharray:2;}#render3439694684 .marker{fill:#F8B229;stroke:#F8B229;}#render3439694684 .marker.cross{stroke:#F8B229;}#render3439694684 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render3439694684 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render3439694684 .cluster-label text{fill:#5a5844;}#render3439694684 .cluster-label span{color:#5a5844;}#render3439694684 .label text,#render3439694684 span{fill:#fff;color:#fff;}#render3439694684 .node rect,#render3439694684 .node circle,#render3439694684 .node ellipse,#render3439694684 .node polygon,#render3439694684 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render3439694684 .node .label{text-align:center;}#render3439694684 .node.clickable{cursor:pointer;}#render3439694684 .arrowheadPath{fill:undefined;}#render3439694684 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render3439694684 .flowchart-link{stroke:#F8B229;fill:none;}#render3439694684 .edgeLabel{background-color:#006100;text-align:center;}#render3439694684 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render3439694684 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render3439694684 .cluster text{fill:#5a5844;}#render3439694684 .cluster span{color:#5a5844;}#render3439694684 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render3439694684 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 0)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path d="M582.5859375,42L582.5859375,46.166666666666664C582.5859375,50.333333333333336,582.5859375,58.666666666666664,582.5859375,67C582.5859375,75.33333333333333,582.5859375,83.66666666666667,582.5859375,87.83333333333333L582.5859375,92" id="L-Client-connector-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Client LE-connector" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="root" transform="translate(0.5, 84)"><g class="clusters"><g class="cluster default" id="connector"><rect style="" rx="0" ry="0" x="8" y="8" width="1149.171875" height="199"></rect><g class="cluster-label" transform="translate(557.5859375, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">连接层</tspan></text></g></g></g><g class="edgePaths"><path d="M503.546875,107.5L507.7135416666667,107.5C511.8802083333333,107.5,520.2135416666666,107.5,528.546875,107.5C536.8802083333334,107.5,545.2135416666666,107.5,549.3802083333334,107.5L553.546875,107.5" id="L-TCPPool-ThreadPool-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-TCPPool LE-ThreadPool" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M953.171875,107.5L957.3385416666666,107.5C961.5052083333334,107.5,969.8385416666666,107.5,978.171875,107.5C986.5052083333334,107.5,994.8385416666666,107.5,999.0052083333334,107.5L1003.171875,107.5" id="L-ThreadPool-Operate-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-ThreadPool LE-Operate" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="root" transform="translate(25.5, 57.5)"><g class="clusters"><g class="cluster default" id="TCPPool"><rect style="" rx="0" ry="0" x="8" y="8" width="470.546875" height="84"></rect><g class="cluster-label" transform="translate(204.3828125, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">TCP连接池</tspan></text></g></g></g><g class="edgePaths"></g><g class="edgeLabels"></g><g class="nodes"><g class="node default default" id="flowchart-connect1-486" transform="translate(79.3203125, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-36.3203125" y="-17" width="72.640625" height="34"></rect><g class="label" style="" transform="translate(-28.8203125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">connect</tspan></text></g></g><g class="node default default" id="flowchart-connect2-487" transform="translate(201.9609375, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-36.3203125" y="-17" width="72.640625" height="34"></rect><g class="label" style="" transform="translate(-28.8203125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">connect</tspan></text></g></g><g class="node default default" id="flowchart-connect-488" transform="translate(304.59375, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-16.3125" y="-17" width="32.625" height="34"></rect><g class="label" style="" transform="translate(-8.8125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">...</tspan></text></g></g><g class="node default default" id="flowchart-connectN-489" transform="translate(407.2265625, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-36.3203125" y="-17" width="72.640625" height="34"></rect><g class="label" style="" transform="translate(-28.8203125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">connect</tspan></text></g></g></g></g><g class="root" transform="translate(546.046875, 57.5)"><g class="clusters"><g class="cluster default" id="ThreadPool"><rect style="" rx="0" ry="0" x="8" y="8" width="399.625" height="84"></rect><g class="cluster-label" transform="translate(182.8125, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">线程池</tspan></text></g></g></g><g class="edgePaths"></g><g class="edgeLabels"></g><g class="nodes"><g class="node default default" id="flowchart-thread1-490" transform="translate(67.5, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-24.5" y="-17" width="49" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">线程</tspan></text></g></g><g class="node default default" id="flowchart-thread2-491" transform="translate(166.5, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-24.5" y="-17" width="49" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">线程</tspan></text></g></g><g class="node default default" id="flowchart-thread-492" transform="translate(257.3125, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-16.3125" y="-17" width="32.625" height="34"></rect><g class="label" style="" transform="translate(-8.8125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">...</tspan></text></g></g><g class="node default default" id="flowchart-threadN-493" transform="translate(348.125, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-24.5" y="-17" width="49" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">线程</tspan></text></g></g></g></g><g class="node default default" id="flowchart-Operate-496" transform="translate(1067.671875, 107.5)"><circle style="" rx="0" ry="0" r="64.5" width="129" height="34"></circle><g class="label" style="" transform="translate(-57, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">认证、权限获取</tspan></text></g></g></g></g><g class="node default default" id="flowchart-Client-483" transform="translate(582.5859375, 25)"><rect style="" rx="17" ry="17" x="-36.75" y="-17" width="73.5" height="34"></rect><g class="label" style="" transform="translate(-25, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">客户端</tspan></text></g></g></g></g></g></svg></div><p>TCP连接收到请求后，必须要分配一个线程专门和客户端交互。所以还会有一个线程池，来完成后面的流程。每个连接从线程池中获取线程，节省了创建和销毁线程的开销。</p><p>以上的内容都可以归纳到<strong>MySQl</strong>的<strong>连接层</strong>中。所以<strong>连接层</strong>的职责就是负责认证、管理连接、获取权限信息。</p><h3 id="%E6%9C%8D%E5%8A%A1%E5%B1%82" tabindex="-1">服务层</h3><p>服务层主要是来完成大多数的核心功能的，比如SQL接口、查询缓存、SQL的分析和优化以及部分内置函数的执行。所有跨存储引擎的功能也在服务层实现，比如过程以及函数等。</p><p>在服务层中，<strong>MySQL</strong>服务会<mark>解析查询</mark>并且创建相应的<mark>内部解析树</mark>，对其完成相应的优化：比如确定查询表的顺序，是否利用索引等，最后生成相应的执行操作。</p><p>如果是<code>SELECT</code>语句，服务器还会<mark>查询内部的缓存</mark>。如果缓存空间足够大，这样在解决大量读操作的环境中能很好的提升性能。</p><h4 id="sql-interface%EF%BC%88sql%E6%8E%A5%E5%8F%A3%EF%BC%89" tabindex="-1">SQL Interface（SQL接口）</h4><ul><li>接收用户的SQL命令，并且返回用户需要查询的结构。比如<code>SELECT...FROM...</code>就是调用的<code>SQL Interface</code>。</li><li>MySQL支持DML（数据操作语言）、DDL（数据定义语言）、存储过程、视图、触发器、自定义函数等多种SQL语言接口。</li></ul><h4 id="parser%EF%BC%88%E8%A7%A3%E6%9E%90%E5%99%A8%EF%BC%89" tabindex="-1">Parser（解析器）</h4><ul><li>在解析器中对SQL语句进行语法分析、语义分析。将SQL语句分解成数据结构，并将这个结构传递到后续步骤，以后SQL语句的传递和处理就是基于这个结构的。如果在分解构成中遇到错误，那么就说明这个SQL语句是不合理的。</li><li>在SQL命令传递到解析器的时候会被解析器验证和解析，并为其创建<mark>语法树</mark>，并根据数据字典丰富查询语法树，会<mark>验证该客户端是否具有执行该查询的权限</mark>。创建好语法后，MySQL还会对SQL查询进行语法上的优化，进行查询重写。</li></ul><h4 id="optimizer%EF%BC%88%E6%9F%A5%E8%AF%A2%E4%BC%98%E5%8C%96%E5%99%A8%EF%BC%89" tabindex="-1">Optimizer（查询优化器）</h4><ul><li><p>SQL语句在语法解析之后、查询之前会使用<code>Optimizer</code>（查询优化器）来确定SQL语句的执行路径，生成一个<mark>执行计划</mark>。</p></li><li><p>这个执行计划表明应该<mark>使用哪些索引</mark>来查询（全表检索或索引检索）、表之间的连接顺序如何。最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询，并且将结果返回给用户。</p></li><li><p>使用<code>选取-投影-连接</code>策略进行查询。比如：</p><pre><code class="language-sql">SELECT id,name FROM t_user WHERE age = 18;</code></pre><ol><li><code>选取</code>：先根据<code>WHERE</code>进行<code>选取</code>，而不是将表全部查询出来以后再过滤。</li><li><code>投影</code>：先根据<code>id</code>和<code>name</code>进行属性<code>投影</code>，而不是将属性全部查询出来之后再过滤掉。</li><li><code>连接</code>：将上面两个条件<code>连接</code>起来生成最终的查询结果。</li></ol></li></ul><h4 id="caches-%26-buffers%EF%BC%88%E7%BC%93%E5%AD%98%E7%BB%84%E4%BB%B6%EF%BC%89" tabindex="-1">Caches &amp; Buffers（缓存组件）</h4><ul><li>MySQL内部维护着一些<code>Cache</code>和<code>Buffer</code>，比如<code>Query Cache</code>用来缓存一条<code>SELECT</code>语句的执行结果。如果能够在其中找到对应的查询结果，那么就不必再进行查询解析、优化以及执行的整个过程了，会直接将结果返回给客户端。</li><li>这个缓存机制是由一系列小缓存组成的：比如表缓存、记录缓存、key缓存、权限缓存等等。</li><li>缓存是可以<mark>在不同的客户端之间共享的</mark>。</li></ul><blockquote><p>🚨从<code>MySQL5.7.20</code>开始，不再推荐使用查询缓存，并且在<code>MySQL8.0</code>中删除了这个机制。</p></blockquote><h3 id="%E5%BC%95%E6%93%8E%E5%B1%82" tabindex="-1">引擎层</h3><p>和其他的数据库相比，<strong>MySQL</strong>有一点与众不同，它的架构可以在多种不同的场景中应用并且发挥良好的作用，主要体现在存储引擎的架构上，<mark>插件式的存储引擎</mark>架构将查询处理和其他的系统任务以及数据的存储相分离。这种架构可以根据业务的需求以及实际需要选择适合的存储引擎。同时开源的<strong>MySQL</strong>还<mark>允许开发人员开发自己的存储引擎</mark>。</p><p>插件式的存储引擎层，<mark>真正地负责了<strong>MySQL</strong>中数据地存储和提取，对物理服务器级别维护的底层数据执行操作</mark>。服务器通过API与存储引擎进行通信，而不同的存储引擎具有的功能不同，这样我们可以根据自己的实际需要进行选取。</p><p>在<code>MySQL8.0.31</code>中默认支持的存储引擎如下：</p><pre><code class="language-mysql">mysql&gt; SHOW ENGINES;+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+| Engine             | Support | Comment                                                        | Transactions | XA   | Savepoints |+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+| ndbcluster         | NO      | Clustered, fault-tolerant tables                               | NULL         | NULL | NULL       || FEDERATED          | NO      | Federated MySQL storage engine                                 | NULL         | NULL | NULL       || MEMORY             | YES     | Hash based, stored in memory, useful for temporary tables      | NO           | NO   | NO         || InnoDB             | DEFAULT | Supports transactions, row-level locking, and foreign keys     | YES          | YES  | YES        || PERFORMANCE_SCHEMA | YES     | Performance Schema                                             | NO           | NO   | NO         || MyISAM             | YES     | MyISAM storage engine                                          | NO           | NO   | NO         || ndbinfo            | NO      | MySQL Cluster system information storage engine                | NULL         | NULL | NULL       || MRG_MYISAM         | YES     | Collection of identical MyISAM tables                          | NO           | NO   | NO         || BLACKHOLE          | YES     | /dev/null storage engine (anything you write to it disappears) | NO           | NO   | NO         || CSV                | YES     | CSV storage engine                                             | NO           | NO   | NO         || ARCHIVE            | YES     | Archive storage engine                                         | NO           | NO   | NO         |+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+11 rows in set (0.00 sec)</code></pre><h2 id="%E5%AD%98%E5%82%A8%E5%B1%82" tabindex="-1">存储层</h2><p>所有的数据、数据库/表的定义、表的每一行内容、索引都是存在<mark>文件系统</mark>上的，以<mark>文件</mark>的方式存在的，并完成与存储引擎的交互。当然有些存储引擎比如InnoDB，也支持不使用文件系统直接管理裸设备，但现代文件系统的实现使这样做没有必要了。在文件系统之下，可以使用本地磁盘，也可以使用<code>DAS</code>、<code>NAS</code>、<code>SAN</code>等各种存储系统。</p><h2 id="%E6%80%BB%E7%BB%93" tabindex="-1">总结</h2><p>开篇提到的架构图可以简化为下面这样：</p><div class="mermaid"><svg id="render4208966095" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="422" style="max-width: 381px;" viewBox="0 0 381 422"><style>#render4208966095 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render4208966095 .error-icon{fill:#a5a7bb;}#render4208966095 .error-text{fill:#5a5844;stroke:#5a5844;}#render4208966095 .edge-thickness-normal{stroke-width:2px;}#render4208966095 .edge-thickness-thick{stroke-width:3.5px;}#render4208966095 .edge-pattern-solid{stroke-dasharray:0;}#render4208966095 .edge-pattern-dashed{stroke-dasharray:3;}#render4208966095 .edge-pattern-dotted{stroke-dasharray:2;}#render4208966095 .marker{fill:#F8B229;stroke:#F8B229;}#render4208966095 .marker.cross{stroke:#F8B229;}#render4208966095 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render4208966095 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render4208966095 .cluster-label text{fill:#5a5844;}#render4208966095 .cluster-label span{color:#5a5844;}#render4208966095 .label text,#render4208966095 span{fill:#fff;color:#fff;}#render4208966095 .node rect,#render4208966095 .node circle,#render4208966095 .node ellipse,#render4208966095 .node polygon,#render4208966095 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render4208966095 .node .label{text-align:center;}#render4208966095 .node.clickable{cursor:pointer;}#render4208966095 .arrowheadPath{fill:undefined;}#render4208966095 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render4208966095 .flowchart-link{stroke:#F8B229;fill:none;}#render4208966095 .edgeLabel{background-color:#006100;text-align:center;}#render4208966095 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render4208966095 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render4208966095 .cluster text{fill:#5a5844;}#render4208966095 .cluster span{color:#5a5844;}#render4208966095 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render4208966095 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 0)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path d="M60.5,42L60.5,46.166666666666664C60.5,50.333333333333336,60.5,58.666666666666664,63.41218637992832,67C66.32437275985663,75.33333333333333,72.14874551971326,83.66666666666667,75.06093189964157,87.83333333333333L77.97311827956989,92" id="L-Client1-mysqlServer-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Client1 LE-mysqlServer" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M190.5,42L190.5,46.166666666666664C190.5,50.333333333333336,190.5,58.666666666666664,190.5,67C190.5,75.33333333333333,190.5,83.66666666666667,190.5,87.83333333333333L190.5,92" id="L-Client2-mysqlServer-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Client2 LE-mysqlServer" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M320.5,42L320.5,46.166666666666664C320.5,50.333333333333336,320.5,58.666666666666664,317.5878136200717,67C314.67562724014334,75.33333333333333,308.85125448028674,83.66666666666667,305.9390681003584,87.83333333333333L303.0268817204301,92" id="L-Client3-mysqlServer-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Client3 LE-mysqlServer" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="root" transform="translate(0.5, 84)"><g class="clusters"><g class="cluster default" id="mysqlServer"><rect style="" rx="0" ry="0" x="8" y="8" width="365" height="322"></rect><g class="cluster-label" transform="translate(153.82421875, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Mysql服务</tspan></text></g></g></g><g class="edgePaths"><path d="M190.5,67L190.5,71.16666666666667C190.5,75.33333333333333,190.5,83.66666666666667,190.5,92C190.5,100.33333333333333,190.5,108.66666666666667,190.5,112.83333333333333L190.5,117" id="L-connector-sql-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-connector LE-sql" style="fill:none;"></path><path d="M190.5,151L190.5,155.16666666666666C190.5,159.33333333333334,190.5,167.66666666666666,190.5,176C190.5,184.33333333333334,190.5,192.66666666666666,190.5,196.83333333333334L190.5,201" id="L-sql-engine-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-sql LE-engine" style="fill:none;"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="root" transform="translate(35.5, 193)"><g class="clusters"><g class="cluster default" id="engine"><rect style="" rx="0" ry="0" x="8" y="8" width="295" height="104"></rect><g class="cluster-label" transform="translate(114.5, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">存储引擎层</tspan></text></g></g></g><g class="edgePaths"><path d="M82,60L86.16666666666667,60C90.33333333333333,60,98.66666666666667,60,107,60C115.33333333333333,60,123.66666666666667,60,127.83333333333333,60L132,60" id="L-disk-ram-0" class=" edge-thickness-normal edge-pattern-dotted flowchart-link LS-disk LE-ram" style="fill:none;stroke-width:2px;stroke-dasharray:3;"></path><path d="M180,60L184.16666666666666,60C188.33333333333334,60,196.66666666666666,60,205,60C213.33333333333334,60,221.66666666666666,60,225.83333333333334,60L230,60" id="L-ram-net-0" class=" edge-thickness-normal edge-pattern-dotted flowchart-link LS-ram LE-net" style="fill:none;stroke-width:2px;stroke-dasharray:3;"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-net-523" transform="translate(254, 60)"><rect class="basic label-container" style="" rx="0" ry="0" x="-24" y="-17" width="48" height="34"></rect><g class="label" style="" transform="translate(-16.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">网络</tspan></text></g></g><g class="node default default" id="flowchart-disk-521" transform="translate(57.5, 60)"><rect class="basic label-container" style="" rx="0" ry="0" x="-24.5" y="-17" width="49" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">磁盘</tspan></text></g></g><g class="node default default" id="flowchart-ram-522" transform="translate(156, 60)"><rect class="basic label-container" style="" rx="0" ry="0" x="-24" y="-17" width="48" height="34"></rect><g class="label" style="" transform="translate(-16.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">内存</tspan></text></g></g></g></g><g class="node default default" id="flowchart-connector-518" transform="translate(190.5, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-32.5" y="-17" width="65" height="34"></rect><g class="label" style="" transform="translate(-25, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">连接层</tspan></text></g></g><g class="node default default" id="flowchart-sql-519" transform="translate(190.5, 134)"><rect class="basic label-container" style="" rx="0" ry="0" x="-29.3046875" y="-17" width="58.609375" height="34"></rect><g class="label" style="" transform="translate(-21.8046875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">SQL层</tspan></text></g></g></g></g><g class="node default default" id="flowchart-Client1-512" transform="translate(60.5, 25)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40" y="-17" width="80" height="34"></rect><g class="label" style="" transform="translate(-32.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">应用程序</tspan></text></g></g><g class="node default default" id="flowchart-Client2-514" transform="translate(190.5, 25)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40" y="-17" width="80" height="34"></rect><g class="label" style="" transform="translate(-32.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">应用程序</tspan></text></g></g><g class="node default default" id="flowchart-Client3-516" transform="translate(320.5, 25)"><rect class="basic label-container" style="" rx="0" ry="0" x="-40" y="-17" width="80" height="34"></rect><g class="label" style="" transform="translate(-32.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">应用程序</tspan></text></g></g></g></g></g></svg></div>]]>
                    </description>
                    <pubDate>Wed, 10 Aug 2022 21:28:12 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Sentinel 学习笔记]]>
                    </title>
                    <link>https://zygzyg.com/archives/a6d79c0bb7814a2e94aa8927a86710e0</link>
                    <description>
                            <![CDATA[<h1 id="sentinel-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0" tabindex="-1">Sentinel 学习笔记</h1><p>随着微服务的流行，服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式、多语言异构化服务架构的流量治理组件，主要以流量为切入点，从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性。</p><h2 id="%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5" tabindex="-1">基本概念</h2><h3 id="%E8%B5%84%E6%BA%90" tabindex="-1"><strong>资源</strong></h3><p>资源是 <a href="https://sentinelguard.io/zh-cn/docs/introduction.html" target="_blank">Sentinel </a>的关键概念。它可以是 Java 应用程序中的任何内容，例如，由应用程序提供的服务，或由应用程序调用的其它应用提供的服务，甚至可以是一段代码。只要通过 Sentinel API 定义的代码，就是资源，能够被 Sentinel 保护起来。大部分情况下，可以使用方法签名，URL，甚至服务名称作为资源名来标示资源。</p><h3 id="%E8%A7%84%E5%88%99" tabindex="-1"><strong>规则</strong></h3><p>围绕资源的实时状态设定的规则，可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。</p><h2 id="sentinel%E7%9A%84%E7%BB%84%E4%BB%B6%E7%94%B1%E4%B8%A4%E4%B8%AA%E9%83%A8%E5%88%86%E7%BB%84%E6%88%90" tabindex="-1">Sentinel的组件由两个部分组成</h2><h3 id="%E6%A0%B8%E5%BF%83%E5%BA%93%EF%BC%88%E5%AE%A2%E6%88%B7%E7%AB%AF%EF%BC%89" tabindex="-1">核心库（客户端）</h3><p>不依赖任何框架/库，能够运行于所有的Java环境，同时对Dubbo/SpringCloud等框架也有较好的支持。</p><h3 id="%E6%8E%A7%E5%88%B6%E5%8F%B0%EF%BC%88dashboard%EF%BC%89" tabindex="-1">控制台（Dashboard）</h3><p>基于SpringBoot开发，打包后可以直接运行，不需要额外的Tomcat等应用容器。</p><h2 id="%E6%B5%81%E6%8E%A7%E8%A7%84%E5%88%99" tabindex="-1">流控规则</h2><ul><li>资源名：唯一名称，默认请求路径。</li><li>针对来源：Sentinel可以针对调用者进行限流，填写微服务名，默认为default（不区分来源）。</li><li>阈值类型/单机阈值：<ul><li>QPS（每秒的请求数量）：当调用该API的QPS达到阈值的时候，进行限流。</li><li>线程数：当调用该API的线程数达到阈值的时候，进行限流。</li></ul></li><li>是否集群：不需要集群</li><li>流控模式：<ul><li>直接：API达到限流条件的时候，直接限流。</li><li>关联：当关联的资源达到阈值的时候，就限流自己。</li><li>链路：只记录指定链路上的流量（指定资源从入口资源进来的流量，如果达到阈值，就进行限流）【API级别的针对来源】。</li></ul></li><li>流控效果：<ul><li>快速失败：直接失败，抛异常。</li><li>Warm Up：根据codeFactor（冷加载因子，默认3）的值，从阈值/codeFactor，经过预热时长。大达到设置的QPS阈值。</li><li>排队等候：匀速排队，让请求以匀速的速度通过，阈值类型必须设置为QPS，否则无效。</li></ul></li></ul><h2 id="%E9%99%8D%E7%BA%A7%E8%A7%84%E5%88%99" tabindex="-1">降级规则</h2><ul><li>RT（平均响应时间，秒级）：平均响应时间超出阈值且在时间窗口内通过的请求&gt;=5，两个条件同时满足后触发降级。窗口期过后关闭断路器，RT最大4900（更大的需要通过<code>-Dcsp.sentinel.statistic.max.rt=xxxx</code> 才能生效）。</li><li>异常比例（秒级）：QPS&gt;=5异常比例（秒级统计）超过阈值时，触发降级；时间窗口结束后，关闭降级。</li><li>异常数（分钟级）：异常数（分钟统计）超过阈值时，触发降级；时间窗口结束后，关闭降级。</li></ul><p>Sentinel熔断降级会在调用链路中的某个资源出现不稳定状态时（例如调用超时或异常比例升高），对这个资源的调用进行限制，让请求快速失败，避免影响到其他的资源而导致级联错误。当资源被降级后，在接下来的降级时间窗口之内，对该资源的调用都自动熔断（默认行为时抛出<code>DegradeException</code>）。Sentinel的断路器是没有半开状态的。</p><blockquote><p>半开状态即系统自动去检测是否请求有异常，没有异常就关闭断路器恢复使用，有异常则继续打开断路器。</p></blockquote>]]>
                    </description>
                    <pubDate>Wed, 20 Jul 2022 13:21:38 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[ Redisson]]>
                    </title>
                    <link>https://zygzyg.com/archives/6bad2c7d205d4b74aed785a370e13609</link>
                    <description>
                            <![CDATA[<h1 id="redisson" tabindex="-1">Redisson</h1><p>首先是来自<a href="https://redis.io/docs/manual/patterns/distributed-locks/" target="_blank">Redis官网</a>的分布式锁的说明：</p><blockquote><p>在不同进程必须以互斥的方式使用共享资源的环境中，分布式锁是一个非常有用的元素。</p><p>有许多库和博客文章描述如何使用Redis实现DLM（分布式锁管理器），但每个库都使用不同的方法，并且许多库使用简单的方法。与稍微复杂的方法相比，保证较低的设计。</p><p>我们提出了一种名为Redlock的算法，它实现了一个DLM，我们认为比普通的单实例方法更安全。我们希望社区能够对其进行分析、提供反馈，并将其用作实现更复杂或替代设计的起点。</p></blockquote><h2 id="%E4%B8%BA%E4%BB%80%E4%B9%88%E5%9F%BA%E4%BA%8E%E6%95%85%E9%9A%9C%E8%BD%AC%E7%A7%BB%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%98%AF%E4%B8%8D%E5%A4%9F%E7%9A%84%EF%BC%9F" tabindex="-1">为什么基于故障转移的实现是不够的？</h2><blockquote><p>在<a href="archives/redis%E5%AE%9E%E7%8E%B0%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" target="_blank">《Redis实现分布式锁》</a>这篇文章中，自己实现了一个分布式锁，但是还是存在一定的问题。<a href="https://redis.io/docs/manual/patterns/distributed-locks/#why-failover-based-implementations-are-not-enough" target="_blank">Redis官网</a>是这样说的。</p></blockquote><p>让我们分析一下大多数基于Redis的分布式锁库的当前情况：</p><p>使用Redis锁定资源的最简单方法是在实例中创建一个键。通常使用Redis过期功能创建带有有限存活时间的键，以便最终会释放它。当客户端需要释放资源时，它会删除该键。</p><p>表面上这很有效，但存在一个问题：就是架构中的单点故障。如果Redis主服务器崩溃会怎样呢？那就添加一个副本！并在主服务器不可用时使用它。但不幸的是，通过这样做，我们无法实现互斥的安全属性，<mark>因为Redis复制是异步的</mark>。</p><p>这个模型存在竞争条件：</p><ol><li>客户端A在主服务器上获取锁。</li><li>写入键的过程在传输到副本之前，主服务器崩溃了。</li><li>副本被提升为主服务器。</li><li>客户端B获取了与客户端A持有的同一资源的锁。<strong>安全性被破坏！</strong></li></ol><p>有时，在特殊情况下，例如在出现故障时，多个客户端同时持有锁是完全可以接受的。如果是这种情况，则可以使用基于复制的解决方案。否则，我们建议实现本文档中描述的解决方案。</p><p>简单说就是：线程A首先获取锁成功了，将键写入了Redis的Master，但是在Master同步数据到Slave之前，Master宕机了。这个时候触发了故障转移，Slave成为新的Master（但是这个Slave并没有之前线程A写入的键），此时线程B来了，尝试获取锁，因为Slave中没有之前线程A写入的键，所以这里线程B理所当然的获取到了锁。那么这时问题出现了，线程A和线程B同时获取到了锁。</p><h2 id="redlock%E7%9A%84%E7%AE%97%E6%B3%95%E8%AE%BE%E8%AE%A1%E7%90%86%E5%BF%B5" tabindex="-1">RedLock的算法设计理念</h2><p>Redis提供了RedLock算法，用来实现基于多个实例的分布式锁。锁变量由多个实例维护，即使有实例发生了故障，锁变量任然时存在的，客户端还是可以完成锁操作。RedLock算法是实现高可靠分布式锁的一种有效解决方案，可以在实际开发中使用。</p><p><a href="https://redis.io/docs/manual/patterns/distributed-locks/#the-redlock-algorithm" target="_blank">官网的介绍</a>是：</p><p>在算法的分布式版本中，我们假设有N个Redis主节点。这些节点是完全独立的，因此我们不使用复制或任何其他隐式协调系统。我们已经描述了如何在单个实例中安全地获取和释放锁。我们认为算法将使用此方法在单个实例中获取和释放锁。在我们的示例中，我们将N设置为5，这是一个合理的值，因此我们需要在不同的计算机或虚拟机上运行5个Redis主节点，以确保它们以几乎独立的方式发生故障。</p><h3 id="%E8%AE%BE%E8%AE%A1%E7%90%86%E5%BF%B5" tabindex="-1">设计理念</h3><p>该方案也是基于（<code>SET</code>加锁、Lua脚本解锁）进行改良的，所以大致方案如下：</p><p>假设有<strong>N</strong>个Redis主节点，例如<strong>N = 5</strong>，这些节点都是完全独立的，不使用复制或者任何其他隐式协调系统，为了获取到锁，客户端执行以下操作：</p><ol><li>以毫秒为单位获取当前时间。</li><li>顺序尝试在所有N个实例中获取锁，使用相同的键名和随机值。在第2步中，设置每个实例中的锁时，客户端使用一个相对于总锁自动释放时间很小的超时时间来获取锁。例如，如果自动释放时间为10秒，则超时时间可以在 [5,50]毫秒范围内。这可以防止客户端尝试与已经宕机的Redis节点通信时长时间被阻塞：如果一个实例不可用，我们应该尽快尝试与下一个实例通信。</li><li>客户端通过从步骤1中获取的时间戳中减去当前时间来计算获得锁所用的时间。当且仅当客户端能够在大多数实例（至少3个）中获得锁时，并且获得锁所用的总时间少于锁的有效时间时，才认为已经获得了锁。</li><li>如果成功获得了锁，则其有效时间被视为初始有效时间减去步骤3中计算出的时间。</li><li>如果客户端由于某种原因未能获得锁（无法锁定N/2+1实例或有效时间为负），则将尝试解锁所有实例（即使客户端认为自己无法锁定某些实例）。</li></ol><p>该方案是为了解决数据不一致的问题，直接舍弃了异步复制而只是用Master节点，同时因为舍弃了Slave，又为了保证可用性，引入了N个节点。</p><h2 id="redisson%E4%BB%8B%E7%BB%8D" tabindex="-1">Redisson介绍</h2><p><a href="https://github.com/redisson/redisson" target="_blank">Redisson</a>是Java的Redis客户端之一，提供了一些API来方便的操作。它是一个在Redis的基础上实现的Java驻内存数据网格（In-Memory Data Grid）。它不仅提供了一系列的分布式的Java常用对象，还提供了许多分布式服务。Redisson的宗旨是促进使用者对Redis的关注分离（Separation of Concern），从而让使用者能够将精力更集中地放在处理业务逻辑上。<strong>一个基于Redis实现的分布式工具，有基本分布式对象和高级又抽象的分布式服务，为每个试图再造分布式轮子的程序员带来了大部分分布式问题的解决办法。</strong></p><h3 id="%E7%AE%80%E5%8D%95%E4%BD%BF%E7%94%A8redisson%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" tabindex="-1">简单使用Redisson的分布式锁</h3><p><a href="https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8" target="_blank">GitHub上关于分布式锁的介绍</a></p><h4 id="%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E9%85%8D%E7%BD%AE%E7%B1%BB" tabindex="-1">创建一个配置类</h4><pre><code class="language-java">@Configurationpublic class RedissionConfig {    @Bean    public RedissonClient getRedisson() {        Config config = new Config();        config.useSingleServer().                setAddress(&quot;redis://127.0.0.1:6371&quot;).                setPassword(&quot;123456&quot;);        config.setCodec(new JsonJacksonCodec());        return Redisson.create(config);    }}</code></pre><h4 id="%E5%9C%A8%E4%B8%9A%E5%8A%A1%E4%BB%A3%E7%A0%81%E4%B8%AD%E4%BD%BF%E7%94%A8" tabindex="-1">在业务代码中使用</h4><pre><code class="language-java">@Resourceprivate RedissonClient redissonClient;@Overridepublic void deduct(Long id) {    RLock lock = redisson.getLock(&quot;LockName&quot;);    try{        lock.lock();        //抢到锁则执行业务    }catch (Exception exception){        exception.printStackTrace();    }finally {        lock.unlock();    }}</code></pre><h2 id="redisson%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90" tabindex="-1">Redisson分布式锁源码分析</h2><blockquote><p>这里可以先看看自己基于<a href="https://zygzyg.cloud/archives/redis%E5%AE%9E%E7%8E%B0%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" target="_blank">Redis实现一个简单的分布式锁</a>。</p></blockquote><h3 id="%E8%87%AA%E5%8A%A8%E7%BB%AD%E6%9C%9F" tabindex="-1">自动续期</h3><blockquote><p>如果Redis上的分布式锁过期了，但是业务逻辑还没有处理完怎么办呢？</p></blockquote><p>Redisson是这样做的：额外开了一个线程，定期检查业务线程是否还持有锁，如果还持有的话则延长过期时间。Redisson使用了<strong>Watch Dog</strong>来定期检查（每1/3的锁时间检查一次）。</p><p><a href="https://github.com/redisson/redisson/wiki/8.-%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E5%92%8C%E5%90%8C%E6%AD%A5%E5%99%A8" target="_blank">Redisson的GitHub</a>是这样描述的：</p><p>大家都知道，如果负责储存这个分布式锁的Redisson节点宕机以后，而且这个锁正好处于锁住的状态时，这个锁会出现锁死的状态。为了避免这种情况的发生，Redisson内部提供了一个监控锁的看门狗，它的作用是在Redisson实例被关闭前，不断的延长锁的有效期。默认情况下，看门狗的检查锁的超时时间是30秒钟，也可以通过修改<a href="https://github.com/redisson/redisson/wiki/2.-%E9%85%8D%E7%BD%AE%E6%96%B9%E6%B3%95#lockwatchdogtimeout%E7%9B%91%E6%8E%A7%E9%94%81%E7%9A%84%E7%9C%8B%E9%97%A8%E7%8B%97%E8%B6%85%E6%97%B6%E5%8D%95%E4%BD%8D%E6%AF%AB%E7%A7%92" target="_blank">Config.lockWatchdogTimeout</a>来另行指定。</p><h4 id="tryacquireasync" tabindex="-1">tryAcquireAsync</h4><p>加锁的逻辑会进入到<code>org.redisson.RedissonLock#tryAcquireAsync</code>方法中，在获取锁成功之后会进入<code>org.redisson.RedissonBaseLock#scheduleExpirationRenewal</code>：</p><pre><code class="language-java">private &lt;T&gt; RFuture&lt;Long&gt; tryAcquireAsync(long waitTime, long leaseTime, TimeUnit unit, long threadId) {    RFuture&lt;Long&gt; ttlRemainingFuture;    if (leaseTime &gt; 0) {        ttlRemainingFuture = tryLockInnerAsync(waitTime, leaseTime, unit, threadId, RedisCommands.EVAL_LONG);    } else {        ttlRemainingFuture = tryLockInnerAsync(waitTime, internalLockLeaseTime,                TimeUnit.MILLISECONDS, threadId, RedisCommands.EVAL_LONG);    }    CompletionStage&lt;Long&gt; f = ttlRemainingFuture.thenApply(ttlRemaining -&gt; {        // lock acquired        if (ttlRemaining == null) {            if (leaseTime &gt; 0) {                internalLockLeaseTime = unit.toMillis(leaseTime);            } else {                scheduleExpirationRenewal(threadId);            }        }        return ttlRemaining;    });    return new CompletableFutureWrapper&lt;&gt;(f);}protected void scheduleExpirationRenewal(long threadId) {    ExpirationEntry entry = new ExpirationEntry();    ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(getEntryName(), entry);    if (oldEntry != null) {        oldEntry.addThreadId(threadId);    } else {        entry.addThreadId(threadId);        try {            renewExpiration();        } finally {            if (Thread.currentThread().isInterrupted()) {                cancelExpirationRenewal(threadId);            }        }    }}</code></pre>]]>
                    </description>
                    <pubDate>Mon, 30 May 2022 18:38:36 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Redis实现分布式锁]]>
                    </title>
                    <link>https://zygzyg.com/archives/5395993d32194ab6b8dd4e120c6b32a4</link>
                    <description>
                            <![CDATA[<h1 id="redis%E5%AE%9E%E7%8E%B0%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" tabindex="-1">Redis实现分布式锁</h1><blockquote><p>Redis除了用来作为缓存，还有哪些其他的用途呢？</p></blockquote><ol><li><p><strong>分布式锁</strong>：在分布式系统中，为了保证数据的正确性，经常需要使用分布式锁。Redis提供了<code>setnx</code>和<code>expire</code>等操作，可以实现分布式锁的功能。使用Redis作为分布式锁可以有效地解决多个进程或者多台机器之间的竞争问题，从而提高系统的稳定性和可靠性。</p></li><li><p><strong>分布式Session</strong>：在Web应用中，为了保证用户的登录状态，经常需要使用会话管理。Redis提供了String和Hash等数据结构，可以方便地实现会话管理的功能。使用Redis作为会话管理存储可以有效地提高系统的性能和可扩展性。</p></li><li><p><strong>全局唯一ID</strong>：Redis可以通过<code>Incr</code>等操作实现全局唯一Id的生成。具体实现方式是在Redis中创建一个计数器，每当需要生成新的全局唯一Id时，对计数器进行自增，将自增后的值作为新的全局唯一Id返回。</p></li><li><p><strong>计数器</strong>：在很多应用场景中，需要对某些数据进行计数。Redis提供了<code>Incr</code>和<code>Decr</code>等操作，可以方便地实现计数器的功能。同时，Redis还提供了HyperLogLog数据结构，可以实现一些基数统计的功能。使用Redis作为计数器可以有效地提高系统的性能和可扩展性。</p></li><li><p><strong>地理位置</strong>：Redis提供了Geo数据结构，可以实现一些基于地理位置的应用，比如附近的人等。使用Redis作为地理位置存储可以有效地提高系统的可扩展性和可维护性。</p><p>除此之外，Redis还提供了很多其他的数据结构和操作，如Set、Sorted Set、Hash等。可以根据具体的需求选择合适的数据结构和操作。</p></li><li><p><strong>轻量级的消息队列</strong>：在分布式系统中，消息队列是一种常见的解决方案。Redis提供了List数据结构，我们可以通过LeftPush和RightPop等操作来实现消息队列的功能。此外，Redis还提供了pub/sub模式，可以实现消息的订阅和发布。使用Redis作为消息队列可以有效地解耦系统各个模块之间的依赖关系，从而提高系统的可靠性和可维护性。</p></li></ol><p>除此之外，Redis还提供了很多其他的数据结构和操作，如Set、Sorted Set、Hash等。可以根据具体的需求选择合适的数据结构和操作。</p><h2 id="%E4%BB%80%E4%B9%88%E6%98%AF%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" tabindex="-1">什么是分布式锁</h2><blockquote><p>要介绍分布式锁，首先要提到与分布式锁相对应的是线程锁、进程锁。</p></blockquote><ul><li><strong>线程锁</strong>：主要用来给方法、代码块加锁。当某个方法或代码使用锁，在同一时刻仅有一个线程执行该方法或该代码段。线程锁只在同一JVM中有效果，因为线程锁的实现在根本上是依靠线程之间共享内存实现的，比如synchronized是共享对象头，显示锁Lock是共享某个变量（state）。</li><li><strong>进程锁</strong>：为了控制同一操作系统中多个进程访问某个共享资源，因为进程具有独立性，各个进程无法访问其他进程的资源，因此无法通过synchronized等线程锁实现进程锁。</li><li><strong>分布式锁</strong>：当多个进程不在同一个系统中(比如分布式系统中控制共享资源访问)，用分布式锁控制多个进程对资源的访问。</li></ul><h2 id="%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E9%9C%80%E8%A6%81%E5%85%B7%E5%A4%87%E7%9A%84%E6%9D%A1%E4%BB%B6" tabindex="-1">分布式锁需要具备的条件</h2><p>一个靠谱的分布式锁需要具备以下条件：</p><ol><li><strong>互斥性</strong>：同一时刻只能有一个客户端持有锁，其他客户端无法获取锁。</li><li><strong>可重入性</strong>：允许同一个客户端在持有锁的情况下再次获取锁，避免死锁。</li><li><strong>高可用性</strong>：当锁服务器出现故障时，需要有备用服务器来提供锁服务。</li><li><strong>容错性</strong>：当一个客户端持有锁的时间超过一定阈值时，需要自动释放锁，避免死锁。</li></ol><p>除此之外，分布式锁的设计中还可以/需要考虑：</p><ol><li>加锁解锁的<strong>同源性</strong>：A加的锁，不能被B解锁。</li><li>获取锁是<strong>非阻塞</strong>的：如果获取不到锁，不能无限期等待。</li><li><strong>高性能</strong>：加锁解锁是高性能的。</li></ol><h2 id="%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%A1%88" tabindex="-1">分布式锁的实现方案</h2><ol><li><strong>基于数据库的实现方案</strong>：<ol><li>基于数据库表（锁表，很少使用）。</li><li>乐观锁（基于版本号）。</li><li>悲观锁（基于排它锁）。</li></ol></li><li><strong>基于Redis实现分布式锁</strong>：<ol><li>单个Redis实例：<code>setnx</code>（key,当前时间+过期时间）+ Lua。</li><li>Redis集群模式：Redlock。</li></ol></li><li><strong>基于ZooKeeper的实现方案</strong>:当一个进程或线程需要访问共享资源时，先在ZooKeeper中创建一个临时节点，并将该节点作为锁。其他进程或线程尝试创建同名节点时，由于该节点已存在，创建操作将失败，从而实现了对共享资源的访问控制。当进程或线程完成对共享资源的访问后，删除该节点，释放锁。</li><li><strong>基于 Consul 实现分布式锁</strong></li></ol><h2 id="%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E7%AE%80%E5%8D%95%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" tabindex="-1">实现一个简单的分布式锁</h2><h3 id="%E4%B8%9A%E5%8A%A1%E5%9C%BA%E6%99%AF" tabindex="-1">业务场景</h3><p>假设一个商品扣减库存的场景，库存数据就存放在Redis了。首先是Service的实现：</p><pre><code class="language-Java">@Overridepublic void deduct(Long id) {    String key = &quot;storage:&quot; + id;    //查询Redis里面的库存    int storage = Optional.ofNullable(redisTemplate.opsForValue().get(key)).map(Integer::parseInt).orElse(0);    //判断库存是否足够    if(storage&gt;0){        //扣减库存        redisTemplate.opsForValue().set(key,String.valueOf(storage-1));    }}</code></pre><p>具体业务就是根据一个库存Id，从Redis查询到剩余库存然后判断是否大于0，最后扣除库存保存到Redis。</p><h3 id="%E4%BD%BF%E7%94%A8setnx%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" tabindex="-1">使用<code>setnx</code>实现简单分布式锁</h3><p>这里是存在线程安全问题的，高并发下极有可能出现”超卖“现象，这里我们使用分布式锁来解决，从上文得知，Redis可以使用<code>setnx</code>来实现分布式锁，那么代码可以改成如下：</p><pre><code class="language-Java">@Overridepublic void deduct(Long id) {    String key = &quot;storage:&quot; + id;    String redisLockKey = &quot;redisLock:storage&quot;;    String uuId = UUID.randomUUID().toString();    String lockId = uuId + Thread.currentThread().getId();    try{        //尝试抢占锁        Boolean hasLock = redisTemplate.opsForValue().setIfAbsent(redisLockKey,lockId);        //这里如果没有抢到锁则重试        if(Boolean.FALSE.equals(hasLock)){            //暂停20ms，然后递归重试            TimeUnit.MICROSECONDS.sleep(20);            deduct(id);        }else{            //抢到锁则执行业务            //查询Redis里面的库存            int storage = Optional.ofNullable(redisTemplate.opsForValue().get(key)).map(Integer::parseInt).orElse(0);            //判断库存是否足够            if(storage&gt;0){                //扣减库存                redisTemplate.opsForValue().set(key,String.valueOf(storage-1));            }        }    }catch (Exception exception){        exception.printStackTrace();    }finally {        //关闭资源，释放锁        redisTemplate.delete(redisLockKey);    }}</code></pre><p>测试上面的代码后并没有出现”超卖“，但是上面的代码还是存在一定的问题的：</p><ul><li>递归的次数：这里使用递归在高并发的场景下很容易导致<strong>SOF</strong>。</li></ul><h4 id="%E4%BD%BF%E7%94%A8%E8%87%AA%E6%97%8B%E6%9D%A5%E9%87%8D%E8%AF%95%E8%8E%B7%E5%8F%96%E9%94%81" tabindex="-1">使用自旋来重试获取锁</h4><pre><code class="language-java">@Overridepublic void deduct(Long id) {    String key = &quot;storage:&quot; + id;    String redisLockKey = &quot;redisLock:storage&quot;;    String uuId = UUID.randomUUID().toString();    String lockId = uuId + Thread.currentThread().getId();    try{        //尝试抢占锁，自旋        while(Boolean.FALSE.equals(redisTemplate.opsForValue().setIfAbsent(redisLockKey, lockId))) {            //暂停20ms            TimeUnit.MICROSECONDS.sleep(20);        }        //抢到锁则执行业务        //查询Redis里面的库存        int storage = Optional.ofNullable(redisTemplate.opsForValue().get(key)).map(Integer::parseInt).orElse(0);        //判断库存是否足够        if(storage&gt;0){            //扣减库存            redisTemplate.opsForValue().set(key,String.valueOf(storage-1));        }    }catch (Exception exception){        exception.printStackTrace();    }finally {        //关闭资源，释放锁        redisTemplate.delete(redisLockKey);    }}</code></pre><p>这里我们使用自旋来代替了上面的递归，避免了上面的SOF的出现，但是这里还是存在一些问题的，想想如果线程的关闭资源之前，服务器挂掉了，那么这个key将会一直存在，其他线程永远获取不了锁。那么怎样才能使这个key不一直存在呢，这里我们可以给这个key加上过期时间，这样就算没有关闭资源，那这个key还是会自动过期删除的。</p><pre><code class="language-Java">redisTemplate.opsForValue().setIfAbsent(redisLockKey,lockId,10L,TimeUnit.SECONDS)</code></pre><blockquote><p>这里不能分开使用<code>expire</code>方法，因为如果不保证原子性的话，还是会出现上述问题。</p></blockquote><h4 id="%E9%98%B2%E6%AD%A2%E8%AF%AF%E5%88%A0key" tabindex="-1">防止误删Key</h4><p>上面我们设置了key的过期时间来保证在没有释放锁的情况下使key过期来自动删除，但是上面过期时间是写死的，实际这个具体时间是没有办法保证的。</p><p>假设这里两个线程A和B，A先抢到了锁并开始执行业务，但是A执行的时间没法保证，所以当某一次A的执行时间超过了设置的过期时间时，这里一直在自旋重试的B则会马上获取到锁，这里当B还在执行业务的同时，A线程执行完了然后释放锁（实际上释放的时B加的锁）。根据上面提到的分布式锁需要具备<a href="#%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84%E5%AE%9E%E7%8E%B0%E6%96%B9%E6%A1%88">锁的同源性</a>，所以这里违背了分布式锁的设计原则。</p><div class="mermaid"><svg id="render2372294720" width="100%" xmlns="http://www.w3.org/2000/svg" height="793" style="max-width: 923px;" viewBox="-50 -10 923 793"><style>#render2372294720 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#render2372294720 .error-icon{fill:#a5a7bb;}#render2372294720 .error-text{fill:#5a5844;stroke:#5a5844;}#render2372294720 .edge-thickness-normal{stroke-width:2px;}#render2372294720 .edge-thickness-thick{stroke-width:3.5px;}#render2372294720 .edge-pattern-solid{stroke-dasharray:0;}#render2372294720 .edge-pattern-dashed{stroke-dasharray:3;}#render2372294720 .edge-pattern-dotted{stroke-dasharray:2;}#render2372294720 .marker{fill:#F8B229;stroke:#F8B229;}#render2372294720 .marker.cross{stroke:#F8B229;}#render2372294720 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render2372294720 .actor{stroke:#7C0000;fill:#025ebb;}#render2372294720 text.actor>tspan{fill:#333;stroke:none;}#render2372294720 .actor-line{stroke:grey;}#render2372294720 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#render2372294720 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#render2372294720 #arrowhead path{fill:#333;stroke:#333;}#render2372294720 .sequenceNumber{fill:#074dd6;}#render2372294720 #sequencenumber{fill:#333;}#render2372294720 #crosshead path{fill:#333;stroke:#333;}#render2372294720 .messageText{fill:#333;stroke:#333;}#render2372294720 .labelBox{stroke:#7C0000;fill:#025ebb;}#render2372294720 .labelText,#render2372294720 .labelText>tspan{fill:#333;stroke:none;}#render2372294720 .loopText,#render2372294720 .loopText>tspan{fill:#333;stroke:none;}#render2372294720 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:#7C0000;fill:#7C0000;}#render2372294720 .note{stroke:hsl(52.6829268293, 60%, 73.9215686275%);fill:#fff5ad;}#render2372294720 .noteText,#render2372294720 .noteText>tspan{fill:#333;stroke:none;}#render2372294720 .activation0{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2372294720 .activation1{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2372294720 .activation2{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2372294720 .actorPopupMenu{position:absolute;}#render2372294720 .actorPopupMenuPanel{position:absolute;fill:#025ebb;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#render2372294720 .actor-man line{stroke:#7C0000;fill:#025ebb;}#render2372294720 .actor-man circle,#render2372294720 line{stroke:#7C0000;fill:#025ebb;stroke-width:2px;}#render2372294720 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g></g><defs><symbol id="computer" width="24" height="24"><path transform="scale(.5)" d="M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z"></path></symbol></defs><defs><symbol id="database" fill-rule="evenodd" clip-rule="evenodd"><path transform="scale(.5)" d="M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z"></path></symbol></defs><defs><symbol id="clock" width="24" height="24"><path transform="scale(.5)" d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z"></path></symbol></defs><g><line id="actor0" x1="75" y1="5" x2="75" y2="727" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-0"><rect x="0" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="75" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">线程A</tspan></text></g></g><g><line id="actor1" x1="348" y1="5" x2="348" y2="727" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-1"><rect x="273" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="348" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="348" dy="0">Redis</tspan></text></g></g><g><line id="actor2" x1="548" y1="5" x2="548" y2="727" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-2"><rect x="473" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="548" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="548" dy="0">线程B</tspan></text></g></g><g><line id="actor3" x1="748" y1="5" x2="748" y2="727" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-3"><rect x="673" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="748" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="748" dy="0">线程C</tspan></text></g></g><defs><marker id="arrowhead" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z"></path></marker></defs><defs><marker id="crosshead" markerWidth="15" markerHeight="8" orient="auto" refX="16" refY="4"><path fill="black" stroke="#000000" stroke-width="1px" d="M 9,2 V 6 L16,4 Z" style="stroke-dasharray: 0, 0;"></path><path fill="none" stroke="#000000" stroke-width="1px" d="M 0,1 L 6,7 M 6,1 L 0,7" style="stroke-dasharray: 0, 0;"></path></marker></defs><defs><marker id="filled-head" refX="18" refY="7" markerWidth="20" markerHeight="28" orient="auto"><path d="M 18,7 L9,13 L14,7 L9,1 Z"></path></marker></defs><defs><marker id="sequencenumber" refX="15" refY="15" markerWidth="60" markerHeight="40" orient="auto"><circle cx="15" cy="15" r="6"></circle></marker></defs><text x="212" y="80" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">加锁</text><line x1="75" y1="113" x2="348" y2="113" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="343" y="113" fill="#EDF2AE" stroke="#666" width="10" height="48" rx="0" ry="0" class="activation0"></rect></g><text x="209" y="128" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">加锁成功</text><line x1="343" y1="161" x2="75" y2="161" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><g><rect x="70" y="161" fill="#EDF2AE" stroke="#666" width="10" height="382" rx="0" ry="0" class="activation0"></rect></g><g><rect x="343" y="163" fill="#EDF2AE" stroke="#666" width="10" height="76" rx="0" ry="0" class="activation0"></rect></g><text x="353" y="176" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">锁过期</text><path d="M 353,209 C 413,199 413,239 353,229" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></path><text x="448" y="254" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">加锁</text><line x1="548" y1="287" x2="348" y2="287" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="343" y="289" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><text x="451" y="302" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">加锁成功</text><line x1="353" y1="335" x2="548" y2="335" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><g><rect x="543" y="337" fill="#EDF2AE" stroke="#666" width="10" height="350" rx="0" ry="0" class="activation0"></rect></g><text x="80" y="350" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">执行业务</text><path d="M 80,383 C 140,373 140,413 80,403" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></path><text x="553" y="428" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">执行业务</text><path d="M 553,461 C 613,451 613,491 553,481" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></path><text x="214" y="506" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">释放锁（释放线程B加的锁）</text><line x1="80" y1="543" x2="348" y2="543" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="343" y="543" fill="#EDF2AE" stroke="#666" width="10" height="48" rx="0" ry="0" class="activation0"></rect></g><text x="209" y="558" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">释放锁成功</text><line x1="343" y1="591" x2="75" y2="591" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="548" y="606" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">加锁</text><line x1="748" y1="639" x2="348" y2="639" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="343" y="641" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><text x="551" y="654" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">加锁成功</text><line x1="353" y1="687" x2="748" y2="687" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><g><rect x="0" y="707" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="75" y="739.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">线程A</tspan></text></g><g><rect x="273" y="707" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="348" y="739.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="348" dy="0">Redis</tspan></text></g><g><rect x="473" y="707" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="548" y="739.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="548" dy="0">线程B</tspan></text></g><g><rect x="673" y="707" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="748" y="739.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="748" dy="0">线程C</tspan></text></g></svg></div><blockquote><p>那么如何解决这个问题呢？首先我们就需要保证每个线程只能删除自己的key，可以在删除key的同时增加一个判断，判断当前key所对应的value是否是自己的。</p></blockquote><pre><code class="language-java">//关闭资源，释放锁if(lockId.equalsIgnoreCase(redisTemplate.opsForValue().get(redisLockKey))){redisTemplate.delete(redisLockKey);}</code></pre><h2 id="%E4%BD%BF%E7%94%A8lua%E4%BF%9D%E8%AF%81%E5%8E%9F%E5%AD%90%E6%80%A7" tabindex="-1">使用Lua保证原子性</h2><blockquote><p>上面我们使用Redis的<code>setnx</code>实现了一个简单的分布式锁，但是实际还存在很多的问题，比如在最后释放锁的时候是不能保证原子性的。所以这里采用Lua来保证原子性。</p></blockquote><h3 id="%E4%BB%80%E4%B9%88%E6%98%AFlua%E8%84%9A%E6%9C%AC" tabindex="-1">什么是Lua脚本</h3><p>Lua是一种轻量小巧的脚本语言，用标准的C语言编写并以源代码形式开放，设计目的时为了嵌入到程序之中，从而为应用程序提供灵活的扩展和定制功能。</p><h3 id="lua%E7%9A%84%E7%89%B9%E6%80%A7" tabindex="-1">Lua的特性</h3><ul><li><strong>轻量级</strong>：使用标准C语言编写，编译后仅仅一百多k，可以很方便的嵌入到别的程序里面。</li><li><strong>可扩展</strong>：Lua提供了非常易于使用的扩展接口和机制：由宿主语言（通常时C或者C++）提供这些功能，Lua可以就像时本来内置的功能一样来使用它们。</li></ul><h3 id="redis%E4%B8%AD%E4%BD%BF%E7%94%A8lua" tabindex="-1">Redis中使用Lua</h3><p>Redis中执行Lua可以通过两种方式：</p><ul><li><code>EVAL</code>：将Lua脚本或命令直接使用Redis执行。</li><li><code>EVALSHA</code>：相当于把脚本或命令保存到redis中，然后使用一串sha码调用（可以理解为调用函数）</li></ul><h4 id="eval" tabindex="-1">eval</h4><p>命令的使用：</p><pre><code class="language-shell">EVAL script numkeys [key [key ...]] [arg [arg ...]]</code></pre><ol><li><code>script</code>：是一段Lua5.1脚本程序。脚本内容就是要执行的lua脚本内容。</li><li><code>numkeys</code>：指定后续参数有几个key，即：key [key …]中key的个数。如没有key，则为0。</li><li><code>key [key …]</code> ：key列表，作为参数传递给Lua语言，lua中是用<code>KEYS[n]</code>来获取对应的参数表示在脚本中所用到的那些Redis键（key）。在Lua脚本中通过KEYS[1], KEYS[2]获取。</li><li><code>arg [arg …]</code>：参数列表是传递给Lua语言，可填可不填，lua中使用<code>ARGV[n]</code>来获取对应的参数。</li></ol><p>比如：</p><pre><code class="language-shell">&gt; EVAL &quot;return&quot; 0 hello&quot;hello&quot;</code></pre><h3 id="%E4%BD%BF%E7%94%A8lua%E8%84%9A%E6%9C%AC%E8%A7%A3%E5%86%B3%E4%B8%8D%E8%83%BD%E4%BF%9D%E8%AF%81%E5%8E%9F%E5%AD%90%E6%80%A7%E7%9A%84%E9%97%AE%E9%A2%98" tabindex="-1">使用Lua脚本解决不能保证原子性的问题</h3><p>修改上面代码中的finally代码块为即可保证释放锁时候的原子性：</p><pre><code class="language-Java">finally {    //关闭资源，释放锁，使用Lua脚本来保证原子性    String luaScript = &quot;if redis.call(&#39;get&#39;,KEYS[1]) == ARGV[1] then&quot; +                            &quot; return redis.call(&#39;del&#39;,KEYS[1]) &quot; +                        &quot;else &quot; +                            &quot;return 0&quot; +                        &quot;end&quot;;    List&lt;String&gt; keys = new ArrayList&lt;&gt;() {{        add(redisLockKey);    }};    DefaultRedisScript&lt;Boolean&gt; redisScript = new DefaultRedisScript&lt;&gt;(luaScript, Boolean.class);    Boolean result = redisTemplate.execute(redisScript, keys, lockId);}</code></pre><h2 id="%E5%8F%AF%E9%87%8D%E5%85%A5%E7%9A%84%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81" tabindex="-1">可重入的分布式锁</h2><h3 id="%E4%BB%80%E4%B9%88%E6%98%AF%E5%8F%AF%E9%87%8D%E5%85%A5%E9%94%81" tabindex="-1">什么是可重入锁</h3><p>可重入锁还可以叫做递归锁，它是指同一个线程在外层方法获取锁的时候，再次进入该方法内层调用的方法时可以自动获取锁（这里前提是同一把锁），不会因为之前已经获取过该锁还没释放而阻塞。可重入锁的一个优点就是在一定程度上防止了死锁。</p><p><strong>简单说就是一个线程中的多个流程可以获取在不释放锁的情况下可以获取同一把锁</strong>。</p><h3 id="%E5%8F%AF%E9%87%8D%E5%85%A5%E9%94%81%E7%9A%84%E7%A7%8D%E7%B1%BB" tabindex="-1">可重入锁的种类</h3><ol><li>隐式锁：synchronized，<strong>synchronized</strong>实现可重入锁是<strong>通过使每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针</strong>：<ol><li>当执行<strong>monitor enter</strong>的时候，如果目标锁对象的计数器为0，那么说明它没有被其他线程所持有，Java虚拟机会将该锁对象的持有线程设置为当前线程，并且将其计数器加1。</li><li>在目标锁对象的计数器不为0的情况下，如果锁对象的持有线程是当前线程，那么Java虚拟机可以将其计数器再加1，否则需要等待其他线程释放锁。</li><li>当执行<strong>monitor exit</strong>时，Java虚拟机则需要将锁对象的计数器减1。计数器为0则代表锁已经被释放。</li></ol></li><li>显式锁：比如Lock中的ReentrantLock。</li></ol><p>简单来说实现一个可重入锁（可以参考synchronized以及JUC中的AQS），当线程再次获取同一把锁时判断当前锁是否被同一个线程获取了，如果是则当前线程便可以直接获得该锁，否则需要等待其他线程释放锁才行。</p><h3 id="%E5%9F%BA%E4%BA%8Eredis%E5%AE%9E%E7%8E%B0%E5%8F%AF%E9%87%8D%E5%85%A5%E9%94%81" tabindex="-1">基于Redis实现可重入锁</h3><blockquote><p>在实现之前，还有一个计数器的问题，显然继续使用<code>setnx</code>并不能满足这一条件，那么要基于Redis实现一个可重入的分布式锁还能用Redis的什么数据类型呢？</p></blockquote><p>我们可以使用Hash这个数据类型来实现：</p><pre><code class="language-shell">HSET RedisLock xxxxxxxx:1 1</code></pre><p>也就是<code>Hset 锁名称 全局唯一Id:线程Id 计数器（线程加锁的次数）</code>。</p><p>修改业务代码为：</p><pre><code class="language-java">@Overridepublic void deduct(Long id) {    String key = &quot;storage:&quot; + id;    Lock lock = distributedLockFactory.createLock(DistributedLockFactory.LockType.REDIS);    try{        lock.lock();        //抢到锁则执行业务        //查询Redis里面的库存        int storage = Optional.ofNullable(redisTemplate.opsForValue().get(key)).map(Integer::parseInt).orElse(0);        //判断库存是否足够        if(storage&gt;0){            //扣减库存            redisTemplate.opsForValue().set(key,String.valueOf(storage-1));        }        testReentrant();    }catch (Exception exception){        exception.printStackTrace();    }finally {        lock.unlock();    }}@Overridepublic void testReentrant() {    Lock lock = distributedLockFactory.createLock(DistributedLockFactory.LockType.REDIS);    try {        lock.lock();    }finally {        lock.unlock();    }}</code></pre><p>基于JUC的Lock接口来实现RedisLock：</p><pre><code class="language-java">public class RedisLock extends AbstractDistributedLock {    private final RedisTemplate&lt;String,Object&gt; redisTemplate;    private final long expireTime;    private final String LOCK_NAME = &quot;RedisLock&quot;;    public RedisLock(RedisTemplate&lt;String, Object&gt; redisTemplate,String uuid) {        this.redisTemplate = redisTemplate;        this.expireTime = 50L;        super.lockId = uuid+&quot;:&quot;+Thread.currentThread().getId();    }    @Override    public void lock() {        while(Boolean.FALSE.equals(tryLock())){            try {                TimeUnit.MILLISECONDS.sleep(60);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }        }    }    @Override    public boolean tryLock() {        String luaScript = &quot;if redis.call(&#39;EXISTS&#39;,KEYS[1]) == 0 or redis.call(&#39;HEXISTS&#39;,KEYS[1],ARGV[1]) == 1 then&quot; +                                &quot;redis.call(&#39;HINCRBY&#39;,KEYS[1],ARGV[1],1)&quot;+                                &quot;redis.call(&#39;EXPIRE&#39;,KEYS[1],ARGV[2])&quot;+                                &quot;return 1&quot;+                            &quot;else&quot;+                                &quot;return 0&quot;+                            &quot;end&quot;;        return Boolean.TRUE.equals(redisTemplate.execute(new DefaultRedisScript&lt;&gt;(luaScript, Boolean.class), List.of(LOCK_NAME), lockId, String.valueOf(expireTime)));    }    @Override    public void unlock() {        String luaScript = &quot;if redis.call(&#39;HEXISTS&#39;,KEYS[1],ARGV[1]) == 0 then&quot; +                                &quot;return nil&quot;+                            &quot;else if redis.call(&#39;HINCRBY&#39;,KEYS[1],ARGV[1],-1) == 0 then&quot;+                                &quot;return redis.call(&#39;DEL&#39;,KEYS[1])&quot;+                            &quot;else&quot;+                                &quot;return 0&quot;+                            &quot;end&quot;;        redisTemplate.execute(new DefaultRedisScript&lt;&gt;(luaScript, Long.class), List.of(LOCK_NAME), lockId);    }}</code></pre>]]>
                    </description>
                    <pubDate>Sat, 28 May 2022 22:38:59 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[缓存双写一致性问题]]>
                    </title>
                    <link>https://zygzyg.com/archives/8c3d8d8153da4e0d98c9fa19d1e1bce0</link>
                    <description>
                            <![CDATA[<h1 id="%E7%BC%93%E5%AD%98%E5%8F%8C%E5%86%99%E4%B8%80%E8%87%B4%E6%80%A7%E9%97%AE%E9%A2%98" tabindex="-1">缓存双写一致性问题</h1><p>首先缓存双写一致性是指缓存和数据库中的数据保持一致。当修改数据库中的数据时，需要同时修改缓存中的数据，以保证两者的一致性。如果只修改了数据库中的数据，而没有及时更新缓存，会导致缓存中的数据与数据库中的数据不一致，进而影响系统的正确性和性能。</p><p>一般使用缓存的流程可以是这样：</p><div class="mermaid"><svg id="render4191268834" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="656" style="max-width: 236.953125px;" viewBox="0 0 236.953125 656"><style>#render4191268834 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render4191268834 .error-icon{fill:#a5a7bb;}#render4191268834 .error-text{fill:#5a5844;stroke:#5a5844;}#render4191268834 .edge-thickness-normal{stroke-width:2px;}#render4191268834 .edge-thickness-thick{stroke-width:3.5px;}#render4191268834 .edge-pattern-solid{stroke-dasharray:0;}#render4191268834 .edge-pattern-dashed{stroke-dasharray:3;}#render4191268834 .edge-pattern-dotted{stroke-dasharray:2;}#render4191268834 .marker{fill:#F8B229;stroke:#F8B229;}#render4191268834 .marker.cross{stroke:#F8B229;}#render4191268834 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render4191268834 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render4191268834 .cluster-label text{fill:#5a5844;}#render4191268834 .cluster-label span{color:#5a5844;}#render4191268834 .label text,#render4191268834 span{fill:#fff;color:#fff;}#render4191268834 .node rect,#render4191268834 .node circle,#render4191268834 .node ellipse,#render4191268834 .node polygon,#render4191268834 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render4191268834 .node .label{text-align:center;}#render4191268834 .node.clickable{cursor:pointer;}#render4191268834 .arrowheadPath{fill:undefined;}#render4191268834 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render4191268834 .flowchart-link{stroke:#F8B229;fill:none;}#render4191268834 .edgeLabel{background-color:#006100;text-align:center;}#render4191268834 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render4191268834 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render4191268834 .cluster text{fill:#5a5844;}#render4191268834 .cluster span{color:#5a5844;}#render4191268834 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render4191268834 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 8)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path d="M81,42L81,46.166666666666664C81,50.333333333333336,81,58.666666666666664,81.08333333333333,67.08333333333333C81.16666666666667,75.5,81.33333333333333,84,81.41666666666667,88.25L81.5,92.5" id="L-Client-IfRedis-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Client LE-IfRedis" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M55.473438084549194,212.4734380845492L49.80078173712433,222.47786507045768C44.12812538969947,232.48229205636616,32.78281269484973,252.49114602818307,27.110156347424866,280.4122396807582C21.4375,308.3333333333333,21.4375,344.1666666666667,21.4375,380C21.4375,415.8333333333333,21.4375,451.6666666666667,21.4375,478.1666666666667C21.4375,504.6666666666667,21.4375,521.8333333333334,21.4375,537.4166666666666C21.4375,553,21.4375,567,28.38620721726191,578.1666666666666C35.33491443452381,589.3333333333334,49.23232886904762,597.6666666666666,56.181036086309526,601.8333333333334L63.12974330357143,606" id="L-IfRedis-Return-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-IfRedis LE-Return" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M105.90866873065015,214.09133126934984L110.75722394220846,223.82610939112487C115.60577915376678,233.56088751289988,125.30288957688339,253.03044375644996,130.234778121775,268.59855521155833C135.16666666666666,284.1666666666667,135.33333333333334,295.8333333333333,135.41666666666666,301.6666666666667L135.5,307.5" id="L-IfRedis-IfMySql-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-IfRedis LE-IfMySql" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M114.46347740617159,432.4634774061716L110.63297596347633,441.63623117180964C106.80247452078106,450.80898493744775,99.14147163539053,469.15449246872384,95.31097019269527,486.91057956769527C91.48046875,504.6666666666667,91.48046875,521.8333333333334,91.48046875,537.4166666666666C91.48046875,553,91.48046875,567,91.48046875,578.1666666666666C91.48046875,589.3333333333334,91.48046875,597.6666666666666,91.48046875,601.8333333333334L91.48046875,606" id="L-IfMySql-Return-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-IfMySql LE-Return" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M156.5365225938284,432.4634774061716L160.20035736985702,441.63623117180964C163.8641921458856,450.80898493744775,171.1918616979428,469.15449246872384,174.85569647397142,484.0772462343619C178.51953125,499,178.51953125,510.5,178.51953125,516.25L178.51953125,522" id="L-IfMySql-WriteRedis-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-IfMySql LE-WriteRedis" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M178.51953125,556L178.51953125,560.1666666666666C178.51953125,564.3333333333334,178.51953125,572.6666666666666,169.88470362103175,581C161.2498759920635,589.3333333333334,143.980220734127,597.6666666666666,135.34539310515873,601.8333333333334L126.71056547619048,606" id="L-WriteRedis-Return-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-WriteRedis LE-Return" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(21.4375, 487.5)"><g class="label" transform="translate(-12.0859375, -9.5)"><rect rx="0" ry="0" width="24.171875" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Yes</tspan></text></g></g><g class="edgeLabel" transform="translate(135, 272.5)"><g class="label" transform="translate(-9.60546875, -9.5)"><rect rx="0" ry="0" width="19.2109375" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">No</tspan></text></g></g><g class="edgeLabel" transform="translate(91.48046875, 539)"><g class="label" transform="translate(-9.60546875, -9.5)"><rect rx="0" ry="0" width="19.2109375" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">No</tspan></text></g></g><g class="edgeLabel" transform="translate(178.51953125, 487.5)"><g class="label" transform="translate(-12.0859375, -9.5)"><rect rx="0" ry="0" width="24.171875" height="19"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Yes</tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-Client-255" transform="translate(81, 25)"><rect style="" rx="17" ry="17" x="-36.75" y="-17" width="73.5" height="34"></rect><g class="label" style="" transform="translate(-25, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">客户端</tspan></text></g></g><g class="node default default" id="flowchart-Return-256" transform="translate(91.48046875, 623)"><rect style="" rx="17" ry="17" x="-44.75" y="-17" width="89.5" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">返回结果</tspan></text></g></g><g class="node default default" id="flowchart-IfRedis-257" transform="translate(81, 165)"><polygon points="73,0 146,-73 73,-146 0,-73" class="label-container" transform="translate(-73,73)" style=""></polygon><g class="label" style="" transform="translate(-40.5, -17.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">缓存</tspan><tspan xml:space="preserve" dy="1em" x="0" class="row">存在数据？</tspan></text></g></g><g class="node default default" id="flowchart-IfMySql-258" transform="translate(135, 380)"><polygon points="73,0 146,-73 73,-146 0,-73" class="label-container" transform="translate(-73,73)" style=""></polygon><g class="label" style="" transform="translate(-40.5, -17.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">数据库</tspan><tspan xml:space="preserve" dy="1em" x="0" class="row">存在数据？</tspan></text></g></g><g class="node default default" id="flowchart-WriteRedis-268" transform="translate(178.51953125, 539)"><rect class="basic label-container" style="" rx="0" ry="0" x="-42.43359375" y="-17" width="84.8671875" height="34"></rect><g class="label" style="" transform="translate(-34.93359375, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">回写Redis</tspan></text></g></g></g></g></g></svg></div><p>主要步骤就是先查询缓存看是否存在数据，存在则直接返回，不存在再去查询数据库，数据库中不存在的话也直接返回，存在的话则回写缓存并且返回。</p><h2 id="%E5%9B%9B%E7%A7%8D%E6%9B%B4%E6%96%B0%E7%AD%96%E7%95%A5" tabindex="-1">四种更新策略</h2><h3 id="%E5%85%88%E6%9B%B4%E6%96%B0%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%8C%E5%86%8D%E6%9B%B4%E6%96%B0%E7%BC%93%E5%AD%98%EF%BC%88%E2%9D%8C%E4%B8%8D%E6%8E%A8%E8%8D%90%EF%BC%89" tabindex="-1">先更新数据库，再更新缓存（❌不推荐）</h3><p>该策略会导致两种问题出现：</p><h4 id="%E6%9B%B4%E6%96%B0%E6%95%B0%E6%8D%AE%E5%BA%93%E6%88%90%E5%8A%9F%EF%BC%8C%E6%9B%B4%E6%96%B0%E7%BC%93%E5%AD%98%E5%A4%B1%E8%B4%A5" tabindex="-1">更新数据库成功，更新缓存失败</h4><p>比如有个商品的库存为1，有订单来了需要扣库存，如果先更新数据库为0，再更新缓存，<mark>但是更新数据库成功了而更新缓存时失败</mark>，这样就会导致缓存中库存依然是1没有扣减，数据库中库存扣减成功为0，这样的数据库与缓存的不一致问题。后续就会读到缓存中的脏数据。</p><div class="mermaid"><svg id="render1468134935" width="100%" xmlns="http://www.w3.org/2000/svg" height="463" style="max-width: 858px;" viewBox="-50 -10 858 463"><style>#render1468134935 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#render1468134935 .error-icon{fill:#a5a7bb;}#render1468134935 .error-text{fill:#5a5844;stroke:#5a5844;}#render1468134935 .edge-thickness-normal{stroke-width:2px;}#render1468134935 .edge-thickness-thick{stroke-width:3.5px;}#render1468134935 .edge-pattern-solid{stroke-dasharray:0;}#render1468134935 .edge-pattern-dashed{stroke-dasharray:3;}#render1468134935 .edge-pattern-dotted{stroke-dasharray:2;}#render1468134935 .marker{fill:#F8B229;stroke:#F8B229;}#render1468134935 .marker.cross{stroke:#F8B229;}#render1468134935 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render1468134935 .actor{stroke:#7C0000;fill:#025ebb;}#render1468134935 text.actor>tspan{fill:#333;stroke:none;}#render1468134935 .actor-line{stroke:grey;}#render1468134935 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#render1468134935 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#render1468134935 #arrowhead path{fill:#333;stroke:#333;}#render1468134935 .sequenceNumber{fill:#074dd6;}#render1468134935 #sequencenumber{fill:#333;}#render1468134935 #crosshead path{fill:#333;stroke:#333;}#render1468134935 .messageText{fill:#333;stroke:#333;}#render1468134935 .labelBox{stroke:#7C0000;fill:#025ebb;}#render1468134935 .labelText,#render1468134935 .labelText>tspan{fill:#333;stroke:none;}#render1468134935 .loopText,#render1468134935 .loopText>tspan{fill:#333;stroke:none;}#render1468134935 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:#7C0000;fill:#7C0000;}#render1468134935 .note{stroke:hsl(52.6829268293, 60%, 73.9215686275%);fill:#fff5ad;}#render1468134935 .noteText,#render1468134935 .noteText>tspan{fill:#333;stroke:none;}#render1468134935 .activation0{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render1468134935 .activation1{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render1468134935 .activation2{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render1468134935 .actorPopupMenu{position:absolute;}#render1468134935 .actorPopupMenuPanel{position:absolute;fill:#025ebb;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#render1468134935 .actor-man line{stroke:#7C0000;fill:#025ebb;}#render1468134935 .actor-man circle,#render1468134935 line{stroke:#7C0000;fill:#025ebb;stroke-width:2px;}#render1468134935 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g></g><defs><symbol id="computer" width="24" height="24"><path transform="scale(.5)" d="M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z"></path></symbol></defs><defs><symbol id="database" fill-rule="evenodd" clip-rule="evenodd"><path transform="scale(.5)" d="M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z"></path></symbol></defs><defs><symbol id="clock" width="24" height="24"><path transform="scale(.5)" d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z"></path></symbol></defs><line id="actor80" x1="75" y1="80" x2="75" y2="397" class="200" stroke-width="0.5px" stroke="#999"></line><g class="actor-man"><line id="actor-man-torso80" x1="75" y1="25" x2="75" y2="45"></line><line id="actor-man-arms80" x1="57" y1="33" x2="93" y2="33"></line><line x1="57" y1="60" x2="75" y2="45"></line><line x1="75" y1="45" x2="91" y2="60"></line><circle cx="75" cy="10" r="15" width="150" height="65"></circle><text x="75" y="67.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">请求A</tspan></text></g><g><line id="actor81" x1="283" y1="5" x2="283" y2="397" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-81"><rect x="208" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="283" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="283" dy="0">数据库</tspan></text></g></g><g><line id="actor82" x1="483" y1="5" x2="483" y2="397" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-82"><rect x="408" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="483" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="483" dy="0">缓存</tspan></text></g></g><line id="actor83" x1="683" y1="80" x2="683" y2="397" class="200" stroke-width="0.5px" stroke="#999"></line><g class="actor-man"><line id="actor-man-torso83" x1="683" y1="25" x2="683" y2="45"></line><line id="actor-man-arms83" x1="665" y1="33" x2="701" y2="33"></line><line x1="665" y1="60" x2="683" y2="45"></line><line x1="683" y1="45" x2="699" y2="60"></line><circle cx="683" cy="10" r="15" width="150" height="65"></circle><text x="683" y="67.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="683" dy="0">请求B</tspan></text></g><defs><marker id="arrowhead" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z"></path></marker></defs><defs><marker id="crosshead" markerWidth="15" markerHeight="8" orient="auto" refX="16" refY="4"><path fill="black" stroke="#000000" stroke-width="1px" d="M 9,2 V 6 L16,4 Z" style="stroke-dasharray: 0, 0;"></path><path fill="none" stroke="#000000" stroke-width="1px" d="M 0,1 L 6,7 M 6,1 L 0,7" style="stroke-dasharray: 0, 0;"></path></marker></defs><defs><marker id="filled-head" refX="18" refY="7" markerWidth="20" markerHeight="28" orient="auto"><path d="M 18,7 L9,13 L14,7 L9,1 Z"></path></marker></defs><defs><marker id="sequencenumber" refX="15" refY="15" markerWidth="60" markerHeight="40" orient="auto"><circle cx="15" cy="15" r="6"></circle></marker></defs><text x="179" y="80" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新数据库库存为0</text><line x1="75" y1="113" x2="283" y2="113" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="278" y="113" fill="#EDF2AE" stroke="#666" width="10" height="48" rx="0" ry="0" class="activation0"></rect></g><g><rect x="70" y="115" fill="#EDF2AE" stroke="#666" width="10" height="142" rx="0" ry="0" class="activation0"></rect></g><text x="179" y="128" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新成功</text><line x1="278" y1="161" x2="80" y2="161" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="282" y="176" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新缓存库存为0</text><line x1="80" y1="209" x2="483" y2="209" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="478" y="211" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><text x="279" y="224" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新失败</text><line x1="478" y1="257" x2="80" y2="257" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#crosshead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="583" y="272" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">查询库存</text><line x1="683" y1="305" x2="483" y2="305" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="478" y="305" fill="#EDF2AE" stroke="#666" width="10" height="52" rx="0" ry="0" class="activation0"></rect></g><text x="586" y="320" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">返回库存为1</text><line x1="488" y1="357" x2="683" y2="357" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><g class="actor-man"><line id="actor-man-torso83" x1="75" y1="402" x2="75" y2="422"></line><line id="actor-man-arms83" x1="57" y1="410" x2="93" y2="410"></line><line x1="57" y1="437" x2="75" y2="422"></line><line x1="75" y1="422" x2="91" y2="437"></line><circle cx="75" cy="387" r="15" width="150" height="65"></circle><text x="75" y="444.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">请求A</tspan></text></g><g><rect x="208" y="377" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="283" y="409.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="283" dy="0">数据库</tspan></text></g><g><rect x="408" y="377" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="483" y="409.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="483" dy="0">缓存</tspan></text></g><g class="actor-man"><line id="actor-man-torso83" x1="683" y1="402" x2="683" y2="422"></line><line id="actor-man-arms83" x1="665" y1="410" x2="701" y2="410"></line><line x1="665" y1="437" x2="683" y2="422"></line><line x1="683" y1="422" x2="699" y2="437"></line><circle cx="683" cy="387" r="15" width="150" height="65"></circle><text x="683" y="444.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="683" dy="0">请求B</tspan></text></g></svg></div><h4 id="%E5%A4%9A%E4%B8%AA%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%97%B6%E6%9B%B4%E6%96%B0%E5%AF%BC%E8%87%B4%E4%B8%8D%E4%B8%80%E8%87%B4" tabindex="-1">多个线程同时更新导致不一致</h4><p>比如A，B两个线程，假如A在更新完数据库后还没来得及更新缓存，此时B已经更新数据库并且更新了缓存，最后A才更新缓存，将会是下面这种情况，最终还是导致了数据库和缓存的不一致问题：</p><div class="mermaid"><svg id="render2530363943" width="100%" xmlns="http://www.w3.org/2000/svg" height="571" style="max-width: 850px;" viewBox="-50 -10 850 571"><style>#render2530363943 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#render2530363943 .error-icon{fill:#a5a7bb;}#render2530363943 .error-text{fill:#5a5844;stroke:#5a5844;}#render2530363943 .edge-thickness-normal{stroke-width:2px;}#render2530363943 .edge-thickness-thick{stroke-width:3.5px;}#render2530363943 .edge-pattern-solid{stroke-dasharray:0;}#render2530363943 .edge-pattern-dashed{stroke-dasharray:3;}#render2530363943 .edge-pattern-dotted{stroke-dasharray:2;}#render2530363943 .marker{fill:#F8B229;stroke:#F8B229;}#render2530363943 .marker.cross{stroke:#F8B229;}#render2530363943 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render2530363943 .actor{stroke:#7C0000;fill:#025ebb;}#render2530363943 text.actor>tspan{fill:#333;stroke:none;}#render2530363943 .actor-line{stroke:grey;}#render2530363943 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#render2530363943 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#render2530363943 #arrowhead path{fill:#333;stroke:#333;}#render2530363943 .sequenceNumber{fill:#074dd6;}#render2530363943 #sequencenumber{fill:#333;}#render2530363943 #crosshead path{fill:#333;stroke:#333;}#render2530363943 .messageText{fill:#333;stroke:#333;}#render2530363943 .labelBox{stroke:#7C0000;fill:#025ebb;}#render2530363943 .labelText,#render2530363943 .labelText>tspan{fill:#333;stroke:none;}#render2530363943 .loopText,#render2530363943 .loopText>tspan{fill:#333;stroke:none;}#render2530363943 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:#7C0000;fill:#7C0000;}#render2530363943 .note{stroke:hsl(52.6829268293, 60%, 73.9215686275%);fill:#fff5ad;}#render2530363943 .noteText,#render2530363943 .noteText>tspan{fill:#333;stroke:none;}#render2530363943 .activation0{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2530363943 .activation1{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2530363943 .activation2{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2530363943 .actorPopupMenu{position:absolute;}#render2530363943 .actorPopupMenuPanel{position:absolute;fill:#025ebb;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#render2530363943 .actor-man line{stroke:#7C0000;fill:#025ebb;}#render2530363943 .actor-man circle,#render2530363943 line{stroke:#7C0000;fill:#025ebb;stroke-width:2px;}#render2530363943 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g></g><defs><symbol id="computer" width="24" height="24"><path transform="scale(.5)" d="M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z"></path></symbol></defs><defs><symbol id="database" fill-rule="evenodd" clip-rule="evenodd"><path transform="scale(.5)" d="M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z"></path></symbol></defs><defs><symbol id="clock" width="24" height="24"><path transform="scale(.5)" d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z"></path></symbol></defs><line id="actor84" x1="75" y1="80" x2="75" y2="505" class="200" stroke-width="0.5px" stroke="#999"></line><g class="actor-man"><line id="actor-man-torso84" x1="75" y1="25" x2="75" y2="45"></line><line id="actor-man-arms84" x1="57" y1="33" x2="93" y2="33"></line><line x1="57" y1="60" x2="75" y2="45"></line><line x1="75" y1="45" x2="91" y2="60"></line><circle cx="75" cy="10" r="15" width="150" height="65"></circle><text x="75" y="67.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">线程A</tspan></text></g><g><line id="actor85" x1="275" y1="5" x2="275" y2="505" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-85"><rect x="200" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="275" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="275" dy="0">数据库</tspan></text></g></g><g><line id="actor86" x1="475" y1="5" x2="475" y2="505" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-86"><rect x="400" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="475" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="475" dy="0">缓存</tspan></text></g></g><line id="actor87" x1="675" y1="80" x2="675" y2="505" class="200" stroke-width="0.5px" stroke="#999"></line><g class="actor-man"><line id="actor-man-torso87" x1="675" y1="25" x2="675" y2="45"></line><line id="actor-man-arms87" x1="657" y1="33" x2="693" y2="33"></line><line x1="657" y1="60" x2="675" y2="45"></line><line x1="675" y1="45" x2="691" y2="60"></line><circle cx="675" cy="10" r="15" width="150" height="65"></circle><text x="675" y="67.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="675" dy="0">线程B</tspan></text></g><defs><marker id="arrowhead" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z"></path></marker></defs><defs><marker id="crosshead" markerWidth="15" markerHeight="8" orient="auto" refX="16" refY="4"><path fill="black" stroke="#000000" stroke-width="1px" d="M 9,2 V 6 L16,4 Z" style="stroke-dasharray: 0, 0;"></path><path fill="none" stroke="#000000" stroke-width="1px" d="M 0,1 L 6,7 M 6,1 L 0,7" style="stroke-dasharray: 0, 0;"></path></marker></defs><defs><marker id="filled-head" refX="18" refY="7" markerWidth="20" markerHeight="28" orient="auto"><path d="M 18,7 L9,13 L14,7 L9,1 Z"></path></marker></defs><defs><marker id="sequencenumber" refX="15" refY="15" markerWidth="60" markerHeight="40" orient="auto"><circle cx="15" cy="15" r="6"></circle></marker></defs><text x="175" y="80" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新数据库为100</text><line x1="75" y1="117" x2="275" y2="117" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="270" y="117" fill="#EDF2AE" stroke="#666" width="10" height="48" rx="0" ry="0" class="activation0"></rect></g><g><rect x="70" y="119" fill="#EDF2AE" stroke="#666" width="10" height="346" rx="0" ry="0" class="activation0"></rect></g><text x="175" y="132" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新成功</text><line x1="270" y1="165" x2="80" y2="165" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="475" y="180" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新数据库为80</text><line x1="675" y1="217" x2="275" y2="217" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="270" y="219" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><g><rect x="670" y="219" fill="#EDF2AE" stroke="#666" width="10" height="146" rx="0" ry="0" class="activation0"></rect></g><text x="475" y="232" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新成功</text><line x1="280" y1="265" x2="670" y2="265" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="573" y="280" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新缓存为80</text><line x1="670" y1="317" x2="475" y2="317" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="470" y="319" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><text x="575" y="332" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新成功</text><line x1="480" y1="365" x2="670" y2="365" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="278" y="380" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新缓存为100</text><line x1="80" y1="417" x2="475" y2="417" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="470" y="419" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><text x="275" y="432" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新成功</text><line x1="470" y1="465" x2="80" y2="465" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><g class="actor-man"><line id="actor-man-torso87" x1="75" y1="510" x2="75" y2="530"></line><line id="actor-man-arms87" x1="57" y1="518" x2="93" y2="518"></line><line x1="57" y1="545" x2="75" y2="530"></line><line x1="75" y1="530" x2="91" y2="545"></line><circle cx="75" cy="495" r="15" width="150" height="65"></circle><text x="75" y="552.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">线程A</tspan></text></g><g><rect x="200" y="485" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="275" y="517.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="275" dy="0">数据库</tspan></text></g><g><rect x="400" y="485" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="475" y="517.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="475" dy="0">缓存</tspan></text></g><g class="actor-man"><line id="actor-man-torso87" x1="675" y1="510" x2="675" y2="530"></line><line id="actor-man-arms87" x1="657" y1="518" x2="693" y2="518"></line><line x1="657" y1="545" x2="675" y2="530"></line><line x1="675" y1="530" x2="691" y2="545"></line><circle cx="675" cy="495" r="15" width="150" height="65"></circle><text x="675" y="552.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="675" dy="0">线程B</tspan></text></g></svg></div><h3 id="%E5%85%88%E6%9B%B4%E6%96%B0%E7%BC%93%E5%AD%98%EF%BC%8C%E5%86%8D%E6%9B%B4%E6%96%B0%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%88%E2%9D%8C%E4%B8%8D%E6%8E%A8%E8%8D%90%EF%BC%89" tabindex="-1">先更新缓存，再更新数据库（❌不推荐）</h3><h4 id="%E5%A4%9A%E4%B8%AA%E7%BA%BF%E7%A8%8B%E5%90%8C%E6%97%B6%E6%9B%B4%E6%96%B0%E5%AF%BC%E8%87%B4%E4%B8%8D%E4%B8%80%E8%87%B4-1" tabindex="-1">多个线程同时更新导致不一致</h4><p>比如A，B两个线程，假如A在更新完缓存后还没来得及更新数据库，此时B已经更新缓存并且更新了数据库，最后A才更新数据库，将会是下面这种情况，最终还是导致了数据库和缓存的不一致问题：</p><div class="mermaid"><svg id="render2072018109" width="100%" xmlns="http://www.w3.org/2000/svg" height="571" style="max-width: 850px;" viewBox="-50 -10 850 571"><style>#render2072018109 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#render2072018109 .error-icon{fill:#a5a7bb;}#render2072018109 .error-text{fill:#5a5844;stroke:#5a5844;}#render2072018109 .edge-thickness-normal{stroke-width:2px;}#render2072018109 .edge-thickness-thick{stroke-width:3.5px;}#render2072018109 .edge-pattern-solid{stroke-dasharray:0;}#render2072018109 .edge-pattern-dashed{stroke-dasharray:3;}#render2072018109 .edge-pattern-dotted{stroke-dasharray:2;}#render2072018109 .marker{fill:#F8B229;stroke:#F8B229;}#render2072018109 .marker.cross{stroke:#F8B229;}#render2072018109 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render2072018109 .actor{stroke:#7C0000;fill:#025ebb;}#render2072018109 text.actor>tspan{fill:#333;stroke:none;}#render2072018109 .actor-line{stroke:grey;}#render2072018109 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#render2072018109 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#render2072018109 #arrowhead path{fill:#333;stroke:#333;}#render2072018109 .sequenceNumber{fill:#074dd6;}#render2072018109 #sequencenumber{fill:#333;}#render2072018109 #crosshead path{fill:#333;stroke:#333;}#render2072018109 .messageText{fill:#333;stroke:#333;}#render2072018109 .labelBox{stroke:#7C0000;fill:#025ebb;}#render2072018109 .labelText,#render2072018109 .labelText>tspan{fill:#333;stroke:none;}#render2072018109 .loopText,#render2072018109 .loopText>tspan{fill:#333;stroke:none;}#render2072018109 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:#7C0000;fill:#7C0000;}#render2072018109 .note{stroke:hsl(52.6829268293, 60%, 73.9215686275%);fill:#fff5ad;}#render2072018109 .noteText,#render2072018109 .noteText>tspan{fill:#333;stroke:none;}#render2072018109 .activation0{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2072018109 .activation1{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2072018109 .activation2{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2072018109 .actorPopupMenu{position:absolute;}#render2072018109 .actorPopupMenuPanel{position:absolute;fill:#025ebb;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#render2072018109 .actor-man line{stroke:#7C0000;fill:#025ebb;}#render2072018109 .actor-man circle,#render2072018109 line{stroke:#7C0000;fill:#025ebb;stroke-width:2px;}#render2072018109 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g></g><defs><symbol id="computer" width="24" height="24"><path transform="scale(.5)" d="M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z"></path></symbol></defs><defs><symbol id="database" fill-rule="evenodd" clip-rule="evenodd"><path transform="scale(.5)" d="M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z"></path></symbol></defs><defs><symbol id="clock" width="24" height="24"><path transform="scale(.5)" d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z"></path></symbol></defs><line id="actor88" x1="75" y1="80" x2="75" y2="505" class="200" stroke-width="0.5px" stroke="#999"></line><g class="actor-man"><line id="actor-man-torso88" x1="75" y1="25" x2="75" y2="45"></line><line id="actor-man-arms88" x1="57" y1="33" x2="93" y2="33"></line><line x1="57" y1="60" x2="75" y2="45"></line><line x1="75" y1="45" x2="91" y2="60"></line><circle cx="75" cy="10" r="15" width="150" height="65"></circle><text x="75" y="67.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">线程A</tspan></text></g><g><line id="actor89" x1="275" y1="5" x2="275" y2="505" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-89"><rect x="200" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="275" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="275" dy="0">数据库</tspan></text></g></g><g><line id="actor90" x1="475" y1="5" x2="475" y2="505" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-90"><rect x="400" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="475" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="475" dy="0">缓存</tspan></text></g></g><line id="actor91" x1="675" y1="80" x2="675" y2="505" class="200" stroke-width="0.5px" stroke="#999"></line><g class="actor-man"><line id="actor-man-torso91" x1="675" y1="25" x2="675" y2="45"></line><line id="actor-man-arms91" x1="657" y1="33" x2="693" y2="33"></line><line x1="657" y1="60" x2="675" y2="45"></line><line x1="675" y1="45" x2="691" y2="60"></line><circle cx="675" cy="10" r="15" width="150" height="65"></circle><text x="675" y="67.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="675" dy="0">线程B</tspan></text></g><defs><marker id="arrowhead" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z"></path></marker></defs><defs><marker id="crosshead" markerWidth="15" markerHeight="8" orient="auto" refX="16" refY="4"><path fill="black" stroke="#000000" stroke-width="1px" d="M 9,2 V 6 L16,4 Z" style="stroke-dasharray: 0, 0;"></path><path fill="none" stroke="#000000" stroke-width="1px" d="M 0,1 L 6,7 M 6,1 L 0,7" style="stroke-dasharray: 0, 0;"></path></marker></defs><defs><marker id="filled-head" refX="18" refY="7" markerWidth="20" markerHeight="28" orient="auto"><path d="M 18,7 L9,13 L14,7 L9,1 Z"></path></marker></defs><defs><marker id="sequencenumber" refX="15" refY="15" markerWidth="60" markerHeight="40" orient="auto"><circle cx="15" cy="15" r="6"></circle></marker></defs><text x="275" y="80" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新缓存为100</text><line x1="75" y1="117" x2="475" y2="117" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="470" y="117" fill="#EDF2AE" stroke="#666" width="10" height="48" rx="0" ry="0" class="activation0"></rect></g><g><rect x="70" y="119" fill="#EDF2AE" stroke="#666" width="10" height="346" rx="0" ry="0" class="activation0"></rect></g><text x="275" y="132" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新成功</text><line x1="470" y1="165" x2="80" y2="165" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="575" y="180" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新缓存为80</text><line x1="675" y1="217" x2="475" y2="217" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="470" y="219" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><g><rect x="670" y="219" fill="#EDF2AE" stroke="#666" width="10" height="146" rx="0" ry="0" class="activation0"></rect></g><text x="575" y="232" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新成功</text><line x1="480" y1="265" x2="670" y2="265" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="473" y="280" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新数据库为80</text><line x1="670" y1="317" x2="275" y2="317" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="270" y="319" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><text x="475" y="332" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新成功</text><line x1="280" y1="365" x2="670" y2="365" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="178" y="380" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新数据库为100</text><line x1="80" y1="417" x2="275" y2="417" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="270" y="419" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><text x="175" y="432" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新成功</text><line x1="270" y1="465" x2="80" y2="465" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><g class="actor-man"><line id="actor-man-torso91" x1="75" y1="510" x2="75" y2="530"></line><line id="actor-man-arms91" x1="57" y1="518" x2="93" y2="518"></line><line x1="57" y1="545" x2="75" y2="530"></line><line x1="75" y1="530" x2="91" y2="545"></line><circle cx="75" cy="495" r="15" width="150" height="65"></circle><text x="75" y="552.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">线程A</tspan></text></g><g><rect x="200" y="485" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="275" y="517.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="275" dy="0">数据库</tspan></text></g><g><rect x="400" y="485" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="475" y="517.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="475" dy="0">缓存</tspan></text></g><g class="actor-man"><line id="actor-man-torso91" x1="675" y1="510" x2="675" y2="530"></line><line id="actor-man-arms91" x1="657" y1="518" x2="693" y2="518"></line><line x1="657" y1="545" x2="675" y2="530"></line><line x1="675" y1="530" x2="691" y2="545"></line><circle cx="675" cy="495" r="15" width="150" height="65"></circle><text x="675" y="552.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="675" dy="0">线程B</tspan></text></g></svg></div><h3 id="%E5%85%88%E5%88%A0%E9%99%A4%E7%BC%93%E5%AD%98%EF%BC%8C%E5%86%8D%E6%9B%B4%E6%96%B0%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%88%E2%9D%8C%E4%B8%8D%E6%8E%A8%E8%8D%90%EF%BC%89" tabindex="-1">先删除缓存，再更新数据库（❌不推荐）</h3><p><strong>读写请求并发的情况下，在写线程删除缓存后且更新数据库的事务提交之前，这时读线程进来了，读线程此时查不到缓存，就去数据库里查到了旧数据然后将数据放入缓存</strong>。这种情况下，直到下一次新的写操作进来之前，缓存中的数据将一直是脏数据。</p><div class="mermaid"><svg id="render2876514850" width="100%" xmlns="http://www.w3.org/2000/svg" height="843" style="max-width: 874px;" viewBox="-50 -10 874 843"><style>#render2876514850 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#render2876514850 .error-icon{fill:#a5a7bb;}#render2876514850 .error-text{fill:#5a5844;stroke:#5a5844;}#render2876514850 .edge-thickness-normal{stroke-width:2px;}#render2876514850 .edge-thickness-thick{stroke-width:3.5px;}#render2876514850 .edge-pattern-solid{stroke-dasharray:0;}#render2876514850 .edge-pattern-dashed{stroke-dasharray:3;}#render2876514850 .edge-pattern-dotted{stroke-dasharray:2;}#render2876514850 .marker{fill:#F8B229;stroke:#F8B229;}#render2876514850 .marker.cross{stroke:#F8B229;}#render2876514850 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render2876514850 .actor{stroke:#7C0000;fill:#025ebb;}#render2876514850 text.actor>tspan{fill:#333;stroke:none;}#render2876514850 .actor-line{stroke:grey;}#render2876514850 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#render2876514850 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#render2876514850 #arrowhead path{fill:#333;stroke:#333;}#render2876514850 .sequenceNumber{fill:#074dd6;}#render2876514850 #sequencenumber{fill:#333;}#render2876514850 #crosshead path{fill:#333;stroke:#333;}#render2876514850 .messageText{fill:#333;stroke:#333;}#render2876514850 .labelBox{stroke:#7C0000;fill:#025ebb;}#render2876514850 .labelText,#render2876514850 .labelText>tspan{fill:#333;stroke:none;}#render2876514850 .loopText,#render2876514850 .loopText>tspan{fill:#333;stroke:none;}#render2876514850 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:#7C0000;fill:#7C0000;}#render2876514850 .note{stroke:hsl(52.6829268293, 60%, 73.9215686275%);fill:#fff5ad;}#render2876514850 .noteText,#render2876514850 .noteText>tspan{fill:#333;stroke:none;}#render2876514850 .activation0{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2876514850 .activation1{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2876514850 .activation2{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render2876514850 .actorPopupMenu{position:absolute;}#render2876514850 .actorPopupMenuPanel{position:absolute;fill:#025ebb;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#render2876514850 .actor-man line{stroke:#7C0000;fill:#025ebb;}#render2876514850 .actor-man circle,#render2876514850 line{stroke:#7C0000;fill:#025ebb;stroke-width:2px;}#render2876514850 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g></g><defs><symbol id="computer" width="24" height="24"><path transform="scale(.5)" d="M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z"></path></symbol></defs><defs><symbol id="database" fill-rule="evenodd" clip-rule="evenodd"><path transform="scale(.5)" d="M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z"></path></symbol></defs><defs><symbol id="clock" width="24" height="24"><path transform="scale(.5)" d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z"></path></symbol></defs><line id="actor92" x1="75" y1="80" x2="75" y2="777" class="200" stroke-width="0.5px" stroke="#999"></line><g class="actor-man"><line id="actor-man-torso92" x1="75" y1="25" x2="75" y2="45"></line><line id="actor-man-arms92" x1="57" y1="33" x2="93" y2="33"></line><line x1="57" y1="60" x2="75" y2="45"></line><line x1="75" y1="45" x2="91" y2="60"></line><circle cx="75" cy="10" r="15" width="150" height="65"></circle><text x="75" y="67.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">写线程</tspan></text></g><g><line id="actor93" x1="299" y1="5" x2="299" y2="777" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-93"><rect x="224" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="299" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="299" dy="0">缓存</tspan></text></g></g><g><line id="actor94" x1="499" y1="5" x2="499" y2="777" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-94"><rect x="424" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="499" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="499" dy="0">数据库</tspan></text></g></g><line id="actor95" x1="699" y1="80" x2="699" y2="777" class="200" stroke-width="0.5px" stroke="#999"></line><g class="actor-man"><line id="actor-man-torso95" x1="699" y1="25" x2="699" y2="45"></line><line id="actor-man-arms95" x1="681" y1="33" x2="717" y2="33"></line><line x1="681" y1="60" x2="699" y2="45"></line><line x1="699" y1="45" x2="715" y2="60"></line><circle cx="699" cy="10" r="15" width="150" height="65"></circle><text x="699" y="67.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="699" dy="0">读线程</tspan></text></g><defs><marker id="arrowhead" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z"></path></marker></defs><defs><marker id="crosshead" markerWidth="15" markerHeight="8" orient="auto" refX="16" refY="4"><path fill="black" stroke="#000000" stroke-width="1px" d="M 9,2 V 6 L16,4 Z" style="stroke-dasharray: 0, 0;"></path><path fill="none" stroke="#000000" stroke-width="1px" d="M 0,1 L 6,7 M 6,1 L 0,7" style="stroke-dasharray: 0, 0;"></path></marker></defs><defs><marker id="filled-head" refX="18" refY="7" markerWidth="20" markerHeight="28" orient="auto"><path d="M 18,7 L9,13 L14,7 L9,1 Z"></path></marker></defs><defs><marker id="sequencenumber" refX="15" refY="15" markerWidth="60" markerHeight="40" orient="auto"><circle cx="15" cy="15" r="6"></circle></marker></defs><g><rect x="274" y="75" fill="#EDF2AE" stroke="#666" width="250" height="36" rx="0" ry="0" class="note"></rect><text x="399" y="80" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="noteText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;"><tspan x="399">数据库值为100，缓存没有100</tspan></text></g><text x="187" y="126" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">1.删除缓存(失效缓存)</text><line x1="75" y1="159" x2="299" y2="159" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="294" y="159" fill="#EDF2AE" stroke="#666" width="10" height="48" rx="0" ry="0" class="activation0"></rect></g><g><rect x="70" y="161" fill="#EDF2AE" stroke="#666" width="10" height="576" rx="0" ry="0" class="activation0"></rect></g><text x="187" y="174" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">删除成功</text><line x1="294" y1="207" x2="80" y2="207" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><g><rect x="274" y="217" fill="#EDF2AE" stroke="#666" width="250" height="36" rx="0" ry="0" class="note"></rect><text x="399" y="222" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="noteText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;"><tspan x="399">数据库值为100，缓存没有值</tspan></text></g><text x="499" y="268" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">2.查询缓存</text><line x1="699" y1="301" x2="299" y2="301" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="294" y="303" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><g><rect x="694" y="303" fill="#EDF2AE" stroke="#666" width="10" height="292" rx="0" ry="0" class="activation0"></rect></g><text x="499" y="316" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">缓存数据不存在</text><line x1="304" y1="349" x2="694" y2="349" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="597" y="364" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">3.读取数据库</text><line x1="694" y1="397" x2="499" y2="397" class="messageLine0" stroke-width="2" stroke="none" style="fill: none;"></line><g><rect x="494" y="399" fill="#EDF2AE" stroke="#666" width="10" height="50" rx="0" ry="0" class="activation0"></rect></g><text x="599" y="412" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">读取到值为100</text><line x1="504" y1="449" x2="694" y2="449" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="497" y="464" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">4.写入缓存值为100</text><line x1="694" y1="501" x2="299" y2="501" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="294" y="503" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><text x="499" y="516" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">写入缓存成功</text><line x1="304" y1="549" x2="694" y2="549" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><g><rect x="274" y="559" fill="#EDF2AE" stroke="#666" width="250" height="36" rx="0" ry="0" class="note"></rect><text x="399" y="564" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="noteText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;"><tspan x="399">数据库值为100，缓存没有100</tspan></text></g><text x="290" y="610" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">5.写入数据库值为80</text><line x1="80" y1="643" x2="499" y2="643" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="494" y="645" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><text x="287" y="658" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">写入数据库成功</text><line x1="494" y1="691" x2="80" y2="691" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><g><rect x="274" y="701" fill="#EDF2AE" stroke="#666" width="250" height="36" rx="0" ry="0" class="note"></rect><text x="399" y="706" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="noteText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;"><tspan x="399">数据库值为80，缓存没有100</tspan></text></g><g class="actor-man"><line id="actor-man-torso95" x1="75" y1="782" x2="75" y2="802"></line><line id="actor-man-arms95" x1="57" y1="790" x2="93" y2="790"></line><line x1="57" y1="817" x2="75" y2="802"></line><line x1="75" y1="802" x2="91" y2="817"></line><circle cx="75" cy="767" r="15" width="150" height="65"></circle><text x="75" y="824.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">写线程</tspan></text></g><g><rect x="224" y="757" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="299" y="789.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="299" dy="0">缓存</tspan></text></g><g><rect x="424" y="757" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="499" y="789.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="499" dy="0">数据库</tspan></text></g><g class="actor-man"><line id="actor-man-torso95" x1="699" y1="782" x2="699" y2="802"></line><line id="actor-man-arms95" x1="681" y1="790" x2="717" y2="790"></line><line x1="681" y1="817" x2="699" y2="802"></line><line x1="699" y1="802" x2="715" y2="817"></line><circle cx="699" cy="767" r="15" width="150" height="65"></circle><text x="699" y="824.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="699" dy="0">读线程</tspan></text></g></svg></div><p>如上图所示，开始时数据库和缓存值都是100，写线程在失效缓存成功后（数据库值为100，缓存没有值），读线程此时请求发现缓存数据为空的话，就会从数据库中读取旧值放入到缓存中（数据库和缓存值都是100），最后写线程在将值写入数据库（数据库值为80，缓存没有100），这样就导致了不一致的问题。另外，数据库如果采用的是主从复制+读写分离的架构，读线程读出来的数据也有可能是主从未同步完成造成的脏数据。</p><p>针对这样的情况可以采用<mark>延时双删</mark>的策略来有效避免，可以在更新完数据库后使用线程休眠一段时间，再次删除缓存：</p><pre><code class="language-java">cache.delKey(key);db.update(data);Thread.sleep(xxx);cache.delKey(key);</code></pre><p>主要是在写请求更新完数据库后休眠一段时间（休眠时间=读数据耗时+主从同步耗时），然后再删除一次缓存，将可能由并发读请求带来的脏数据失效掉。这种通过延时双删的方式需要线程休眠，因此很显然会降低系统吞吐量，并不是一种比较好的解决方式，也可以采用异步删除的方式。当然也可以设置缓存过期时间，到期后缓存自动失效，但这样做需要系统能够容忍一段时间的数据不一致。</p><h3 id="%E5%85%88%E6%9B%B4%E6%96%B0%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%8C%E5%86%8D%E5%88%A0%E9%99%A4%E7%BC%93%E5%AD%98%EF%BC%88%E2%9A%A0%EF%B8%8F%E6%8E%A8%E8%8D%90%EF%BC%89" tabindex="-1">先更新数据库，再删除缓存（⚠️推荐）</h3><blockquote><p>实际上这种方式也存在数据不一致的情况。</p></blockquote><div class="mermaid"><svg id="render3053899381" width="100%" xmlns="http://www.w3.org/2000/svg" height="797" style="max-width: 864px;" viewBox="-50 -10 864 797"><style>#render3053899381 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#render3053899381 .error-icon{fill:#a5a7bb;}#render3053899381 .error-text{fill:#5a5844;stroke:#5a5844;}#render3053899381 .edge-thickness-normal{stroke-width:2px;}#render3053899381 .edge-thickness-thick{stroke-width:3.5px;}#render3053899381 .edge-pattern-solid{stroke-dasharray:0;}#render3053899381 .edge-pattern-dashed{stroke-dasharray:3;}#render3053899381 .edge-pattern-dotted{stroke-dasharray:2;}#render3053899381 .marker{fill:#F8B229;stroke:#F8B229;}#render3053899381 .marker.cross{stroke:#F8B229;}#render3053899381 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render3053899381 .actor{stroke:#7C0000;fill:#025ebb;}#render3053899381 text.actor>tspan{fill:#333;stroke:none;}#render3053899381 .actor-line{stroke:grey;}#render3053899381 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#render3053899381 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#render3053899381 #arrowhead path{fill:#333;stroke:#333;}#render3053899381 .sequenceNumber{fill:#074dd6;}#render3053899381 #sequencenumber{fill:#333;}#render3053899381 #crosshead path{fill:#333;stroke:#333;}#render3053899381 .messageText{fill:#333;stroke:#333;}#render3053899381 .labelBox{stroke:#7C0000;fill:#025ebb;}#render3053899381 .labelText,#render3053899381 .labelText>tspan{fill:#333;stroke:none;}#render3053899381 .loopText,#render3053899381 .loopText>tspan{fill:#333;stroke:none;}#render3053899381 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:#7C0000;fill:#7C0000;}#render3053899381 .note{stroke:hsl(52.6829268293, 60%, 73.9215686275%);fill:#fff5ad;}#render3053899381 .noteText,#render3053899381 .noteText>tspan{fill:#333;stroke:none;}#render3053899381 .activation0{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render3053899381 .activation1{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render3053899381 .activation2{fill:#006100;stroke:hsl(120, 100%, 9.0196078431%);}#render3053899381 .actorPopupMenu{position:absolute;}#render3053899381 .actorPopupMenuPanel{position:absolute;fill:#025ebb;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#render3053899381 .actor-man line{stroke:#7C0000;fill:#025ebb;}#render3053899381 .actor-man circle,#render3053899381 line{stroke:#7C0000;fill:#025ebb;stroke-width:2px;}#render3053899381 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g></g><defs><symbol id="computer" width="24" height="24"><path transform="scale(.5)" d="M2 2v13h20v-13h-20zm18 11h-16v-9h16v9zm-10.228 6l.466-1h3.524l.467 1h-4.457zm14.228 3h-24l2-6h2.104l-1.33 4h18.45l-1.297-4h2.073l2 6zm-5-10h-14v-7h14v7z"></path></symbol></defs><defs><symbol id="database" fill-rule="evenodd" clip-rule="evenodd"><path transform="scale(.5)" d="M12.258.001l.256.004.255.005.253.008.251.01.249.012.247.015.246.016.242.019.241.02.239.023.236.024.233.027.231.028.229.031.225.032.223.034.22.036.217.038.214.04.211.041.208.043.205.045.201.046.198.048.194.05.191.051.187.053.183.054.18.056.175.057.172.059.168.06.163.061.16.063.155.064.15.066.074.033.073.033.071.034.07.034.069.035.068.035.067.035.066.035.064.036.064.036.062.036.06.036.06.037.058.037.058.037.055.038.055.038.053.038.052.038.051.039.05.039.048.039.047.039.045.04.044.04.043.04.041.04.04.041.039.041.037.041.036.041.034.041.033.042.032.042.03.042.029.042.027.042.026.043.024.043.023.043.021.043.02.043.018.044.017.043.015.044.013.044.012.044.011.045.009.044.007.045.006.045.004.045.002.045.001.045v17l-.001.045-.002.045-.004.045-.006.045-.007.045-.009.044-.011.045-.012.044-.013.044-.015.044-.017.043-.018.044-.02.043-.021.043-.023.043-.024.043-.026.043-.027.042-.029.042-.03.042-.032.042-.033.042-.034.041-.036.041-.037.041-.039.041-.04.041-.041.04-.043.04-.044.04-.045.04-.047.039-.048.039-.05.039-.051.039-.052.038-.053.038-.055.038-.055.038-.058.037-.058.037-.06.037-.06.036-.062.036-.064.036-.064.036-.066.035-.067.035-.068.035-.069.035-.07.034-.071.034-.073.033-.074.033-.15.066-.155.064-.16.063-.163.061-.168.06-.172.059-.175.057-.18.056-.183.054-.187.053-.191.051-.194.05-.198.048-.201.046-.205.045-.208.043-.211.041-.214.04-.217.038-.22.036-.223.034-.225.032-.229.031-.231.028-.233.027-.236.024-.239.023-.241.02-.242.019-.246.016-.247.015-.249.012-.251.01-.253.008-.255.005-.256.004-.258.001-.258-.001-.256-.004-.255-.005-.253-.008-.251-.01-.249-.012-.247-.015-.245-.016-.243-.019-.241-.02-.238-.023-.236-.024-.234-.027-.231-.028-.228-.031-.226-.032-.223-.034-.22-.036-.217-.038-.214-.04-.211-.041-.208-.043-.204-.045-.201-.046-.198-.048-.195-.05-.19-.051-.187-.053-.184-.054-.179-.056-.176-.057-.172-.059-.167-.06-.164-.061-.159-.063-.155-.064-.151-.066-.074-.033-.072-.033-.072-.034-.07-.034-.069-.035-.068-.035-.067-.035-.066-.035-.064-.036-.063-.036-.062-.036-.061-.036-.06-.037-.058-.037-.057-.037-.056-.038-.055-.038-.053-.038-.052-.038-.051-.039-.049-.039-.049-.039-.046-.039-.046-.04-.044-.04-.043-.04-.041-.04-.04-.041-.039-.041-.037-.041-.036-.041-.034-.041-.033-.042-.032-.042-.03-.042-.029-.042-.027-.042-.026-.043-.024-.043-.023-.043-.021-.043-.02-.043-.018-.044-.017-.043-.015-.044-.013-.044-.012-.044-.011-.045-.009-.044-.007-.045-.006-.045-.004-.045-.002-.045-.001-.045v-17l.001-.045.002-.045.004-.045.006-.045.007-.045.009-.044.011-.045.012-.044.013-.044.015-.044.017-.043.018-.044.02-.043.021-.043.023-.043.024-.043.026-.043.027-.042.029-.042.03-.042.032-.042.033-.042.034-.041.036-.041.037-.041.039-.041.04-.041.041-.04.043-.04.044-.04.046-.04.046-.039.049-.039.049-.039.051-.039.052-.038.053-.038.055-.038.056-.038.057-.037.058-.037.06-.037.061-.036.062-.036.063-.036.064-.036.066-.035.067-.035.068-.035.069-.035.07-.034.072-.034.072-.033.074-.033.151-.066.155-.064.159-.063.164-.061.167-.06.172-.059.176-.057.179-.056.184-.054.187-.053.19-.051.195-.05.198-.048.201-.046.204-.045.208-.043.211-.041.214-.04.217-.038.22-.036.223-.034.226-.032.228-.031.231-.028.234-.027.236-.024.238-.023.241-.02.243-.019.245-.016.247-.015.249-.012.251-.01.253-.008.255-.005.256-.004.258-.001.258.001zm-9.258 20.499v.01l.001.021.003.021.004.022.005.021.006.022.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.023.018.024.019.024.021.024.022.025.023.024.024.025.052.049.056.05.061.051.066.051.07.051.075.051.079.052.084.052.088.052.092.052.097.052.102.051.105.052.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.048.144.049.147.047.152.047.155.047.16.045.163.045.167.043.171.043.176.041.178.041.183.039.187.039.19.037.194.035.197.035.202.033.204.031.209.03.212.029.216.027.219.025.222.024.226.021.23.02.233.018.236.016.24.015.243.012.246.01.249.008.253.005.256.004.259.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.021.224-.024.22-.026.216-.027.212-.028.21-.031.205-.031.202-.034.198-.034.194-.036.191-.037.187-.039.183-.04.179-.04.175-.042.172-.043.168-.044.163-.045.16-.046.155-.046.152-.047.148-.048.143-.049.139-.049.136-.05.131-.05.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.053.083-.051.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.05.023-.024.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.023.01-.022.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.127l-.077.055-.08.053-.083.054-.085.053-.087.052-.09.052-.093.051-.095.05-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.045-.118.044-.12.043-.122.042-.124.042-.126.041-.128.04-.13.04-.132.038-.134.038-.135.037-.138.037-.139.035-.142.035-.143.034-.144.033-.147.032-.148.031-.15.03-.151.03-.153.029-.154.027-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.01-.179.008-.179.008-.181.006-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.006-.179-.008-.179-.008-.178-.01-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.027-.153-.029-.151-.03-.15-.03-.148-.031-.146-.032-.145-.033-.143-.034-.141-.035-.14-.035-.137-.037-.136-.037-.134-.038-.132-.038-.13-.04-.128-.04-.126-.041-.124-.042-.122-.042-.12-.044-.117-.043-.116-.045-.113-.045-.112-.046-.109-.047-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.05-.093-.052-.09-.051-.087-.052-.085-.053-.083-.054-.08-.054-.077-.054v4.127zm0-5.654v.011l.001.021.003.021.004.021.005.022.006.022.007.022.009.022.01.022.011.023.012.023.013.023.015.024.016.023.017.024.018.024.019.024.021.024.022.024.023.025.024.024.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.052.11.051.114.051.119.052.123.05.127.051.131.05.135.049.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.044.171.042.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.022.23.02.233.018.236.016.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.012.241-.015.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.048.139-.05.136-.049.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.051.051-.049.023-.025.023-.024.021-.025.02-.024.019-.024.018-.024.017-.024.015-.023.014-.023.013-.024.012-.022.01-.023.01-.023.008-.022.006-.022.006-.022.004-.021.004-.022.001-.021.001-.021v-4.139l-.077.054-.08.054-.083.054-.085.052-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.049-.105.048-.106.047-.109.047-.111.046-.114.045-.115.044-.118.044-.12.044-.122.042-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.035-.143.033-.144.033-.147.033-.148.031-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.025-.161.024-.162.023-.163.022-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.011-.178.009-.179.009-.179.007-.181.007-.182.005-.182.004-.184.003-.184.002h-.37l-.184-.002-.184-.003-.182-.004-.182-.005-.181-.007-.179-.007-.179-.009-.178-.009-.176-.011-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.022-.162-.023-.161-.024-.159-.025-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.031-.146-.033-.145-.033-.143-.033-.141-.035-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.04-.126-.041-.124-.042-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.047-.105-.048-.102-.049-.1-.049-.097-.05-.095-.051-.093-.051-.09-.051-.087-.053-.085-.052-.083-.054-.08-.054-.077-.054v4.139zm0-5.666v.011l.001.02.003.022.004.021.005.022.006.021.007.022.009.023.01.022.011.023.012.023.013.023.015.023.016.024.017.024.018.023.019.024.021.025.022.024.023.024.024.025.052.05.056.05.061.05.066.051.07.051.075.052.079.051.084.052.088.052.092.052.097.052.102.052.105.051.11.052.114.051.119.051.123.051.127.05.131.05.135.05.139.049.144.048.147.048.152.047.155.046.16.045.163.045.167.043.171.043.176.042.178.04.183.04.187.038.19.037.194.036.197.034.202.033.204.032.209.03.212.028.216.027.219.025.222.024.226.021.23.02.233.018.236.017.24.014.243.012.246.01.249.008.253.006.256.003.259.001.26-.001.257-.003.254-.006.25-.008.247-.01.244-.013.241-.014.237-.016.233-.018.231-.02.226-.022.224-.024.22-.025.216-.027.212-.029.21-.03.205-.032.202-.033.198-.035.194-.036.191-.037.187-.039.183-.039.179-.041.175-.042.172-.043.168-.044.163-.045.16-.045.155-.047.152-.047.148-.048.143-.049.139-.049.136-.049.131-.051.126-.05.123-.051.118-.052.114-.051.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.052.07-.051.065-.051.06-.051.056-.05.051-.049.023-.025.023-.025.021-.024.02-.024.019-.024.018-.024.017-.024.015-.023.014-.024.013-.023.012-.023.01-.022.01-.023.008-.022.006-.022.006-.022.004-.022.004-.021.001-.021.001-.021v-4.153l-.077.054-.08.054-.083.053-.085.053-.087.053-.09.051-.093.051-.095.051-.097.05-.1.049-.102.048-.105.048-.106.048-.109.046-.111.046-.114.046-.115.044-.118.044-.12.043-.122.043-.124.042-.126.041-.128.04-.13.039-.132.039-.134.038-.135.037-.138.036-.139.036-.142.034-.143.034-.144.033-.147.032-.148.032-.15.03-.151.03-.153.028-.154.028-.156.027-.158.026-.159.024-.161.024-.162.023-.163.023-.165.021-.166.02-.167.019-.169.018-.169.017-.171.016-.173.015-.173.014-.175.013-.175.012-.177.01-.178.01-.179.009-.179.007-.181.006-.182.006-.182.004-.184.003-.184.001-.185.001-.185-.001-.184-.001-.184-.003-.182-.004-.182-.006-.181-.006-.179-.007-.179-.009-.178-.01-.176-.01-.176-.012-.175-.013-.173-.014-.172-.015-.171-.016-.17-.017-.169-.018-.167-.019-.166-.02-.165-.021-.163-.023-.162-.023-.161-.024-.159-.024-.157-.026-.156-.027-.155-.028-.153-.028-.151-.03-.15-.03-.148-.032-.146-.032-.145-.033-.143-.034-.141-.034-.14-.036-.137-.036-.136-.037-.134-.038-.132-.039-.13-.039-.128-.041-.126-.041-.124-.041-.122-.043-.12-.043-.117-.044-.116-.044-.113-.046-.112-.046-.109-.046-.106-.048-.105-.048-.102-.048-.1-.05-.097-.049-.095-.051-.093-.051-.09-.052-.087-.052-.085-.053-.083-.053-.08-.054-.077-.054v4.153zm8.74-8.179l-.257.004-.254.005-.25.008-.247.011-.244.012-.241.014-.237.016-.233.018-.231.021-.226.022-.224.023-.22.026-.216.027-.212.028-.21.031-.205.032-.202.033-.198.034-.194.036-.191.038-.187.038-.183.04-.179.041-.175.042-.172.043-.168.043-.163.045-.16.046-.155.046-.152.048-.148.048-.143.048-.139.049-.136.05-.131.05-.126.051-.123.051-.118.051-.114.052-.11.052-.106.052-.101.052-.096.052-.092.052-.088.052-.083.052-.079.052-.074.051-.07.052-.065.051-.06.05-.056.05-.051.05-.023.025-.023.024-.021.024-.02.025-.019.024-.018.024-.017.023-.015.024-.014.023-.013.023-.012.023-.01.023-.01.022-.008.022-.006.023-.006.021-.004.022-.004.021-.001.021-.001.021.001.021.001.021.004.021.004.022.006.021.006.023.008.022.01.022.01.023.012.023.013.023.014.023.015.024.017.023.018.024.019.024.02.025.021.024.023.024.023.025.051.05.056.05.06.05.065.051.07.052.074.051.079.052.083.052.088.052.092.052.096.052.101.052.106.052.11.052.114.052.118.051.123.051.126.051.131.05.136.05.139.049.143.048.148.048.152.048.155.046.16.046.163.045.168.043.172.043.175.042.179.041.183.04.187.038.191.038.194.036.198.034.202.033.205.032.21.031.212.028.216.027.22.026.224.023.226.022.231.021.233.018.237.016.241.014.244.012.247.011.25.008.254.005.257.004.26.001.26-.001.257-.004.254-.005.25-.008.247-.011.244-.012.241-.014.237-.016.233-.018.231-.021.226-.022.224-.023.22-.026.216-.027.212-.028.21-.031.205-.032.202-.033.198-.034.194-.036.191-.038.187-.038.183-.04.179-.041.175-.042.172-.043.168-.043.163-.045.16-.046.155-.046.152-.048.148-.048.143-.048.139-.049.136-.05.131-.05.126-.051.123-.051.118-.051.114-.052.11-.052.106-.052.101-.052.096-.052.092-.052.088-.052.083-.052.079-.052.074-.051.07-.052.065-.051.06-.05.056-.05.051-.05.023-.025.023-.024.021-.024.02-.025.019-.024.018-.024.017-.023.015-.024.014-.023.013-.023.012-.023.01-.023.01-.022.008-.022.006-.023.006-.021.004-.022.004-.021.001-.021.001-.021-.001-.021-.001-.021-.004-.021-.004-.022-.006-.021-.006-.023-.008-.022-.01-.022-.01-.023-.012-.023-.013-.023-.014-.023-.015-.024-.017-.023-.018-.024-.019-.024-.02-.025-.021-.024-.023-.024-.023-.025-.051-.05-.056-.05-.06-.05-.065-.051-.07-.052-.074-.051-.079-.052-.083-.052-.088-.052-.092-.052-.096-.052-.101-.052-.106-.052-.11-.052-.114-.052-.118-.051-.123-.051-.126-.051-.131-.05-.136-.05-.139-.049-.143-.048-.148-.048-.152-.048-.155-.046-.16-.046-.163-.045-.168-.043-.172-.043-.175-.042-.179-.041-.183-.04-.187-.038-.191-.038-.194-.036-.198-.034-.202-.033-.205-.032-.21-.031-.212-.028-.216-.027-.22-.026-.224-.023-.226-.022-.231-.021-.233-.018-.237-.016-.241-.014-.244-.012-.247-.011-.25-.008-.254-.005-.257-.004-.26-.001-.26.001z"></path></symbol></defs><defs><symbol id="clock" width="24" height="24"><path transform="scale(.5)" d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm5.848 12.459c.202.038.202.333.001.372-1.907.361-6.045 1.111-6.547 1.111-.719 0-1.301-.582-1.301-1.301 0-.512.77-5.447 1.125-7.445.034-.192.312-.181.343.014l.985 6.238 5.394 1.011z"></path></symbol></defs><line id="actor96" x1="75" y1="80" x2="75" y2="731" class="200" stroke-width="0.5px" stroke="#999"></line><g class="actor-man"><line id="actor-man-torso96" x1="75" y1="25" x2="75" y2="45"></line><line id="actor-man-arms96" x1="57" y1="33" x2="93" y2="33"></line><line x1="57" y1="60" x2="75" y2="45"></line><line x1="75" y1="45" x2="91" y2="60"></line><circle cx="75" cy="10" r="15" width="150" height="65"></circle><text x="75" y="67.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">读线程</tspan></text></g><g><line id="actor97" x1="275" y1="5" x2="275" y2="731" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-97"><rect x="200" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="275" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="275" dy="0">缓存</tspan></text></g></g><g><line id="actor98" x1="475" y1="5" x2="475" y2="731" class="200" stroke-width="0.5px" stroke="#999"></line><g id="root-98"><rect x="400" y="0" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="475" y="32.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="475" dy="0">数据库</tspan></text></g></g><line id="actor99" x1="689" y1="80" x2="689" y2="731" class="200" stroke-width="0.5px" stroke="#999"></line><g class="actor-man"><line id="actor-man-torso99" x1="689" y1="25" x2="689" y2="45"></line><line id="actor-man-arms99" x1="671" y1="33" x2="707" y2="33"></line><line x1="671" y1="60" x2="689" y2="45"></line><line x1="689" y1="45" x2="705" y2="60"></line><circle cx="689" cy="10" r="15" width="150" height="65"></circle><text x="689" y="67.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="689" dy="0">写线程</tspan></text></g><defs><marker id="arrowhead" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z"></path></marker></defs><defs><marker id="crosshead" markerWidth="15" markerHeight="8" orient="auto" refX="16" refY="4"><path fill="black" stroke="#000000" stroke-width="1px" d="M 9,2 V 6 L16,4 Z" style="stroke-dasharray: 0, 0;"></path><path fill="none" stroke="#000000" stroke-width="1px" d="M 0,1 L 6,7 M 6,1 L 0,7" style="stroke-dasharray: 0, 0;"></path></marker></defs><defs><marker id="filled-head" refX="18" refY="7" markerWidth="20" markerHeight="28" orient="auto"><path d="M 18,7 L9,13 L14,7 L9,1 Z"></path></marker></defs><defs><marker id="sequencenumber" refX="15" refY="15" markerWidth="60" markerHeight="40" orient="auto"><circle cx="15" cy="15" r="6"></circle></marker></defs><g><rect x="250" y="75" fill="#EDF2AE" stroke="#666" width="250" height="36" rx="0" ry="0" class="note"></rect><text x="375" y="80" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="noteText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;"><tspan x="375">数据库值为100，缓存没有值</tspan></text></g><text x="175" y="126" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">1.查询缓存</text><line x1="75" y1="159" x2="275" y2="159" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="270" y="159" fill="#EDF2AE" stroke="#666" width="10" height="48" rx="0" ry="0" class="activation0"></rect></g><g><rect x="70" y="161" fill="#EDF2AE" stroke="#666" width="10" height="530" rx="0" ry="0" class="activation0"></rect></g><text x="175" y="174" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">缓存值不存在</text><line x1="270" y1="207" x2="80" y2="207" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="278" y="222" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">2.查询数据库</text><line x1="80" y1="255" x2="475" y2="255" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="470" y="257" fill="#EDF2AE" stroke="#666" width="10" height="50" rx="0" ry="0" class="activation0"></rect></g><text x="275" y="270" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">查询成功返回值100</text><line x1="470" y1="307" x2="80" y2="307" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="582" y="322" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">3.更新数据库值为80</text><line x1="689" y1="355" x2="475" y2="355" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="470" y="357" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><g><rect x="684" y="357" fill="#EDF2AE" stroke="#666" width="10" height="334" rx="0" ry="0" class="activation0"></rect></g><text x="582" y="370" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新成功</text><line x1="480" y1="403" x2="684" y2="403" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><text x="480" y="418" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">4.删除缓存</text><line x1="684" y1="451" x2="275" y2="451" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="270" y="453" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><text x="482" y="466" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">删除缓存成功</text><line x1="280" y1="499" x2="684" y2="499" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><g><rect x="250" y="509" fill="#EDF2AE" stroke="#666" width="250" height="36" rx="0" ry="0" class="note"></rect><text x="375" y="514" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="noteText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;"><tspan x="375">数据库值为80，缓存没有值</tspan></text></g><text x="178" y="560" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">5.更新缓存为100</text><line x1="80" y1="597" x2="275" y2="597" class="messageLine0" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="fill: none;"></line><g><rect x="270" y="599" fill="#EDF2AE" stroke="#666" width="10" height="46" rx="0" ry="0" class="activation0"></rect></g><text x="175" y="612" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="messageText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 16px; font-weight: 400;">更新成功</text><line x1="270" y1="645" x2="80" y2="645" class="messageLine1" stroke-width="2" stroke="none" marker-end="url(#arrowhead)" style="stroke-dasharray: 3, 3; fill: none;"></line><g><rect x="250" y="655" fill="#EDF2AE" stroke="#666" width="250" height="36" rx="0" ry="0" class="note"></rect><text x="375" y="660" text-anchor="middle" dominant-baseline="middle" alignment-baseline="middle" class="noteText" dy="1em" style="font-family: &quot;trebuchet ms&quot;, verdana, arial, sans-serif; font-size: 14px; font-weight: 400;"><tspan x="375">数据库值为80，缓存值为100</tspan></text></g><g class="actor-man"><line id="actor-man-torso99" x1="75" y1="736" x2="75" y2="756"></line><line id="actor-man-arms99" x1="57" y1="744" x2="93" y2="744"></line><line x1="57" y1="771" x2="75" y2="756"></line><line x1="75" y1="756" x2="91" y2="771"></line><circle cx="75" cy="721" r="15" width="150" height="65"></circle><text x="75" y="778.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="75" dy="0">读线程</tspan></text></g><g><rect x="200" y="711" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="275" y="743.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="275" dy="0">缓存</tspan></text></g><g><rect x="400" y="711" fill="#eaeaea" stroke="#666" width="150" height="65" rx="3" ry="3" class="actor"></rect><text x="475" y="743.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="475" dy="0">数据库</tspan></text></g><g class="actor-man"><line id="actor-man-torso99" x1="689" y1="736" x2="689" y2="756"></line><line id="actor-man-arms99" x1="671" y1="744" x2="707" y2="744"></line><line x1="671" y1="771" x2="689" y2="756"></line><line x1="689" y1="756" x2="705" y2="771"></line><circle cx="689" cy="721" r="15" width="150" height="65"></circle><text x="689" y="778.5" dominant-baseline="central" alignment-baseline="central" class="actor" style="text-anchor: middle; font-size: 14px; font-weight: 400; font-family: Open-Sans, &quot;sans-serif&quot;;"><tspan x="689" dy="0">写线程</tspan></text></g></svg></div><ol><li>读线程发起查询请求，此时缓存恰好失效了（可能是到期了），直接到数据库查询，查到了数据还没有来得及去设置缓存。（此时数据库值为100缓存没有值）</li><li>写线程要更新值，先更新数据库，再失效缓存。（此时数据库值为80缓存没有值）</li><li>读线程这时才设置缓存。（此时数据库值为80缓存值为100）</li></ol><p>这样的话最终还是会导致缓存和数据库不一致，主要就是因为缓存突然失效了，而且还要保证写线程的更新操作比读线程的<mark>查询并且更新缓存的操作</mark>还要快。（实际上这种情况发生的概率很低）要发生这种情况的前提条件是写数据库要先于读数据库完成，一般而言数据库的读相比于写耗时更短，这种前提条件成立的概率很低。采用上文提到的延时双删方法可以达到最终一致。</p><blockquote><p>比较于上面的<strong>先删除缓存再更新数据库</strong>，<strong>先更新数据库再失效缓存依然会有问题</strong>，但是概率非常低。</p></blockquote><h2 id="%E5%AF%BC%E8%87%B4%E4%B8%8D%E4%B8%80%E8%87%B4%E7%9A%84%E5%8E%9F%E5%9B%A0" tabindex="-1">导致不一致的原因</h2><ul><li><strong>逻辑失败造成的数据不一致</strong>：在并发的情况下，无论是先删除缓存后更新数据库，还是先更新数据库后失效缓存，都会有数据不一致的情况，主要是由读写请求在并发情况下的操作时序导致的，这种特殊时序造成的不一致称之为“逻辑失败”，解决这种因为并发时序导致的不一致，核心的解决思想是将操作进行串行化。</li><li><strong>物理失败造成的数据不一致</strong>：先更新数据库后删除缓存，如果删除缓存操作出现失败，也会出现数据不一致的情况。但是数据库更新以及缓存操作不适合放到一个事务中，一般来说，如果使用分布式缓存有网络传输的耗时，如果这个耗时较长，那么将更新数据库以及失效缓存放到一个事务中，就会造成事务耗时过长，很快耗尽数据库的连接池，严重的降低系统性能，导致系统崩溃。像这种因为操作失败导致的数据不一致称之为“物理失败”，大多数物理失败的情况可以用重试的方案解决。</li></ul><h2 id="%E6%9C%80%E7%BB%88%E4%BF%9D%E8%AF%81%E4%B8%80%E8%87%B4%E6%80%A7%E7%9A%84%E6%96%B9%E6%A1%88" tabindex="-1">最终保证一致性的方案</h2><div class="mermaid"><svg id="render4059917413" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="589.268310546875" style="max-width: 841.296875px;" viewBox="0 0 841.296875 589.268310546875"><style>#render4059917413 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render4059917413 .error-icon{fill:#a5a7bb;}#render4059917413 .error-text{fill:#5a5844;stroke:#5a5844;}#render4059917413 .edge-thickness-normal{stroke-width:2px;}#render4059917413 .edge-thickness-thick{stroke-width:3.5px;}#render4059917413 .edge-pattern-solid{stroke-dasharray:0;}#render4059917413 .edge-pattern-dashed{stroke-dasharray:3;}#render4059917413 .edge-pattern-dotted{stroke-dasharray:2;}#render4059917413 .marker{fill:#F8B229;stroke:#F8B229;}#render4059917413 .marker.cross{stroke:#F8B229;}#render4059917413 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render4059917413 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render4059917413 .cluster-label text{fill:#5a5844;}#render4059917413 .cluster-label span{color:#5a5844;}#render4059917413 .label text,#render4059917413 span{fill:#fff;color:#fff;}#render4059917413 .node rect,#render4059917413 .node circle,#render4059917413 .node ellipse,#render4059917413 .node polygon,#render4059917413 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render4059917413 .node .label{text-align:center;}#render4059917413 .node.clickable{cursor:pointer;}#render4059917413 .arrowheadPath{fill:undefined;}#render4059917413 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render4059917413 .flowchart-link{stroke:#F8B229;fill:none;}#render4059917413 .edgeLabel{background-color:#006100;text-align:center;}#render4059917413 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render4059917413 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render4059917413 .cluster text{fill:#5a5844;}#render4059917413 .cluster span{color:#5a5844;}#render4059917413 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render4059917413 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 8)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path d="M350.375,294.6341552734375L335.9733072916667,294.6341552734375C321.5716145833333,294.6341552734375,292.7682291666667,294.6341552734375,263.96484375,294.6341552734375C235.16145833333334,294.6341552734375,206.35807291666666,294.6341552734375,191.95638020833334,294.6341552734375L177.5546875,294.6341552734375" id="L-sub-Main-0" class=" edge-thickness-normal edge-pattern-dotted flowchart-link LS-sub LE-Main" style="fill:none;stroke-width:2px;stroke-dasharray:3;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel" transform="translate(263.96484375, 294.6341552734375)"><g class="label" transform="translate(-61.41015625, -9.5)"><rect rx="0" ry="0" width="122.75464630126953" height="19.000085830688477"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">3.订阅binlog日志</tspan></text></g></g></g><g class="nodes"><g class="root" transform="translate(342.875, 0)"><g class="clusters"><g class="cluster default" id="sub"><rect style="" rx="0" ry="0" x="8" y="8" width="474.921875" height="565.2682914733887"></rect><g class="cluster-label" transform="translate(205.4609375, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">非业务代码</tspan></text></g></g></g><g class="edgePaths"><path d="M251.515625,67L251.515625,71.16666666666667C251.515625,75.33333333333333,251.515625,83.66666666666667,251.515625,92C251.515625,100.33333333333333,251.515625,108.66666666666667,251.515625,112.83333333333333L251.515625,117" id="L-E-F-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-E LE-F" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M251.515625,151L251.515625,155.16666666666666C251.515625,159.33333333333334,251.515625,167.66666666666666,251.515625,176C251.515625,184.33333333333334,251.515625,192.66666666666666,251.515625,196.83333333333334L251.515625,201" id="L-F-G-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-F LE-G" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M219.91731770833334,235L212.17263454861111,239.16666666666666C204.4279513888889,243.33333333333334,188.93858506944446,251.66666666666666,181.27723524305557,260.0833333333333C173.61588541666666,268.5,173.78255208333334,277,173.86588541666666,281.25L173.94921875,285.5" id="L-G-H-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-G LE-H" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M147.89312738541201,390.443908635412L141.14271032117668,400.4532571961767C134.39229325694134,410.4626057569414,120.89145912847067,430.4813028784707,114.14104206423535,448.67967572868434C107.390625,466.87804857889813,107.390625,483.2560971577962,107.390625,491.4451214472453L107.390625,499.63414573669434" id="L-H-I-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-H LE-I" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M200.00531011458799,390.443908635412L206.58906051215664,400.4532571961767C213.1728109097253,410.4626057569414,226.34031170486264,430.4813028784707,239.71150648441025,447.28379918838C253.0827012639578,464.08629549828925,266.6575900279156,477.6725909965785,273.4450344098945,484.46573874572306L280.2324787918734,491.25888649486774" id="L-H-J-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-H LE-J" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M346.28167397725326,487.9622783523613L351.4997022727111,481.7185652936344C356.71773056816886,475.47485223490753,367.15378715908446,462.9874261174538,372.37181545454223,440.07704639206025C377.58984375,417.1666666666667,377.58984375,383.8333333333333,377.58984375,352.0833333333333C377.58984375,320.3333333333333,377.58984375,290.1666666666667,364.4329427083333,270.70028143557965C351.2760416666667,251.23389620449265,324.9622395833333,242.46779240898528,311.8053385416667,238.08474051123162L298.6484375,233.70168861347793" id="L-J-G-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-J LE-G" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(107.390625, 450.5)"><g class="label" transform="translate(-12.0859375, -9.5)"><rect rx="0" ry="0" width="24.161867141723633" height="19.000085830688477"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Yes</tspan></text></g></g><g class="edgeLabel" transform="translate(239.5078125, 450.5)"><g class="label" transform="translate(-100.03125, -9.5)"><rect rx="0" ry="0" width="199.94386291503906" height="19.000085830688477"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">6.发送提取出来的数据和key</tspan></text></g></g><g class="edgeLabel" transform="translate(377.58984375, 350.5)"><g class="label" transform="translate(-55.6328125, -9.5)"><rect rx="0" ry="0" width="111.2071533203125" height="19.000085830688477"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">7.重试删除缓存</tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-I-294" transform="translate(107.390625, 516.6341457366943)"><rect style="" rx="17" ry="17" x="-28.75" y="-17" width="57.5" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">结束</tspan></text></g></g><g class="node default default" id="flowchart-E-290" transform="translate(251.515625, 50)"><rect style="" rx="17" ry="17" x="-28.75" y="-17" width="57.5" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">开始</tspan></text></g></g><g class="node default default" id="flowchart-F-291" transform="translate(251.515625, 134)"><rect class="basic label-container" style="" rx="0" ry="0" x="-100.03125" y="-17" width="200.0625" height="34"></rect><g class="label" style="" transform="translate(-92.53125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">4.提取出操作的数据和key</tspan></text></g></g><g class="node default default" id="flowchart-G-292" transform="translate(251.515625, 218)"><rect class="basic label-container" style="" rx="0" ry="0" x="-47.1328125" y="-17" width="94.265625" height="34"></rect><g class="label" style="" transform="translate(-39.6328125, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">5.删除缓存</tspan></text></g></g><g class="node default default" id="flowchart-H-293" transform="translate(173.44921875, 350.5)"><polygon points="65.5,0 131,-65.5 65.5,-131 0,-65.5" class="label-container" transform="translate(-65.5,65.5)" style=""></polygon><g class="label" style="" transform="translate(-33, -17.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">删除缓存</tspan><tspan xml:space="preserve" dy="1em" x="0" class="row">成功?</tspan></text></g></g><g class="node default default" id="flowchart-J-296" label-offset-y="9.756097560975611" transform="translate(317.57421875, 516.6341457366943)"><path style="" d="M 0,9.756097560975611 a 40,9.756097560975611 0,0,0 80 0 a 40,9.756097560975611 0,0,0 -80 0 l 0,43.75609756097561 a 40,9.756097560975611 0,0,0 80 0 l 0,-43.75609756097561" transform="translate(-40,-31.634146341463417)"></path><g class="label" style="" transform="translate(-32.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">消息队列</tspan></text></g></g></g></g><g class="root" transform="translate(0.5, 82.93574523925781)"><g class="clusters"><g class="cluster default" id="Main"><rect style="" rx="0" ry="0" x="8" y="8" width="161.5546875" height="399.39682388305664"></rect><g class="cluster-label" transform="translate(56.77734375, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">业务代码</tspan></text></g></g></g><g class="edgePaths"><path d="M88.77734375,67L88.77734375,72.75C88.77734375,78.5,88.77734375,90,88.77734375,101.5C88.77734375,113,88.77734375,124.5,88.77734375,130.25L88.77734375,136" id="L-A-B-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-A LE-B" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M88.77734375,195.39682388305664L88.77734375,201.14682388305664C88.77734375,206.89682388305664,88.77734375,218.39682388305664,88.77734375,229.89682388305664C88.77734375,241.39682388305664,88.77734375,252.89682388305664,88.77734375,258.64682388305664L88.77734375,264.39682388305664" id="L-B-C-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-B LE-C" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M88.77734375,298.39682388305664L88.77734375,302.5634905497233C88.77734375,306.73015721638996,88.77734375,315.0634905497233,88.77734375,323.39682388305664C88.77734375,331.73015721638996,88.77734375,340.0634905497233,88.77734375,344.23015721638996L88.77734375,348.39682388305664" id="L-C-D-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-C LE-D" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel" transform="translate(88.77734375, 101.5)"><g class="label" transform="translate(-47.1328125, -9.5)"><rect rx="0" ry="0" width="94.21917724609375" height="19.000085830688477"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">1.更新数据库</tspan></text></g></g><g class="edgeLabel" transform="translate(88.77734375, 229.89682388305664)"><g class="label" transform="translate(-44.9140625, -9.5)"><rect rx="0" ry="0" width="89.76602935791016" height="19.000085830688477"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">2.写入binlog</tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-D-287" transform="translate(88.77734375, 365.39682388305664)"><rect style="" rx="17" ry="17" x="-28.75" y="-17" width="57.5" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">结束</tspan></text></g></g><g class="node default default" id="flowchart-A-284" transform="translate(88.77734375, 50)"><rect style="" rx="17" ry="17" x="-28.75" y="-17" width="57.5" height="34"></rect><g class="label" style="" transform="translate(-17, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">开始</tspan></text></g></g><g class="node default default" id="flowchart-B-285" label-offset-y="8.465608465608465" transform="translate(88.77734375, 165.69841194152832)"><path style="" d="M 0,8.465608465608465 a 32,8.465608465608465 0,0,0 64 0 a 32,8.465608465608465 0,0,0 -64 0 l 0,42.46560846560847 a 32,8.465608465608465 0,0,0 64 0 l 0,-42.46560846560847" transform="translate(-32,-29.6984126984127)"></path><g class="label" style="" transform="translate(-24.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">数据库</tspan></text></g></g><g class="node default default" id="flowchart-C-286" transform="translate(88.77734375, 281.39682388305664)"><rect class="basic label-container" style="" rx="0" ry="0" x="-45.77734375" y="-17" width="91.5546875" height="34"></rect><g class="label" style="" transform="translate(-38.27734375, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">binlog日志</tspan></text></g></g></g></g></g></g></g></svg></div><p>主要分为以下几步：</p><ol><li>更新数据库数据。</li><li>数据库会将操作信息写入Binlog日志当中。</li><li>订阅程序提取出所需要的数据以及key。</li><li>另起一段非业务代码，获得该信息。</li><li>尝试删除缓存操作。</li><li>如果删除缓存操作失败，将这些信息发送至消息队列。</li><li>重新从消息队列中获得该数据，重试删除操作。</li></ol><blockquote><p>采用这种方案，业务系统只负责处理业务逻辑，更新MySQL，完全不用管如何去更新缓存。而负责更新缓存的服务，把自己伪装成一个MySQL的从节点，从MySQL接收Binlog，解析Binlog之后，可以得到实时的数据变更信息，然后根据这个变更信息去更新缓存。这个方案的缺点是，实现缓存更新服务有点儿复杂，毕竟解析Binlog文件还是很麻烦的。</p></blockquote>]]>
                    </description>
                    <pubDate>Sat, 21 May 2022 22:43:46 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[MoreKey & BigKey]]>
                    </title>
                    <link>https://zygzyg.com/archives/d81c7210d6b646ee9444eb22c92a384d</link>
                    <description>
                            <![CDATA[<h1 id="morekey-%26-bigkey" tabindex="-1">MoreKey &amp; BigKey</h1><h2 id="morekey%E9%97%AE%E9%A2%98" tabindex="-1">MoreKey问题</h2><blockquote><p>假如现在Redis服务上有100W条记录，如何遍历？<code>keys *</code>可以吗？</p></blockquote><h3 id="%E6%A8%A1%E6%8B%9F%E5%A4%A7%E9%87%8F%E7%9A%84key" tabindex="-1">模拟大量的key</h3><p>首先要在Redis里面插入100W条记录，这里先生成一个临时文件使用命令：</p><pre><code class="language-shell">for((i=1;i&lt;=100*10000);i++); do echo &quot;set k$i v$i&quot; &gt;&gt;/tmp/redisTest.txt; done;</code></pre><p>等待完毕后使用<code>more</code>查看文件内容：</p><pre><code class="language-shell">root@zygzyg:~# more /tmp/redisTest.txt set k1 v1set k2 v2set k3 v3set k4 v4set k5 v5set k6 v6set k7 v7set k8 v8...</code></pre><p>可以看到命令已经成功的写入文件(大概耗时几秒钟吧)。</p><h3 id="%E6%89%B9%E9%87%8F%E6%89%A7%E8%A1%8C%E5%91%BD%E4%BB%A4" tabindex="-1">批量执行命令</h3><blockquote><p>上面已经生成了一个有100W条命令的文件，那么如何将这些记录写入Redis呢？这里可以使用<a href="https://zygzyg.cloud/archives/redispipline" target="_blank">Redis管道（Redis Pipline）</a>。</p></blockquote><p>这里使用Pipline即可将redisTest.txt中的命令写入Redis。</p><pre><code class="language-shell">root@3581da7bfc4e:/data#cat /tmp/redisTest.txt | redis-cli -h 127.0.0.1 -p 6379 --pipeAll data transferred. Waiting for the last reply...Last reply received from server.errors: 0, replies: 1000000</code></pre><p>这里可以看到已经成功写入100W条记录了，使用<code>DBSIZE</code>查看：</p><pre><code class="language-shell">127.0.0.1:6379&gt; DBSIZE(integer) 1000000</code></pre><h3 id="%E6%B5%8B%E8%AF%95keys-*" tabindex="-1">测试<code>keys *</code></h3><pre><code class="language-shell">127.0.0.1:6379&gt; keys * ......1000000) &quot;k375734&quot;1000001) &quot;k911335&quot;1000002) &quot;k702315&quot;1000003) &quot;k538803&quot;1000004) &quot;k254614&quot;(113.48s)</code></pre><p>可以看到这里耗时有113秒左右，由于<a href="https://zygzyg.cloud/archives/redis%E6%98%AF%E5%8D%95%E7%BA%BF%E7%A8%8B%E8%BF%98%E6%98%AF%E5%A4%9A%E7%BA%BF%E7%A8%8B" target="_blank">Redis<mark>执行命令是单线程</mark>的</a>，也就是说这里的<code>keys *</code>执行返回之前该线程是处于阻塞状态的。所以<code>keys *</code>这个命令有致命的弊端，在实际生产中最好是不要使用。</p><blockquote><p>⚠️这个命令没有<code>offset</code>、<code>limit</code>参数，也就是一旦执行就会查出所有满足条件的key，由于Redis<mark>执行命令是单线程</mark>的，它的所有操作都是原子的，而keys算法是遍历算法，复杂度是O(n)，如果Redis中有百万级或者千万级以上的key，这个指令就会导致Redis服务阻塞，所有读写Redis的其他指令都会被迫延迟甚至超时报错，可能会引起缓存雪崩、数据库压力飙升甚至宕机。</p></blockquote><h4 id="%E7%A6%81%E7%94%A8redis%E5%91%BD%E4%BB%A4" tabindex="-1">禁用Redis命令</h4><blockquote><p>既然这些命令在生产上容易出现问题，那么如何在生产上限制这些命令的使用呢？</p></blockquote><p>可以通过Redis配置redis.conf来禁用这些命令</p><pre><code class="language-conf"># It is also possible to completely kill a command by renaming it into# an empty string:# 简答说就是将想要禁用的命令后面配置为空字符串即可# rename-command CONFIG &quot;&quot;rename-command KEYS &quot;&quot;rename-command FLUSHALL &quot;&quot;rename-command FLUSHDB &quot;&quot;</code></pre><p>通过上述配置就可以禁用<code>KEYS</code>、<code>FLUSHALL</code>、<code>FLUSHDB</code>命令了，再次使用会报如下错误：</p><pre><code class="language-shell">127.0.0.1:6379&gt; keys *(error) ERR unknown command &#39;keys&#39;, with args beginning with: &#39;*&#39; 127.0.0.1:6379&gt; FLUSHALL(error) ERR unknown command &#39;FLUSHALL&#39;, with args beginning with: 127.0.0.1:6379&gt; FLUSHDB(error) ERR unknown command &#39;FLUSHDB&#39;, with args beginning with: </code></pre><h3 id="scan%E5%91%BD%E4%BB%A4" tabindex="-1"><code>SCAN</code>命令</h3><blockquote><p>既然<code>KEYS *</code>会造成阻塞，那么该如何遍历呢？<a href="https://redis.io/commands/scan/" target="_blank">Redis还提供了<code>SCAN</code>命令</a>，有点类似于MySQL中的limit，但是并不是完全相同。</p></blockquote><pre><code class="language-shell">SCAN cursor [MATCH pattern] [COUNT count]</code></pre><ul><li>cursor：游标。</li><li>pattern：匹配的模式。</li><li>count：指定从数据集里面返回多少元素，默认是10。</li></ul><p>它是基于游标的迭代器，需要基于上一次的游标延续之前的迭代过程以0作为游标开始一次新的迭代，直到命令返回游标0完成一次遍历，不保证每次执行都返回某个给定数量的元素，支持模糊查询一次返回的数量不可控，只能是大概率符合<code>count</code>参数。</p><pre><code class="language-shell">127.0.0.1:6379&gt; SCAN 0 MATCH k* COUNT 101) &quot;196608&quot;2)  1) &quot;k176810&quot;    2) &quot;k738876&quot;    3) &quot;k689302&quot;    4) &quot;k526619&quot;    5) &quot;k600652&quot;    6) &quot;k152020&quot;    7) &quot;k289919&quot;    8) &quot;k819980&quot;    9) &quot;k98466&quot;   10) &quot;k755213&quot;   11) &quot;k10086&quot;127.0.0.1:6379&gt; SCAN 196608 MATCH k* COUNT 101) &quot;425984&quot;2)  1) &quot;k181985&quot;    2) &quot;k870789&quot;    3) &quot;k535112&quot;    4) &quot;k990462&quot;    5) &quot;k591390&quot;    6) &quot;k188791&quot;    7) &quot;k917745&quot;    8) &quot;k483131&quot;    9) &quot;k511975&quot;   10) &quot;k273108&quot;   11) &quot;k479350&quot;</code></pre><h4 id="scan%E7%9A%84%E9%81%8D%E5%8E%86%E9%A1%BA%E5%BA%8F" tabindex="-1"><code>SCAN</code>的遍历顺序</h4><p>SCAN的遍历顺序非常特别，它不是从第一维数组的第零位一直遍历到末尾，而是采用了高位进位加法来遍历。之所以使用这样特殊的方式进行遍历，是考虑到字典的扩容和缩容时避免槽位的遍历重复和遗漏。</p><h2 id="bigkey%E9%97%AE%E9%A2%98" tabindex="-1">BigKey问题</h2><blockquote><p>多大的Key算BigKey呢？严格来说真正大的应该是key对应的value。可以参考<a href="https://developer.aliyun.com/article/531067#slide-2" target="_blank">《阿里云Redis开发规范》</a>：</p><p><mark>string类型控制在10KB以内，hash、list、set、zset元素个数不要超过5000。</mark></p><p>反例：一个包含200万个元素的list。</p><p><mark>非字符串的bigkey，不要使用del删除</mark>，使用hscan、sscan、zscan方式渐进式删除，同时要注意防止bigkey过期时间自动删除问题(例如一个200万的zset设置1小时过期，会触发del操作，造成阻塞，而且该操作不会不出现在慢查询中(latency可查)，<a href="https://developer.aliyun.com/article/531067#cc1" target="_blank">查找方法</a>和<a href="https://developer.aliyun.com/article/531067#cc2" target="_blank">删除方法</a>。</p><p>那么BigKey会造成哪些问题呢？</p></blockquote><h3 id="bigkey%E4%BC%9A%E9%80%A0%E6%88%90%E5%93%AA%E4%BA%9B%E9%97%AE%E9%A2%98" tabindex="-1">BigKey会造成哪些问题</h3><ol><li>如果是集群模式下，无法做到负载均衡，导致请求倾斜到某个实例上，而这个实例的QPS会比较大，内存占用也较多；对于Redis单线程模型又容易出现CPU瓶颈，当内存出现瓶颈时，只能进行纵向扩容。</li><li>涉及到大key的操作，尤其是使用<code>hgetall</code>、<code>lrange 0 -1</code>、<code>get</code>、<code>hmget</code> 等操作时，网卡可能会成为瓶颈，也会到导致堵塞其它操作，QPS就有可能出现突降或者突升的情况，趋势上看起来十分不平滑，严重时会导致应用程序连不上，实例或者集群在某些时间段内不可用的状态。</li><li>假如这个key需要进行删除操作，如果直接进行<code>DEL</code>操作，被操作的实例会被阻塞，导致无法响应应用的请求，而这个Block的时间会随着key的变大而变长。</li></ol><h3 id="bigkey%E6%98%AF%E5%A6%82%E4%BD%95%E4%BA%A7%E7%94%9F%E7%9A%84" tabindex="-1">BigKey是如何产生的</h3><p>一般来说，bigkey是由于程序员的程序设计不当，或对数据规模预料不清楚造成的：</p><ol><li>社交类：粉丝列表，如果某些明星或大V，粉丝逐渐增加。一定会造成BigKey。</li><li>统计类：如果按天存储某项功能或网站的用户集合，除非没几个人用，否则必定是bigkey。</li><li>缓存类：作为数据库数据的冗余存储，这种是redis的最常用场景，但有2点要注意：<ol><li>是不是有必要把所有数据都缓存？</li><li>有没有相关关联的数据？</li></ol></li></ol><p>举个例子，该同学把某明星一个专辑下的所有视频信息都缓存成了一个巨大的json，这个json达到了6MB。</p><h3 id="bigkey%E5%A6%82%E4%BD%95%E5%8F%91%E7%8E%B0" tabindex="-1">BigKey如何发现</h3><ol><li><p><code>redis-cli--bigkeys</code></p><blockquote><p>redis-cli原生自带–bigkeys功能，可以找到某个实例5种数据类型(String、hash、list、set、zset)的最大key。</p></blockquote><p>其好处有能给出每种数据结构TOP 1的BigKey，同时还能给出每种数据类型的键值个数和平均大小。</p><p>不足之处有，当想要查询大于10kb的所有key时，–bigkeys就无能为力了，这时可以用memory useage来计算每个键值对的字节数。</p><pre><code class="language-shell">root@3581da7bfc4e:/data# redis-cli --bigkeys# Scanning the entire keyspace to find biggest keys as well as# average sizes per key type.  You can use -i 0.1 to sleep 0.1 sec# per 100 SCAN commands (not usually needed).[00.00%] Biggest string found so far &#39;&quot;k176810&quot;&#39; with 7 bytes[42.58%] Biggest string found so far &#39;&quot;Test&quot;&#39; with 19 bytes[100.00%] Sampled 1000000 keys so far-------- summary -------Sampled 1000004 keys in the keyspace!Total key length in bytes is 6888908 (avg len 6.89)Biggest string found &#39;&quot;Test&quot;&#39; has 19 bytes0 lists with 0 items (00.00% of keys, avg size 0.00)0 hashs with 0 fields (00.00% of keys, avg size 0.00)1000004 strings with 6888925 bytes (100.00% of keys, avg size 6.89)0 streams with 0 entries (00.00% of keys, avg size 0.00)0 sets with 0 members (00.00% of keys, avg size 0.00)0 zsets with 0 members (00.00% of keys, avg size 0.00)</code></pre><p>如果加上 <code>-i 0.1</code>，那么每隔100条<code>scan</code>命令就会休眠0.1秒，这要QPS就不会剧烈抬升，但是扫描的时间会变长。</p></li><li><p><code>memory useage</code></p><p>命令<code>MEMORY USAGE</code> 给出一个key和它值在内存中占用的字节数，返回的结果是key的值以及为管理该key分配的内存总字节数，对于嵌套数据类型，可以使用选项<code>SAMPLES</code>，其中<code>COUNT</code>表示抽样的元素个数，默认值为5。当需要抽样所有元素时，使用<code>SAMPLES 0</code></p><pre><code class="language-shell">127.0.0.1:6379&gt; MEMORY USAGE Test(integer) 48</code></pre></li><li><p>在redis实例上执行<code>bgsave</code>，然后对dump出来的rdb文件进行分析，找到其中的大KEY。</p></li></ol><h3 id="bigkey%E5%A6%82%E4%BD%95%E5%88%A0%E9%99%A4" tabindex="-1">BigKey如何删除</h3><p><a href="https://redis.io/commands/del/" target="_blank">DEL命令</a>在删除单个集合类型的Key时，命令的时间复杂度是O(M)，其中M是集合类型Key包含的元素个数。</p><blockquote><p>DEL keyTime complexity: O(N) where N is the number of keys that will be removed. When a key to remove holds a value other than a string, the individual complexity for this key is O(M) where M is the number of elements in the list, set, sorted set or hash. Removing a single key that holds a string value is O(1).</p></blockquote><p>生产环境中遇到过多次因业务删除大Key，导致Redis阻塞，出现故障切换和应用程序雪崩的故障。</p><ol><li><p>String：一般使用<code>DEL</code>，如果过于庞大则使用<code>UNLINK</code>。</p></li><li><p>Hash：通过<code>hscan</code>命令，每次获取少量字段，再用<code>hdel</code>命令，每次删除1个字段，<a href="https://developer.aliyun.com/article/531067#cc2" target="_blank">《阿里云Redis开发手册》</a>中的Java代码：</p><pre><code class="language-java">public void delBigHash(String host, int port, String password, String bigHashKey) {    Jedis jedis = new Jedis(host, port);    if (password != null &amp;&amp; !&quot;&quot;.equals(password)) {        jedis.auth(password);    }    ScanParams scanParams = new ScanParams().count(100);    String cursor = &quot;0&quot;;    do {        ScanResult&lt;Entry&lt;String, String&gt;&gt; scanResult = jedis.hscan(bigHashKey, cursor, scanParams);        List&lt;Entry&lt;String, String&gt;&gt; entryList = scanResult.getResult();        if (entryList != null &amp;&amp; !entryList.isEmpty()) {            for (Entry&lt;String, String&gt; entry : entryList) {                jedis.hdel(bigHashKey, entry.getKey());            }        }        cursor = scanResult.getStringCursor();    } while (!&quot;0&quot;.equals(cursor));    //删除bigkey    jedis.del(bigHashKey);}</code></pre></li><li><p>List：使用<code>ltrim</code>渐进式删除，直到全部删除完成，<a href="https://developer.aliyun.com/article/531067#cc2" target="_blank">《阿里云Redis开发手册》</a>中的Java代码：</p><pre><code class="language-java">public void delBigList(String host, int port, String password, String bigListKey) {    Jedis jedis = new Jedis(host, port);    if (password != null &amp;&amp; !&quot;&quot;.equals(password)) {        jedis.auth(password);    }    long llen = jedis.llen(bigListKey);    int counter = 0;    int left = 100;    while (counter &lt; llen) {        //每次从左侧截掉100个        jedis.ltrim(bigListKey, left, llen);        counter += left;    }    //最终删除key    jedis.del(bigListKey);}</code></pre></li><li><p>Set：使用<code>sscan</code>每次获取部分元素，再使用<code>srem</code>命令删除每个元素，<a href="https://developer.aliyun.com/article/531067#cc2" target="_blank">《阿里云Redis开发手册》</a>中的Java代码：</p><pre><code class="language-java">public void delBigSet(String host, int port, String password, String bigSetKey) {    Jedis jedis = new Jedis(host, port);    if (password != null &amp;&amp; !&quot;&quot;.equals(password)) {        jedis.auth(password);    }    ScanParams scanParams = new ScanParams().count(100);    String cursor = &quot;0&quot;;    do {        ScanResult&lt;String&gt; scanResult = jedis.sscan(bigSetKey, cursor, scanParams);        List&lt;String&gt; memberList = scanResult.getResult();        if (memberList != null &amp;&amp; !memberList.isEmpty()) {            for (String member : memberList) {                jedis.srem(bigSetKey, member);            }        }        cursor = scanResult.getStringCursor();    } while (!&quot;0&quot;.equals(cursor));    //删除bigkey    jedis.del(bigSetKey);}</code></pre></li><li><p>ZSet：使用<code>zscan</code>每次获取部分元素，再使用<code>ZREMRANGEBYRANK</code>命令删除每个元素，<a href="https://developer.aliyun.com/article/531067#cc2" target="_blank">《阿里云Redis开发手册》</a>中的Java代码：</p><pre><code class="language-java">public void delBigZset(String host, int port, String password, String bigZsetKey) {    Jedis jedis = new Jedis(host, port);    if (password != null &amp;&amp; !&quot;&quot;.equals(password)) {        jedis.auth(password);    }    ScanParams scanParams = new ScanParams().count(100);    String cursor = &quot;0&quot;;    do {        ScanResult&lt;Tuple&gt; scanResult = jedis.zscan(bigZsetKey, cursor, scanParams);        List&lt;Tuple&gt; tupleList = scanResult.getResult();        if (tupleList != null &amp;&amp; !tupleList.isEmpty()) {            for (Tuple tuple : tupleList) {                jedis.zrem(bigZsetKey, tuple.getElement());            }        }        cursor = scanResult.getStringCursor();    } while (!&quot;0&quot;.equals(cursor));        //删除bigkey    jedis.del(bigZsetKey);}</code></pre></li></ol><h3 id="bigkey%E7%94%9F%E4%BA%A7%E8%B0%83%E4%BC%98" tabindex="-1">BigKey生产调优</h3><blockquote><p>为了解决redis使用<code>del</code>命令删除大体积的key，或者使用<code>flushdb</code>、<code>flushall</code>删除数据库时，造成redis阻塞的情况，在redis 4.0引入了lazyfree机制，可将删除操作放在后台，让后台子线程(bio)执行，避免主线程阻塞。</p></blockquote><p>redis.conf文件里Lazy Freeing的相关说明:</p><ul><li><p>Redis有两个用于删除键的原语。一个称为<code>DEL</code>，是对象的阻塞删除。这意味着服务器停止处理新命令以同步方式回收与对象相关联的所有内存。如果删除的键与小对象相关联，则执行<code>DEL</code>命令所需的时间非常快，可与大多数其他O(1)或O(log_N)命令相媲美。但是，如果键与包含数百万元素的聚合值相关联，则服务器可能会阻塞很长时间（甚至几秒钟）以完成操作。</p></li><li><p>Redis提供了非阻塞删除原语，例如<code>UNLINK</code>（非阻塞DEL）和<code>FLUSHALL</code>和<code>FLUSHDB</code>命令的<code>ASYNC</code>选项，以便在后台回收内存。这些命令在恒定时间内执行。另一个线程将尽可能快地逐步释放对象。</p></li><li><p><code>DEL</code>、<code>UNLINK</code>和<code>FLUSHALL</code>、<code>FLUSHDB</code>的<code>ASYNC</code>选项由用户控制。由应用程序的设计决定何时使用哪个。但是，Redis服务器有时必须删除键或刷新整个数据库作为其他操作的副作用。具体来说，Redis在以下场景中独立于用户调用删除对象：</p><ol><li>当因为<code>maxmemory</code>和<code>maxmemory</code>策略配置而需要为新数据腾出空间以避免超过指定的内存限制时，会发生清除操作。</li><li>因为过期:当具有关联的生存时间的键必须从内存中删除时。</li><li>因为命令的副作用，该命令将数据存储在可能已经存在的键上。例如，当<code>RENAME</code>命令被另一个命令替换时，它可能会删除旧键内容。同样，<code>SUNIONSTORE</code>或具有<code>STORE</code>选项的SORT可能会删除现有键。<code>SET</code>命令本身会删除指定键的任何旧内容，以便用指定字符串替换它。</li><li>当副本与其主服务器执行完全重新同步时，会发生清除操作，以便加载刚刚传输的整个数据库内容。</li></ol></li><li><p>在所有上述情况下，默认情况是以阻塞方式删除对象，就像调用<code>DEL</code>一样。但是，您可以针对每种情况进行特定配置，以便以非阻塞方式释放内存，就像调用<code>UNLINK</code>一样，使用以下配置指令:</p><pre><code class="language-conf"># 针对redis内存使用达到maxmeory，并设置有淘汰策略时；在被动淘汰键时，是否采用lazy free机制；lazyfree-lazy-eviction yes# 针对设置有TTL的键，达到过期后，被redis清理删除时是否采用lazy free机制；此场景建议开启，因TTL本身是自适应调整的速度。lazyfree-lazy-expire yes# 针对有些指令在处理已存在的键时，会带有一个隐式的DEL键的操作。如rename命令，当目标键已存在,redis会先删除目标键，如果这些目标键是一个big key,那就会引入阻塞删除的性能问题。 此参数设置就是解决类问题，建议可开启。lazyfree-lazy-server-del yes# 针对slave进行全量数据同步，slave在加载master的RDB文件前，会运行flushall来清理自己的数据场景,# 参数设置决定是否采用异常flush机制。如果内存变动不大，建议可开启。可减少全量同步耗时，从而减少主库因输出缓冲区爆涨引起的内存使用增长。replica-lazy-flush yes</code></pre></li><li><p>还可以通过以下配置指令将<code>DEL</code>命令的默认行为修改为与<code>UNLINK</code>完全相同，以便在无法轻松替换用户代码<code>DEL</code>调用为<code>UNLINK</code>调用的情况下使用：</p><pre><code class="language-conf">lazyfree-lazy-user-del yes</code></pre></li><li><p><code>FLUSHDB</code>、<code>FLUSHALL</code>、<code>SCRIPT FLUSH</code>和<code>FUNCTION FLUSH</code>支持异步和同步删除，可以通过将[<code>SYNC</code>|<code>ASYNC</code>]标志传递到命令中来控制。当没有传递标志时，将使用此配置来确定数据是否应异步删除:</p><pre><code class="language-conf">lazyfree-lazy-user-flush yes</code></pre></li></ul>]]>
                    </description>
                    <pubDate>Wed, 18 May 2022 23:50:05 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Redis是单线程还是多线程]]>
                    </title>
                    <link>https://zygzyg.com/archives/fe6b765c42694d02baf0d683033412ca</link>
                    <description>
                            <![CDATA[<h1 id="redis%E6%98%AF%E5%8D%95%E7%BA%BF%E7%A8%8B%E8%BF%98%E6%98%AF%E5%A4%9A%E7%BA%BF%E7%A8%8B" tabindex="-1">Redis是单线程还是多线程</h1><blockquote><p>要说Redis是单线程还是多线程实际要看实际版本</p></blockquote><p>Redis的版本有很多，其架构也是不一样的。</p><ul><li>在<strong>Redis3</strong>的时候是单线程。</li><li>从<strong>Redis4</strong>开始，严格意义上来说已经不是单线程了，而是负责处理客户端请求的线程是单线程，但是开始加了点多线程的东西。</li><li><strong>Redis6</strong>和<strong>Redis7</strong>彻底的用上了多线程。</li></ul><p>Redis中有几个里程碑式的重要版本:</p><table><thead><tr><th>时间</th><th>版本</th><th>版本特性</th></tr></thead><tbody><tr><td>2012年10月</td><td>Redis2.6</td><td>开始支持Lua脚本</td></tr><tr><td>2015年4月</td><td>Redis3.0</td><td>官方的集群方案</td></tr><tr><td>2017年7月</td><td>Redis4.0</td><td>混合持久化、多线程异步删除</td></tr><tr><td>2018年10月</td><td>Redis5.0</td><td>核心代码重构</td></tr><tr><td>2020年5月</td><td>Redis6.0</td><td><mark>多线程IO</mark></td></tr></tbody></table><h2 id="%E6%80%8E%E4%B9%88%E7%90%86%E8%A7%A3redis%E6%98%AF%E5%8D%95%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%91%A2%EF%BC%9F" tabindex="-1">怎么理解Redis是单线程的呢？</h2><p>Redis的单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的，Redis在处理客户端的请求时包括获取（Socket读）、解析、执行、内容返回（Socket写）等都是由一个顺序串行的主线程处理，这就是所谓的”<strong>单线程</strong>“。这也是Redis对外提供键值存储服务的主要流程。</p><ul><li>Redis采用Reactor模式的网络模型，对于一个客户端请求，主线程负责一个完整的处理过程</li></ul><div class="mermaid"><svg id="render2613618389" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="160" style="max-width: 669px;" viewBox="0 0 669 160"><style>#render2613618389 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render2613618389 .error-icon{fill:#a5a7bb;}#render2613618389 .error-text{fill:#5a5844;stroke:#5a5844;}#render2613618389 .edge-thickness-normal{stroke-width:2px;}#render2613618389 .edge-thickness-thick{stroke-width:3.5px;}#render2613618389 .edge-pattern-solid{stroke-dasharray:0;}#render2613618389 .edge-pattern-dashed{stroke-dasharray:3;}#render2613618389 .edge-pattern-dotted{stroke-dasharray:2;}#render2613618389 .marker{fill:#F8B229;stroke:#F8B229;}#render2613618389 .marker.cross{stroke:#F8B229;}#render2613618389 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render2613618389 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render2613618389 .cluster-label text{fill:#5a5844;}#render2613618389 .cluster-label span{color:#5a5844;}#render2613618389 .label text,#render2613618389 span{fill:#fff;color:#fff;}#render2613618389 .node rect,#render2613618389 .node circle,#render2613618389 .node ellipse,#render2613618389 .node polygon,#render2613618389 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render2613618389 .node .label{text-align:center;}#render2613618389 .node.clickable{cursor:pointer;}#render2613618389 .arrowheadPath{fill:undefined;}#render2613618389 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render2613618389 .flowchart-link{stroke:#F8B229;fill:none;}#render2613618389 .edgeLabel{background-color:#006100;text-align:center;}#render2613618389 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render2613618389 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render2613618389 .cluster text{fill:#5a5844;}#render2613618389 .cluster span{color:#5a5844;}#render2613618389 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render2613618389 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 0)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"></g><g class="edgeLabels"></g><g class="nodes"><g class="root" transform="translate(0.5, 0)"><g class="clusters"><g class="cluster default" id="Main"><rect style="" rx="0" ry="0" x="8" y="8" width="653" height="144"></rect><g class="cluster-label" transform="translate(310, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">主线程</tspan></text></g></g><g class="cluster default" id="LongTime1"><rect style="" rx="0" ry="0" x="33" y="28" width="277" height="104"></rect><g class="cluster-label" transform="translate(130.5, 33)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">耗时的操作</tspan></text></g></g><g class="cluster default" id="LongTime2"><rect style="" rx="0" ry="0" x="491" y="28" width="145" height="104"></rect><g class="cluster-label" transform="translate(522.5, 33)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">耗时的操作</tspan></text></g></g></g><g class="edgePaths"><path d="M154,80L158.16666666666666,80C162.33333333333334,80,170.66666666666666,80,179,80C187.33333333333334,80,195.66666666666666,80,199.83333333333334,80L204,80" id="L-Read-Analyze-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Read LE-Analyze" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M285,80L289.1666666666667,80C293.3333333333333,80,301.6666666666667,80,310,80C318.3333333333333,80,326.6666666666667,80,335,80C343.3333333333333,80,351.6666666666667,80,355.8333333333333,80L360,80" id="L-Analyze-Exec-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Analyze LE-Exec" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M441,80L445.1666666666667,80C449.3333333333333,80,457.6666666666667,80,466,80C474.3333333333333,80,482.6666666666667,80,491,80C499.3333333333333,80,507.6666666666667,80,511.8333333333333,80L516,80" id="L-Exec-Write-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Exec LE-Write" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-Read-3945" transform="translate(106, 80)"><rect class="basic label-container" style="fill:#f96;stroke:#333;stroke-width:4px;" rx="0" ry="0" x="-48" y="-17" width="96" height="34"></rect><g class="label" style="" transform="translate(-40.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">读取Socket</tspan></text></g></g><g class="node default default" id="flowchart-Analyze-3946" transform="translate(244.5, 80)"><rect class="basic label-container" style="fill:#e8a909;" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">解析请求</tspan></text></g></g><g class="node default default" id="flowchart-Exec-3947" transform="translate(400.5, 80)"><rect class="basic label-container" style="fill:#0c4ee8;" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">执行操作</tspan></text></g></g><g class="node default default" id="flowchart-Write-3948" transform="translate(563.5, 80)"><rect class="basic label-container" style="fill:#e80004;" rx="0" ry="0" x="-47.5" y="-17" width="95" height="34"></rect><g class="label" style="" transform="translate(-40, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">写入Socket</tspan></text></g></g></g></g></g></g></g></svg></div><p>但是Redis的其他功能，比如持久化RDB、AOF、异步删除、集群数据同步等等其实都是由额外的线程来完成的。Redis命令工作线程是单线程的，但是对于整个Redis来说，实际是多线程的。</p><h2 id="%E4%B8%BA%E4%BB%80%E4%B9%88redis%E4%BD%BF%E7%94%A8%E5%8D%95%E7%BA%BF%E7%A8%8B%E4%BE%9D%E7%84%B6%E5%BE%88%E5%BF%AB%EF%BC%9F" tabindex="-1">为什么Redis使用单线程依然很快？</h2><ul><li><strong>基于内存操作</strong>：Redis的所有数据都在内存中，因此所有的运算都是内存级别的。</li><li><strong>数据结构简单</strong>：Redis的数据结构是专门设计的，这些简单的数据结构的查找和操作的时间复杂度大部分都是O(1)。</li><li><strong>多路IO复用和非阻塞IO</strong>：Redis使用IO多路复用功能来监听多个Socket连接客户端，这样就可以使用一个线程连接来处理多个请求，减少线程切换带来的开销。同时也避免了IO阻塞操作。</li><li><strong>避免上下文切换</strong>：因为是单线程模型，所以避免了不必要的上下文切换和多线程竞争。省去了多线程切换带来的时间和性能上的消耗，而且单线程不会导致死锁问题发生。</li></ul><h2 id="redis%E5%A6%82%E4%BD%95%E5%88%A9%E7%94%A8%E5%A4%9A%E6%A0%B8cpu%E5%91%A2%EF%BC%9F" tabindex="-1">Redis如何利用多核CPU呢？</h2><blockquote><p><a href="https://redis.io/docs/getting-started/faq/#how-can-redis-use-multiple-cpus-or-cores" target="_blank">官网是这么回答的</a>：</p><p>Redis很少出现CPU成为瓶颈的情况，通常Redis要么是内存首先，要么是网络受限。</p><p>例如：使用在平均Linux系统上运行的流水线Redis每秒可以发送100万个请求，因此如果您的应用程序主要使用O(N)或O(log(N))命令，则几乎不会使用太多CPU。</p><p>但是，要最大化CPU使用率，您可以在同一台计算机上启动多个Redis实例，并将它们视为不同的服务器。在某些时候，单个计算机可能已经不够了，因此如果要使用多个CPU，则可以开始考虑更早地进行分片的某种方式。</p><p>从4.0版本开始，Redis开始具有更多线程。目前，这仅限于在后台删除对象和阻止通过Redis模块实现的命令。对于随后的版本，计划是使Redis越来越多线程化。</p></blockquote><p>官网的大致意思就是说Redis是基于内存操作的，<mark>因此它的瓶颈应该是服务器的内存或者网络带宽而并非是CPU</mark>，既然CPU不是瓶颈，那么自然就采用单线程的方案了，况且使用多线程比较麻烦。<mark>但是在Redis4.0中开始支持多线程了，比如后台删除、备份等功能</mark>。</p><h3 id="redis%E5%9C%A84.0%E4%B9%8B%E5%89%8D%E4%B8%80%E7%9B%B4%E9%87%87%E7%94%A8%E5%8D%95%E7%BA%BF%E7%A8%8B%E7%9A%84%E5%8E%9F%E5%9B%A0%E4%B8%BB%E8%A6%81%E6%9C%893%E4%B8%AA" tabindex="-1">Redis在4.0之前一直采用单线程的原因主要有3个</h3><ol><li>使用单线程模型让Redis的开发和维护更简单，因为单线程模型方便开发和调试。</li><li>即使使用单线程模型也能并发处理多客户端的请求，主要就是使用了IO多路复用和非阻塞IO。</li><li>对于Redis来说，主要性能瓶颈是内存或者网络带宽而并非CPU。</li></ol><h2 id="%E4%B8%BA%E4%BB%80%E4%B9%88redis%E5%90%8E%E9%9D%A2%E5%BC%80%E5%A7%8B%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%91%A2%EF%BC%9F" tabindex="-1">为什么Redis后面开始使用多线程呢？</h2><blockquote><p>正常情况下使用<code>del</code>指令可以很快的删除数据，但是当被删除的key是一个非常大的对象时（比如包含了上万个元素的hash集合），那么<code>del</code>指令就会造成Redis主线程阻塞。</p></blockquote><p>那么如何解决Redis删除大key时的阻塞问题呢？使用惰性删除可以有效地避免这个问题：</p><p>比如当删除一个很大的key的时候，因为单线程导致阻塞卡顿，所以Redis4.0中新增了多线程模块(此版本的多线程主要是为了解决删除数据效率比较低的问题)，主要有这么几个命令：</p><ol><li>UNLINK KEY</li><li>FLUSHDB ASYNC</li><li>FLUSHALL ASYNC</li></ol><p>这里其实就是把删除工作交给了子线程异步来完成了。</p><p>因为Redis是使用单个线程处理请求，Redis作者一直强调“Lazy Redis is better Redis”，而Lazy Free的本质就是把某些Cost（主要时间复杂度，占用主线程CPU时间片）较高操作，从Redis主线程剥离出来让bio子线程来处理，极大地减少主线程阻塞地时间。从而减少删除大key导致地性能和稳定性问题。</p><h2 id="redis6.0%E4%B9%8B%E5%90%8E%E7%9C%9F%E6%AD%A3%E5%9C%B0%E5%8A%A0%E5%85%A5%E4%BA%86%E5%A4%9A%E7%BA%BF%E7%A8%8B" tabindex="-1">Redis6.0之后真正地加入了多线程</h2><blockquote><p>Redis6.0之后，最受关注的特性第一个就是多线程。</p></blockquote><p>因为Redis一直被熟知地就是单线程，虽然有些命令操作可以用后台线程或子进程执行（比如数据删除、快照生成、AOF重写）。但是真正从网络IO到实际读写命令地处理都是由单个线程完成的。</p><p>随着网络硬件的性能提升，Redis的性能瓶颈有时也会出现在网络IO上，也就是说单个主线程的处理请求的速度跟不上网络硬件的速度。</p><p>为了应对这个问题：<mark>Redis6.0之后采用了多个IO线程来处理网络请求，提高网络请求处理的并行度</mark>。</p><p>但是Redis的多IO线程只是用来处理网络请求的，<mark>对于读写操作命令Redis仍然采用单线程处理</mark>，主要是因为：</p><ul><li>Redis处理请求时，网络处理经常成为瓶颈，而通过多个IO线程并行处理网络请求，可以提升服务的整体处理性能。</li><li>继续使用单线程处理读写命令操作，就不用为了保证Lua脚本、事务的原子性，额外开发多线程互斥加锁机制，这样一来就简化了Redis的实现。</li></ul><h3 id="%E4%B8%BB%E7%BA%BF%E7%A8%8B%E5%92%8Cio%E7%BA%BF%E7%A8%8B%E6%98%AF%E6%80%8E%E4%B9%88%E5%8D%8F%E4%BD%9C%E5%AE%8C%E6%88%90%E8%AF%B7%E6%B1%82%E5%A4%84%E7%90%86%E7%9A%84%E5%91%A2%EF%BC%9F" tabindex="-1">主线程和IO线程是怎么协作完成请求处理的呢？</h3><p>主要有四个阶段：</p><ol><li><p><strong>阶段一</strong>：服务端和客户端建立Socket连接，并分配处理线程</p><p>首先主线程负责接收建立连接请求，当有客户端请求和服务建立Socket连接时，主线程会创建和客户端的连接，并把Socket放入全局等待队列中。紧接着，主线程通过轮询的方法把Socket连接分配给IO线程。</p></li><li><p><strong>阶段二</strong>：IO线程读取并解析请求</p><p>主线程一旦把Socket分配给IO线程，就会进入阻塞状态，等待IO线程完成客户端请求读取和解析。因为有多个IO线程在并行处理，所以这个过程很快就能完成。</p></li><li><p><strong>阶段三</strong>：主线程执行请求的操作</p><p>等到IO线程解析完毕，主线程还是会以单线程的方式执行这些读写命令。</p></li><li><p><strong>阶段四</strong>：IO线程回写Socket和主线程清空全局队列</p><p>当主线程执行完毕读写命令后，会把需要返回的结果写入缓冲区，然后主线程会阻塞并等待IO线程把这些结果回写到Socket中，并返回给客户端。和IO线程读取、解析请求一样，IO线程回写到SOcket时也是有多个线程在并发执行，所以回写Socket的速度也很快。等待IO线程回写完毕，主线程会清空全局队列，等待客户端的后续请求。</p></li></ol><p>具体流程如下图所示：</p><div class="mermaid"><svg id="render526869830" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="716" style="max-width: 1212.97265625px;" viewBox="0 0 1212.97265625 716"><style>#render526869830 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render526869830 .error-icon{fill:#a5a7bb;}#render526869830 .error-text{fill:#5a5844;stroke:#5a5844;}#render526869830 .edge-thickness-normal{stroke-width:2px;}#render526869830 .edge-thickness-thick{stroke-width:3.5px;}#render526869830 .edge-pattern-solid{stroke-dasharray:0;}#render526869830 .edge-pattern-dashed{stroke-dasharray:3;}#render526869830 .edge-pattern-dotted{stroke-dasharray:2;}#render526869830 .marker{fill:#F8B229;stroke:#F8B229;}#render526869830 .marker.cross{stroke:#F8B229;}#render526869830 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render526869830 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render526869830 .cluster-label text{fill:#5a5844;}#render526869830 .cluster-label span{color:#5a5844;}#render526869830 .label text,#render526869830 span{fill:#fff;color:#fff;}#render526869830 .node rect,#render526869830 .node circle,#render526869830 .node ellipse,#render526869830 .node polygon,#render526869830 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render526869830 .node .label{text-align:center;}#render526869830 .node.clickable{cursor:pointer;}#render526869830 .arrowheadPath{fill:undefined;}#render526869830 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render526869830 .flowchart-link{stroke:#F8B229;fill:none;}#render526869830 .edgeLabel{background-color:#006100;text-align:center;}#render526869830 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render526869830 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render526869830 .cluster text{fill:#5a5844;}#render526869830 .cluster span{color:#5a5844;}#render526869830 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render526869830 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#render526869830 .IOThreadClass path{fill:#f96!important;stroke:#333!important;stroke-width:4px!important;}#render526869830 .IOThreadClass rect{fill:#f96!important;stroke:#333!important;stroke-width:4px!important;}#render526869830 .IOThreadClass polygon{fill:#f96!important;stroke:#333!important;stroke-width:4px!important;}#render526869830 .IOThreadClass ellipse{fill:#f96!important;stroke:#333!important;stroke-width:4px!important;}#render526869830 .IOThreadClass circle{fill:#f96!important;stroke:#333!important;stroke-width:4px!important;}</style><g transform="translate(0, 8)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"></g><g class="edgePaths"><path d="M579.046875,358L583.2135416666666,358C587.3802083333334,358,595.7135416666666,358,604.046875,358C612.3802083333334,358,620.7135416666666,358,624.8802083333334,358L629.046875,358" id="L-step1-step3-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-step1 LE-step3" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="root" transform="translate(621.546875, 42)"><g class="clusters"><g class="cluster default" id="step3"><rect style="" rx="0" ry="0" x="8" y="8" width="567.92578125" height="608"></rect><g class="cluster-label" transform="translate(235.462890625, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">阶段三：主线程</tspan></text></g></g></g><g class="edgePaths"><path d="M282.8671875,67L282.8671875,71.16666666666667C282.8671875,75.33333333333333,282.8671875,83.66666666666667,282.8671875,92C282.8671875,100.33333333333333,282.8671875,108.66666666666667,282.8671875,112.83333333333333L282.8671875,117" id="L-I-J-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-I LE-J" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M282.8671875,151L282.8671875,155.16666666666666C282.8671875,159.33333333333334,282.8671875,167.66666666666666,282.8671875,176C282.8671875,184.33333333333334,282.8671875,192.66666666666666,282.8671875,196.83333333333334L282.8671875,201" id="L-J-K-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-J LE-K" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M238.2847390776699,235L223.20538152305826,240.75C208.1260239684466,246.5,177.9673088592233,258,162.88795130461165,284.8333333333333C147.80859375,311.6666666666667,147.80859375,353.8333333333333,147.80859375,396C147.80859375,438.1666666666667,147.80859375,480.3333333333333,162.88795130461165,507.1666666666667C177.9673088592233,534,208.1260239684466,545.5,223.20538152305824,551.25L238.2847390776699,557" id="L-K-N-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-K LE-N" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M327.4496359223301,235L342.5289934769418,240.75C357.6083510315534,246.5,387.7670661407767,258,402.8464236953884,269.5C417.92578125,281,417.92578125,292.5,417.92578125,298.25L417.92578125,304" id="L-K-step4-0" class=" edge-thickness-normal edge-pattern-dotted flowchart-link LS-K LE-step4" style="fill:none;stroke-width:2px;stroke-dasharray:3;" marker-end="url(#flowchart-pointEnd)"></path><path d="M417.92578125,488L417.92578125,493.75C417.92578125,499.5,417.92578125,511,402.8464236953884,522.5C387.7670661407767,534,357.6083510315534,545.5,342.5289934769418,551.25L327.4496359223301,557" id="L-step4-N-0" class=" edge-thickness-normal edge-pattern-dotted flowchart-link LS-step4 LE-N" style="fill:none;stroke-width:2px;stroke-dasharray:3;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(147.80859375, 396)"><g class="label" transform="translate(-112.1171875, -9.5)"><rect rx="0" ry="0" width="224.09979248046875" height="19.00006103515625"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">等待IO线程完成数据回写Socket</tspan></text></g></g><g class="edgeLabel" transform="translate(417.92578125, 269.5)"><g class="label" transform="translate(-56.1171875, -9.5)"><rect rx="0" ry="0" width="112.1751480102539" height="19.00006103515625"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">IO线程开始执行</tspan></text></g></g><g class="edgeLabel" transform="translate(417.92578125, 522.5)"><g class="label" transform="translate(-56.5, -9.5)"><rect rx="0" ry="0" width="112.9403076171875" height="19.00006103515625"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">主线程开始执行</tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-N-3995" transform="translate(282.8671875, 574)"><rect class="basic label-container" style="" rx="0" ry="0" x="-112.5" y="-17" width="225" height="34"></rect><g class="label" style="" transform="translate(-105, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">清空等待队列，等待后续请求</tspan></text></g></g><g class="node default default" id="flowchart-I-3992" transform="translate(282.8671875, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-96" y="-17" width="192" height="34"></rect><g class="label" style="" transform="translate(-88.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">请求的命令操作执行完成</tspan></text></g></g><g class="node default default" id="flowchart-J-3993" transform="translate(282.8671875, 134)"><rect class="basic label-container" style="" rx="0" ry="0" x="-88" y="-17" width="176" height="34"></rect><g class="label" style="" transform="translate(-80.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">将结果数据写入缓冲区</tspan></text></g></g><g class="node default default" id="flowchart-K-3994" transform="translate(282.8671875, 218)"><rect class="basic label-container" style="" rx="0" ry="0" x="-48" y="-17" width="96" height="34"></rect><g class="label" style="" transform="translate(-40.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">主线程阻塞</tspan></text></g></g><g class="root" transform="translate(287.42578125, 296)"><g class="clusters"><g class="cluster default" id="step4"><rect style="" rx="0" ry="0" x="8" y="8" width="246" height="184"></rect><g class="cluster-label" transform="translate(74.8828125, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">阶段四：IO线程</tspan></text></g></g></g><g class="edgePaths"><path d="M131,83L131,87.16666666666667C131,91.33333333333333,131,99.66666666666667,131,108C131,116.33333333333333,131,124.66666666666667,131,128.83333333333334L131,133" id="L-L-M-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-L LE-M" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default IOThreadClass" id="flowchart-M-3997" transform="translate(131, 150)"><rect class="basic label-container" style="" rx="0" ry="0" x="-63.671875" y="-17" width="127.34375" height="34"></rect><g class="label" style="" transform="translate(-56.171875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">Socket回写完成</tspan></text></g></g><g class="node default IOThreadClass" id="flowchart-L-3996" transform="translate(131, 58)"><rect class="basic label-container" style="" rx="0" ry="0" x="-88" y="-25" width="176" height="50"></rect><g class="label" style="" transform="translate(-80.5, -17.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">将结果数据写回Socket</tspan><tspan xml:space="preserve" dy="1em" x="0" class="row">（并行化执行）</tspan></text></g></g></g></g></g></g><g class="root" transform="translate(0.5, 0)"><g class="clusters"><g class="cluster default" id="step1"><rect style="" rx="0" ry="0" x="8" y="8" width="563.046875" height="692"></rect><g class="cluster-label" transform="translate(233.0234375, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">阶段一：主线程</tspan></text></g></g></g><g class="edgePaths"><path d="M278.2890625,67L278.2890625,71.16666666666667C278.2890625,75.33333333333333,278.2890625,83.66666666666667,278.2890625,92C278.2890625,100.33333333333333,278.2890625,108.66666666666667,278.2890625,112.83333333333333L278.2890625,117" id="L-A-B-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-A LE-B" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M278.2890625,151L278.2890625,155.16666666666666C278.2890625,159.33333333333334,278.2890625,167.66666666666666,278.2890625,176C278.2890625,184.33333333333334,278.2890625,192.66666666666666,278.2890625,196.83333333333334L278.2890625,201" id="L-B-C-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-B LE-C" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M239.14418992718447,235L225.90401243932038,240.75C212.66383495145632,246.5,186.18347997572815,258,172.9433024878641,289C159.703125,320,159.703125,370.5,159.703125,395.75L159.703125,421" id="L-C-G-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-C LE-G" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M159.703125,455L159.703125,480.25C159.703125,505.5,159.703125,556,172.9433024878641,587C186.18347997572815,618,212.66383495145632,629.5,225.90401243932038,635.25L239.14418992718447,641" id="L-G-H-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-G LE-H" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M317.43393507281553,235L330.6741125606796,240.75C343.9142900485437,246.5,370.3946450242718,258,383.63482251213594,269.5C396.875,281,396.875,292.5,396.875,298.25L396.875,304" id="L-C-step2-0" class=" edge-thickness-normal edge-pattern-dotted flowchart-link LS-C LE-step2" style="fill:none;stroke-width:2px;stroke-dasharray:3;" marker-end="url(#flowchart-pointEnd)"></path><path d="M396.875,572L396.875,577.75C396.875,583.5,396.875,595,383.63482251213594,606.5C370.3946450242718,618,343.9142900485437,629.5,330.6741125606796,635.25L317.43393507281553,641" id="L-step2-H-0" class=" edge-thickness-normal edge-pattern-dotted flowchart-link LS-step2 LE-H" style="fill:none;stroke-width:2px;stroke-dasharray:3;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel" transform="translate(159.703125, 606.5)"><g class="label" transform="translate(-112.6171875, -9.5)"><rect rx="0" ry="0" width="225.10543823242188" height="19.00006103515625"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">等待IO线程完成请求读取和解析</tspan></text></g></g><g class="edgeLabel" transform="translate(396.875, 269.5)"><g class="label" transform="translate(-56.1171875, -9.5)"><rect rx="0" ry="0" width="112.1751480102539" height="19.00006103515625"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">IO线程开始执行</tspan></text></g></g><g class="edgeLabel" transform="translate(396.875, 606.5)"><g class="label" transform="translate(-56.5, -9.5)"><rect rx="0" ry="0" width="112.9403076171875" height="19.00006103515625"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">主线程开始执行</tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-H-3988" transform="translate(278.2890625, 658)"><rect class="basic label-container" style="" rx="0" ry="0" x="-80.5" y="-17" width="161" height="34"></rect><g class="label" style="" transform="translate(-73, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">执行请求的命令操作</tspan></text></g></g><g class="node default default" id="flowchart-A-3984" transform="translate(278.2890625, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-119.5" y="-17" width="239" height="34"></rect><g class="label" style="" transform="translate(-112, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">接收建立连接请求，获取Socket</tspan></text></g></g><g class="node default default" id="flowchart-B-3985" transform="translate(278.2890625, 134)"><rect class="basic label-container" style="" rx="0" ry="0" x="-103.671875" y="-17" width="207.34375" height="34"></rect><g class="label" style="" transform="translate(-96.171875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">将Socket放入全局等待队列</tspan></text></g></g><g class="node default default" id="flowchart-C-3986" transform="translate(278.2890625, 218)"><rect class="basic label-container" style="" rx="0" ry="0" x="-135.7890625" y="-17" width="271.578125" height="34"></rect><g class="label" style="" transform="translate(-128.2890625, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">轮询方式将Socket连接分配给IO线程</tspan></text></g></g><g class="node default default" id="flowchart-G-3987" transform="translate(159.703125, 438)"><rect class="basic label-container" style="" rx="0" ry="0" x="-48" y="-17" width="96" height="34"></rect><g class="label" style="" transform="translate(-40.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">主线程阻塞</tspan></text></g></g><g class="root" transform="translate(250.203125, 296)"><g class="clusters"><g class="cluster default" id="step2"><rect style="" rx="0" ry="0" x="8" y="8" width="278.34375" height="268"></rect><g class="cluster-label" transform="translate(91.0546875, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">阶段二：IO线程</tspan></text></g></g></g><g class="edgePaths"><path d="M147.171875,67L147.171875,71.16666666666667C147.171875,75.33333333333333,147.171875,83.66666666666667,147.171875,92C147.171875,100.33333333333333,147.171875,108.66666666666667,147.171875,112.83333333333333L147.171875,117" id="L-D-E-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-D LE-E" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M147.171875,167L147.171875,171.16666666666666C147.171875,175.33333333333334,147.171875,183.66666666666666,147.171875,192C147.171875,200.33333333333334,147.171875,208.66666666666666,147.171875,212.83333333333334L147.171875,217" id="L-E-F-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-E LE-F" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default IOThreadClass" id="flowchart-F-3991" transform="translate(147.171875, 234)"><rect class="basic label-container" style="" rx="0" ry="0" x="-56" y="-17" width="112" height="34"></rect><g class="label" style="" transform="translate(-48.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">请求解析完成</tspan></text></g></g><g class="node default IOThreadClass" id="flowchart-D-3989" transform="translate(147.171875, 50)"><rect class="basic label-container" style="" rx="0" ry="0" x="-80.171875" y="-17" width="160.34375" height="34"></rect><g class="label" style="" transform="translate(-72.671875, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">将Socket和线程绑定</tspan></text></g></g><g class="node default IOThreadClass" id="flowchart-E-3990" transform="translate(147.171875, 142)"><rect class="basic label-container" style="" rx="0" ry="0" x="-104.171875" y="-25" width="208.34375" height="50"></rect><g class="label" style="" transform="translate(-96.671875, -17.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">读取Socket中的请求并解析</tspan><tspan xml:space="preserve" dy="1em" x="0" class="row">（并行化执行）</tspan></text></g></g></g></g></g></g></g></g></g></svg></div><h3 id="io%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8" tabindex="-1">IO多路复用</h3><h4 id="unix%E7%BD%91%E7%BB%9C%E7%BC%96%E7%A8%8B%E4%B8%AD%E7%9A%84%E4%BA%94%E7%A7%8Dio%E6%A8%A1%E5%9E%8B" tabindex="-1">Unix网络编程中的五种IO模型</h4><table><thead><tr><th style="text-align:center">Blocking IO</th><th style="text-align:center">NoneBlocking IO</th><th style="text-align:center">IO MuiltiPlexing</th><th style="text-align:center">Signal Driven IO</th><th style="text-align:center">Asynchronous IO</th></tr></thead><tbody><tr><td style="text-align:center">阻塞IO</td><td style="text-align:center">非阻塞IO</td><td style="text-align:center">IO多路复用</td><td style="text-align:center">信号驱动IO</td><td style="text-align:center">异步IO</td></tr></tbody></table><h4 id="file-descriptor" tabindex="-1">File Descriptor</h4><p>Linux系统中一切皆文件，文件描述符（File Descriptor简称FD），句柄。</p><p>文件描述符（File Descriptor）是计算机科学中的一个术语，是一个用于表述指向文件的引用的抽象化概念。文件描述符在形式上是一个<strong>非负整数</strong>。实际上它是一个索引值，指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时，内核向进程返回一个文件描述符。在程序设计中，文件描述符这一概念往往只适用于UNIX、LINUX这样的操作系统。</p><h4 id="io%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E7%AE%80%E5%8D%95%E7%90%86%E8%A7%A3" tabindex="-1">IO多路复用简单理解</h4><p>IO多路复用是一种<strong>同步</strong>的IO模型，实现<mark>一个线程</mark>监视<mark>多个文件句柄</mark>。</p><ul><li><strong>一旦某个文件句柄就绪</strong>就能够通知到对应的应用程序进行相应的读写操作。</li><li><strong>没有文件句柄就绪时</strong>就会阻塞应用程序，从而释放CPU资源。</li></ul><p>IO多路复用字面意思可以理解为：</p><table><thead><tr><th style="text-align:center">IO</th><th style="text-align:center">多路</th><th style="text-align:center">复用</th></tr></thead><tbody><tr><td style="text-align:center">网络IO，尤其在操作系统层面指数据在内核态和用户态之间的读写操作</td><td style="text-align:center">多个客户端连接（连接就是套接字描述符，即Socket或者Channel）</td><td style="text-align:center">复用一个或者多个线程</td></tr></tbody></table><p>所以IO多路复用就是一个或者一组线程处理多个TCP连接，使用单进程就能够实现同时处理多个客户端的连接，<mark>无需创建或者维护过多的进程/线程</mark>。</p><blockquote><p>一句话就是一个服务端进程可以同时处理多个套接字描述符。</p><p>实现IO多路复用的模型有3种：可以分为<code>select</code>-&gt;<code>poll</code>-&gt;<code>epoll</code>三个阶段来描述。</p></blockquote><h4 id="io%E5%A4%9A%E8%B7%AF%E5%A4%8D%E7%94%A8%E7%AE%80%E5%8D%95%E6%B5%81%E7%A8%8B" tabindex="-1">IO多路复用简单流程</h4><p>将用户Socket对应的文件描述符（File Descriptor）注册进epoll，然后<code>epoll</code>监听哪些Socket上有消息到达，这样就避免了大量的无用操作。此时的Socket应该采用<strong>非阻塞模式</strong>。这样整个过程只有在调用<code>select</code>、<code>poll</code>、<code>epoll</code>的时候才会阻塞，收发客户端消息是不会阻塞的，整个进程或者线程就被充分利用起来，这就是事件驱动，所谓的<strong>reactor</strong>反应模式。</p><p><mark>单个线程通过记录跟踪每一个Socket（IO流）的状态来同时管理多个IO流</mark>，一个服务端进程可以同时处理多个套接字描述符，目的是尽量多的提高服务器的吞吐能力。</p><h4 id="%E6%80%BB%E7%BB%93" tabindex="-1">总结</h4><p>客户端请求服务端时，实际就是在服务端的Socket文件中写入客户端对应的文件描述符（File Descriptor），如果有多个客户端同时请求服务端，为每次请求都分配一个线程的话，这样就会比较耗费服务端资源。所以只使用一个线程来监听多个文件描述符，就是IO多路复用。<mark>采用IO多路复用技术可以让单个线程高效的处理多个连接请求</mark>。</p><p>从Redis6.0开始，就新增了多线程的功能来提高IO的读写性能，它的主要思路就是将主线程的IO读写任务拆分给一组独立的线程去执行，这样就可以使多个Socket的读写并行化了，采用IO多路复用技术可以让单个线程高效的处理多个连接请求（尽量减少网络IO的时间消耗），将最耗时的Socket读取、请求解析、写入单独地外包出去，剩下的命令执行仍然由主线程串行执行。</p><ul><li>网络IO操作变成多线程化，其他核心部分仍然是线程安全的，是个不错的折中办法</li></ul><div class="mermaid"><svg id="render1770338836" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="120" style="max-width: 669px;" viewBox="0 0 669 120"><style>#render1770338836 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#fff;}#render1770338836 .error-icon{fill:#a5a7bb;}#render1770338836 .error-text{fill:#5a5844;stroke:#5a5844;}#render1770338836 .edge-thickness-normal{stroke-width:2px;}#render1770338836 .edge-thickness-thick{stroke-width:3.5px;}#render1770338836 .edge-pattern-solid{stroke-dasharray:0;}#render1770338836 .edge-pattern-dashed{stroke-dasharray:3;}#render1770338836 .edge-pattern-dotted{stroke-dasharray:2;}#render1770338836 .marker{fill:#F8B229;stroke:#F8B229;}#render1770338836 .marker.cross{stroke:#F8B229;}#render1770338836 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render1770338836 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#fff;}#render1770338836 .cluster-label text{fill:#5a5844;}#render1770338836 .cluster-label span{color:#5a5844;}#render1770338836 .label text,#render1770338836 span{fill:#fff;color:#fff;}#render1770338836 .node rect,#render1770338836 .node circle,#render1770338836 .node ellipse,#render1770338836 .node polygon,#render1770338836 .node path{fill:#025ebb;stroke:#7C0000;stroke-width:1px;}#render1770338836 .node .label{text-align:center;}#render1770338836 .node.clickable{cursor:pointer;}#render1770338836 .arrowheadPath{fill:undefined;}#render1770338836 .edgePath .path{stroke:#F8B229;stroke-width:2.0px;}#render1770338836 .flowchart-link{stroke:#F8B229;fill:none;}#render1770338836 .edgeLabel{background-color:#006100;text-align:center;}#render1770338836 .edgeLabel rect{opacity:0.5;background-color:#006100;fill:#006100;}#render1770338836 .cluster rect{fill:#a5a7bb;stroke:hsl(234.5454545455, 0%, 59.0196078431%);stroke-width:1px;}#render1770338836 .cluster text{fill:#5a5844;}#render1770338836 .cluster span{color:#5a5844;}#render1770338836 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:#a5a7bb;border:1px solid undefined;border-radius:2px;pointer-events:none;z-index:100;}#render1770338836 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}</style><g transform="translate(0, 0)"><marker id="flowchart-pointEnd" class="marker flowchart" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-pointStart" class="marker flowchart" viewBox="0 0 10 10" refX="0" refY="5" markerUnits="userSpaceOnUse" markerWidth="12" markerHeight="12" orient="auto"><path d="M 0 5 L 10 10 L 10 0 z" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-circleEnd" class="marker flowchart" viewBox="0 0 10 10" refX="11" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-circleStart" class="marker flowchart" viewBox="0 0 10 10" refX="-1" refY="5" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><circle cx="5" cy="5" r="5" class="arrowMarkerPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></circle></marker><marker id="flowchart-crossEnd" class="marker cross flowchart" viewBox="0 0 11 11" refX="12" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><marker id="flowchart-crossStart" class="marker cross flowchart" viewBox="0 0 11 11" refX="-1" refY="5.2" markerUnits="userSpaceOnUse" markerWidth="11" markerHeight="11" orient="auto"><path d="M 1,1 l 9,9 M 10,1 l -9,9" class="arrowMarkerPath" style="stroke-width: 2; stroke-dasharray: 1, 0;"></path></marker><g class="root"><g class="clusters"><g class="cluster default" id="Main"><rect style="" rx="0" ry="0" x="335" y="8" width="131" height="104"></rect><g class="cluster-label" transform="translate(376, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">主线程</tspan></text></g></g><g class="cluster default" id="IO2"><rect style="" rx="0" ry="0" x="516" y="8" width="145" height="104"></rect><g class="cluster-label" transform="translate(548.3828125, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">多个IO线程</tspan></text></g></g><g class="cluster default" id="IO1"><rect style="" rx="0" ry="0" x="8" y="8" width="277" height="104"></rect><g class="cluster-label" transform="translate(106.3828125, 13)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">多个IO线程</tspan></text></g></g></g><g class="edgePaths"><path d="M129,60L133.16666666666666,60C137.33333333333334,60,145.66666666666666,60,154,60C162.33333333333334,60,170.66666666666666,60,174.83333333333334,60L179,60" id="L-Read-Analyze-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Read LE-Analyze" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M260,60L264.1666666666667,60C268.3333333333333,60,276.6666666666667,60,285,60C293.3333333333333,60,301.6666666666667,60,310,60C318.3333333333333,60,326.6666666666667,60,335,60C343.3333333333333,60,351.6666666666667,60,355.8333333333333,60L360,60" id="L-Analyze-Exec-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Analyze LE-Exec" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path><path d="M441,60L445.1666666666667,60C449.3333333333333,60,457.6666666666667,60,466,60C474.3333333333333,60,482.6666666666667,60,491,60C499.3333333333333,60,507.6666666666667,60,516,60C524.3333333333334,60,532.6666666666666,60,536.8333333333334,60L541,60" id="L-Exec-Write-0" class=" edge-thickness-normal edge-pattern-solid flowchart-link LS-Exec LE-Write" style="fill:none;" marker-end="url(#flowchart-pointEnd)"></path></g><g class="edgeLabels"><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g><g class="edgeLabel"><g class="label" transform="translate(0, 0)"><rect rx="0" ry="0" width="0" height="0"></rect><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row"></tspan></text></g></g></g><g class="nodes"><g class="node default default" id="flowchart-Exec-4026" transform="translate(400.5, 60)"><rect class="basic label-container" style="fill:#0c4ee8;" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">执行操作</tspan></text></g></g><g class="node default default" id="flowchart-Write-4027" transform="translate(588.5, 60)"><rect class="basic label-container" style="fill:#e80004;" rx="0" ry="0" x="-47.5" y="-17" width="95" height="34"></rect><g class="label" style="" transform="translate(-40, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">写入Socket</tspan></text></g></g><g class="node default default" id="flowchart-Read-4024" transform="translate(81, 60)"><rect class="basic label-container" style="fill:#f96;stroke:#333;stroke-width:4px;" rx="0" ry="0" x="-48" y="-17" width="96" height="34"></rect><g class="label" style="" transform="translate(-40.5, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">读取Socket</tspan></text></g></g><g class="node default default" id="flowchart-Analyze-4025" transform="translate(219.5, 60)"><rect class="basic label-container" style="fill:#e8a909;" rx="0" ry="0" x="-40.5" y="-17" width="81" height="34"></rect><g class="label" style="" transform="translate(-33, -9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="0" class="row">解析请求</tspan></text></g></g></g></g></g></svg></div><h3 id="redis%E6%98%AF%E5%90%A6%E9%BB%98%E8%AE%A4%E5%BC%80%E5%90%AF%E4%BA%86%E5%A4%9A%E7%BA%BF%E7%A8%8B" tabindex="-1">Redis是否默认开启了多线程</h3><blockquote><p>如果在实际应用中，发现Redis服务的CPU开销不大但是吞吐量却没有提升，可以考虑使用Redis的多线程机制，加速网络处理，从而提升吞吐量。</p></blockquote><p>在Redis6.0之后多线程机制默认是关闭的，如果需要使用多线程功能，则需要在配置文件<code>redis.conf</code>中完成两个设置：</p><pre><code class="language-conf"># Redis大部分是单线程的，但有一些线程操作，例如UNLINK、缓慢的I/O访问和其他一些事情是在子线程上执行的。# 现在，可以将Redis客户端套接字的读写处理在不同的I/O线程中。由于写入是如此缓慢，通常Redis用户使用流水线技术来加速每个核心的Redis性能，并产生多个实例以进行更好的扩展。# 使用I/O线程可以轻松地将Redis的速度提高两倍，而不需要使用流水线技术或实例分片。# 默认情况下，IO线程是禁用的，我们建议仅在具有4个或更多核心的机器上启用IO线程，留下至少一个备用核心。使用超过8个线程不太可能有太大帮助。# 我们还建议仅在实际存在性能问题的情况下使用线程化I/O，Redis实例能够使用相当大的CPU时间百分比，否则使用此功能没有意义。# 因此，例如，如果您有一个四核心的CPU，尝试使用2或3个I/O线程，如果您有8个核心，则尝试使用6个线程。要启用I/O线程，请使用以下配置指令：io-threads 4# 将io-threads设置为1将像往常一样使用主线程。# 启用I/O线程时，我们仅在写入时使用线程，即将write(2)系统调用线程化并将客户端缓冲区传输到套接字。但是也可以通过将以下配置指令设置为yes来启用读取和协议解析的多线程化：io-threads-do-reads no</code></pre><blockquote><p>❗**注意：**通常情况下，线程读取没有太大帮助。</p><ol><li>此配置指令不能通过CONFIG SET在运行时更改。另外，当启用SSL时，此功能不起作用。</li><li>如果要使用<code>redis-benchmark</code>测试Redis，请确保运行基准测试也是多线程模式，使用<code>--threads</code>选项匹配上面配置的Redis线程的数量，否则将无法体现提升。</li></ol></blockquote>]]>
                    </description>
                    <pubDate>Sun, 15 May 2022 00:17:34 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Seata学习笔记]]>
                    </title>
                    <link>https://zygzyg.com/archives/4f4180200f3d4596824fd63014092fbf</link>
                    <description>
                            <![CDATA[<h1 id="seata-%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1" tabindex="-1">Seata 分布式事务</h1><p>单体应用被拆分成微服务应用，原来的多个模块被拆分成多个独立的应用，分别使用多个独立的数据源，业务操作需要调用多个服务来完成。此时每个服务内部的数据一致性由本地事务来保证，但是全局的数据一致性问题没有办法保证。</p><h2 id="%E7%AE%80%E4%BB%8B" tabindex="-1">简介</h2><p><a href="https://seata.io/zh-cn/" target="_blank">Seata</a>是一款开源的分布式事务解决方案，旨在解决分布式事务场景下的数据一致性问题。Seata提供了一系列的技术手段，如TC（事务协调者）、TM（事务管理器）、RM（资源管理器）等，来确保分布式事务的正确性和一致性。</p><p>Seata的工作原理是通过TC来协调各个RM，实现全局事务的管理。当一个分布式事务开始时，TM会向TC注册一个全局事务，然后TC会向各个RM注册分支事务。在分支事务执行完成后，RM会将事务执行结果通知TC，TC会根据所有分支事务的执行结果来决定是否提交或回滚全局事务。</p><p>Seata支持多种分布式事务模式，如AT模式、TCC模式、Saga模式等。同时，Seata还提供了一些高级功能，如分布式锁、分布式ID、分布式事务日志等，以满足不同场景下的需求。</p><ul><li><code>TC（Transaction Coordinator，事务协调者）</code>是全局事务的协调者，负责协调各个RM的工作。当一个分布式事务开始时，TM会向TC注册一个全局事务，然后TC会向各个RM注册分支事务。在分支事务执行完成后，RM会将事务执行结果通知TC，TC会根据所有分支事务的执行结果来决定是否提交或回滚全局事务。</li><li><code>TM（Transaction Manager，事务管理器）</code>负责全局事务的注册和提交/回滚。当一个分布式事务开始时，TM会向TC注册一个全局事务，然后根据事务的执行结果来决定是否提交或回滚全局事务。</li><li><code>RM（Resource Manager，资源管理器）</code>负责本地事务的执行和管理。当TC向RM注册分支事务后，RM会执行分支事务并将执行结果报告给TC，然后根据TC的指示来提交或回滚本地事务。</li></ul><h2 id="seata%E7%9A%84%E5%A4%84%E7%90%86%E6%B5%81%E7%A8%8B" tabindex="-1">Seata的处理流程</h2><p><img src="/upload/2023/03/Seata%E6%B5%81%E7%A8%8B.png" alt="Seata流程" /></p><ol><li>TM向TC注册全局事务，全局事务创建成功并生成一个全局唯一的XID。</li><li>XID在微服务调用链路的上下文中传播。</li><li>RM向TC注册分支事务，将其纳入XID对应全局事务的管辖。</li><li>TM向TC发起针对XID的全局提交或回滚决议。</li><li>TC调度XID下管辖的全部分支事务完成提交或者回滚请求。</li></ol><h2 id="%E5%AE%89%E8%A3%85seata" tabindex="-1">安装Seata</h2><h3 id="%E5%AE%89%E8%A3%85%E4%B9%8B%E5%89%8D" tabindex="-1">安装之前</h3><p>Seata分TC、TM和RM三个角色，TC（Server端）为单独服务端部署，TM和RM（Client端）由业务系统集成。本文使用docker安装。</p><blockquote><p>安装之前按照<a href="https://seata.io/zh-cn/docs/ops/deploy-by-docker-compose.html" target="_blank">官网</a>的说法Seata Server 1.5.0版本开始，配置文件改为application.yml，所以在使用自定义配置的时候，需要先把原生配置拷贝出来。</p><p>为了获取seata server 1.5.0的配置文件，我们需要先启动一个seata server 1.5.0的服务，然后再从启动的容器实例中把默认的配置文件复制出来，再进行修改。</p><ol><li><p>先启动一个容器</p><pre><code class="language-yaml">version: &quot;3.8&quot;services:  seata-server:    image: seataio/seata-server:${latest-release-version}    container_name: seata-server    ports:      - &quot;7091:7091&quot;      - &quot;8091:8091&quot;</code></pre></li><li><p>从容器中将配置文件拷贝到一个临时目录</p><pre><code class="language-shell">docker cp seata-server:/seata-server/resources /tmp  </code></pre></li><li><p>后续使用拷贝出来的<code>application.yml</code>配置文件即可。</p></li></ol></blockquote><h3 id="%E4%BF%AE%E6%94%B9%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6" tabindex="-1">修改配置文件</h3><p>这里使用Nacos为注册中心以及配置中心。修改<code>seata.config</code>以及<code>seata.registry</code> 。</p><pre><code class="language-yaml">server:  port: 7091spring:  application:    name: seata-serverlogging:  config: classpath:logback-spring.xml  file:    path: ${user.home}/logs/seata  extend:    logstash-appender:      destination: 127.0.0.1:4560    kafka-appender:      bootstrap-servers: 127.0.0.1:9092      topic: logback_to_logstashconsole:  user:    username: seata    password: seataseata:  config:    # support: nacos, consul, apollo, zk, etcd3    type: nacos    nacos:      server-addr: nacos-server:8848      namespace: seata-server      group: SEATA_GROUP      usernam: nacos      password: nacos      data-id: seataServer.properties  registry:    # support: nacos, eureka, redis, zk, consul, etcd3, sofa    type: nacos    nacos:      application: seata-server      server-addr: nacos-server:8848      group: SEATA_GROUP      namespace: seata-server      # tc集群名称      cluster: default      username: nacos      password: nacos#  store:#    # support: file 、 db 、 redis#    mode: file#  server:#    service-port: 8091 #If not configured, the default is &#39;${server.port} + 1000&#39;  security:    secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017    tokenValidityInMilliseconds: 1800000    ignore:      urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-fe/public/**,/api/v1/auth/login</code></pre><h3 id="%E5%9C%A8nacos%E9%85%8D%E7%BD%AE%E4%B8%AD%E5%BF%83%E4%B8%8A%E5%88%9B%E5%BB%BA%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6" tabindex="-1">在nacos配置中心上创建配置文件</h3><p>此处dataId为seataServer.properties</p><p><img src="/upload/2023/03/1677234924500.jpg" alt="在nacos配置中心上创建配置文件" /></p><h3 id="%E4%BF%AE%E6%94%B9nacos%E4%B8%8A%E7%9A%84%E9%85%8D%E7%BD%AE" tabindex="-1">修改nacos上的配置</h3><p>这里我使用的是mysql8，官方提供了<a href="https://github.com/seata/seata/blob/develop/script/server/db/mysql.sql" target="_blank">建表脚本</a>文件。</p><pre><code class="language-properties">store.mode=db#-----db-----store.db.datasource=druidstore.db.dbType=mysql# 需要根据mysql的版本调整driverClassName# mysql8及以上版本对应的driver：com.mysql.cj.jdbc.Driver# mysql8以下版本的driver：com.mysql.jdbc.Driverstore.db.driverClassName=com.mysql.cj.jdbc.Driverstore.db.url=jdbc:mysql://mysql:3306/seata-server?useUnicode=true&amp;characterEncoding=utf8&amp;connectTimeout=1000&amp;socketTimeout=3000&amp;autoReconnect=true&amp;useSSL=falsestore.db.user= 用户名store.db.password=密码# 数据库初始连接数store.db.minConn=1# 数据库最大连接数store.db.maxConn=20# 获取连接时最大等待时间 默认5000，单位毫秒store.db.maxWait=5000# 全局事务表名 默认global_tablestore.db.globalTable=global_table# 分支事务表名 默认branch_tablestore.db.branchTable=branch_table# 全局锁表名 默认lock_tablestore.db.lockTable=lock_table# 查询全局事务一次的最大条数 默认100store.db.queryLimit=100# undo保留天数 默认7天,log_status=1（附录3）和未正常清理的undoserver.undo.logSaveDays=7# undo清理线程间隔时间 默认86400000，单位毫秒server.undo.logDeletePeriod=86400000# 二阶段提交重试超时时长 单位ms,s,m,h,d,对应毫秒,秒,分,小时,天,默认毫秒。默认值-1表示无限重试# 公式: timeout&gt;=now-globalTransactionBeginTime,true表示超时则不再重试# 注: 达到超时时间后将不会做任何重试,有数据不一致风险,除非业务自行可校准数据,否者慎用server.maxCommitRetryTimeout=-1# 二阶段回滚重试超时时长server.maxRollbackRetryTimeout=-1# 二阶段提交未完成状态全局事务重试提交线程间隔时间 默认1000，单位毫秒server.recovery.committingRetryPeriod=1000# 二阶段异步提交状态重试提交线程间隔时间 默认1000，单位毫秒server.recovery.asynCommittingRetryPeriod=1000# 二阶段回滚状态重试回滚线程间隔时间  默认1000，单位毫秒server.recovery.rollbackingRetryPeriod=1000# 超时状态检测重试线程间隔时间 默认1000，单位毫秒，检测出超时将全局事务置入回滚会话管理器server.recovery.timeoutRetryPeriod=1000</code></pre><h3 id="docker-compose.yml%E6%96%87%E4%BB%B6" tabindex="-1">docker-compose.yml文件</h3><pre><code class="language-yaml">version: &quot;3.8&quot;services:  seata1:    image: seataio/seata-server:latest    container_name: seata1    ports:      - &quot;7091:7091&quot;      - &quot;8091:8091&quot;    environment:      - STORE_MODE=db      # 以SEATA_IP作为host注册seata server,可选, 指定seata-server启动的IP, 该IP用于向注册中心注册时使用, 如eureka等      - SEATA_IP=${seata_ip}      # 可选, 指定seata-server启动的端口, 默认为 8091           - SEATA_PORT=8091    volumes:      - &quot;/usr/share/zoneinfo/Asia/Shanghai:/etc/localtime&quot;        #设置系统时区      - &quot;/usr/share/zoneinfo/Asia/Shanghai:/etc/timezone&quot;  #设置时区      # 假设我们通过docker cp命令把资源文件拷贝到相对路径&#96;./seata-server/resources&#96;中      - &quot;./seata-server/resources:/seata-server/resources&quot;  seata2:    image: seataio/seata-server:latest    container_name: seata2    ports:      - &quot;7092:7091&quot;      - &quot;8092:8092&quot;    environment:      - STORE_MODE=db      # 以SEATA_IP作为host注册seata server,可选, 指定seata-server启动的IP, 该IP用于向注册中心注册时使用, 如eureka等      - SEATA_IP=${seata_ip}      # 可选, 指定seata-server启动的端口, 默认为 8091        - SEATA_PORT=8092    volumes:      - &quot;/usr/share/zoneinfo/Asia/Shanghai:/etc/localtime&quot;        #设置系统时区      - &quot;/usr/share/zoneinfo/Asia/Shanghai:/etc/timezone&quot;  #设置时区      # 假设我们通过docker cp命令把资源文件拷贝到相对路径&#96;./seata-server/resources&#96;中      - &quot;./seata-server/resources:/seata-server/resources&quot;</code></pre><blockquote><p>踩坑：</p><ol><li><p><code>services.seata.environment</code> 下的SEATA_IP不填写的话，会默认将容器IP注册到Nacos，可能导致网络不通。</p></li><li><p><code>services.seata.environment</code> 下SEATA_PORT，这里特别注意下，当时没有仔细看官网的环境变量描述，直接拷贝了docker-compose.yml，以为这个配置的仅仅是注册到nacos的端口，实际上也是seata服务启动的端口，官网上集群配置第二个是错的。直接启动客户端，总是会连不上第二个seata，客户端日志会打印出这个日志：</p><p><code>can not connect to xxx.xxx.xxx.xxx:8092 cause:can not register RM,err:register RMROLE error, errMsg:null</code></p><p>官网的配置：</p><p><img src="/upload/2023/03/1677263852528.jpg" alt="官网的配置" /><br />这里官网上配置启动端口为<code>8092</code>，注册到nacos的端口也是<code>8092</code> ，但是实际上seata在容器内启动的端口是<code>8091</code> ，这里导致我就是连不上第二台服务。</p></li></ol></blockquote><h2 id="%E9%85%8D%E7%BD%AE%E5%AE%A2%E6%88%B7%E7%AB%AF" tabindex="-1">配置客户端</h2><h3 id="maven%E4%BE%9D%E8%B5%96" tabindex="-1">Maven依赖</h3><pre><code class="language-xml">&lt;dependency&gt;    &lt;groupId&gt;com.alibaba.cloud&lt;/groupId&gt;    &lt;artifactId&gt;spring-cloud-starter-alibaba-seata&lt;/artifactId&gt;    &lt;version&gt;${seata.version}&lt;/version&gt;&lt;/dependency&gt;</code></pre><h3 id="%E5%AE%A2%E6%88%B7%E7%AB%AFapplication.yml%E9%85%8D%E7%BD%AE" tabindex="-1">客户端application.yml配置</h3><pre><code class="language-yaml">seata:  enabled: true  tx-service-group: test_tx_group  service:    vgroup-mapping:      test_tx_group: default  registry:    type: nacos          #注册中心    nacos:      server-addr: ${spring.cloud.nacos.discovery.server-addr}      group: SEATA_GROUP      namespace: seata-server      cluster: default  config:    type: nacos          #配置中心    nacos:      server-addr: ${spring.cloud.nacos.discovery.server-addr}      namespace: seata-server      group: SEATA_GROUP      data-id: seataServer.properties</code></pre><h2 id="seata%E5%8E%9F%E7%90%86" tabindex="-1">Seata原理</h2><ol><li>TM开启分布式事务（TM向TC注册全局事务记录）。</li><li>按业务场景，编排数据库，服务等事务内资源（RM向TC汇报资源准备状态）。</li><li>TM结束分布式事务，事务一阶段结束（TM通知TC提交/回滚分布式事务）。</li><li>TC汇总事务信息，决定分布式事务是提交还是回滚。</li><li>TC通知所有RM提交/回滚资源。</li></ol><h3 id="at%E6%A8%A1%E5%BC%8F%E6%98%AF%E6%80%8E%E4%B9%88%E5%81%9A%E5%88%B0%E5%AF%B9%E4%B8%9A%E5%8A%A1%E6%97%A0%E4%BE%B5%E5%85%A5%E7%9A%84" tabindex="-1">AT模式是怎么做到对业务无侵入的</h3><h4 id="%E4%BA%8B%E5%8A%A1%E4%B8%80%E9%98%B6%E6%AE%B5" tabindex="-1">事务一阶段</h4><p>在第一阶段，Seata会拦截业务SQL：</p><ol><li>解析SQL语义，找到业务SQL要更新的业务数据，在业务被更新之前，将其保存为“before image”。</li><li>执行业务SQL更新业务数据，在业务数据更新之后。</li><li>其保存为“after image”，最后生成行锁。</li></ol><p>以上操作全部在一个数据库事务内完成，这样保证了一阶段操作的原子性。</p><div class="mermaid"><svg id="render1208629680" width="100%" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="63" style="max-width: 1544.921875px;" viewBox="-8 -8 1544.921875 63"><style>#render1208629680 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#render1208629680 .error-icon{fill:#552222;}#render1208629680 .error-text{fill:#552222;stroke:#552222;}#render1208629680 .edge-thickness-normal{stroke-width:2px;}#render1208629680 .edge-thickness-thick{stroke-width:3.5px;}#render1208629680 .edge-pattern-solid{stroke-dasharray:0;}#render1208629680 .edge-pattern-dashed{stroke-dasharray:3;}#render1208629680 .edge-pattern-dotted{stroke-dasharray:2;}#render1208629680 .marker{fill:#333333;stroke:#333333;}#render1208629680 .marker.cross{stroke:#333333;}#render1208629680 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#render1208629680 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#render1208629680 .cluster-label text{fill:#333;}#render1208629680 .cluster-label span{color:#333;}#render1208629680 .label text,#render1208629680 span{fill:#333;color:#333;}#render1208629680 .node rect,#render1208629680 .node circle,#render1208629680 .node ellipse,#render1208629680 .node polygon,#render1208629680 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#render1208629680 .node .label{text-align:center;}#render1208629680 .node.clickable{cursor:pointer;}#render1208629680 .arrowheadPath{fill:#333333;}#render1208629680 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#render1208629680 .flowchart-link{stroke:#333333;fill:none;}#render1208629680 .edgeLabel{background-color:#e8e8e8;text-align:center;}#render1208629680 .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#render1208629680 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#render1208629680 .cluster text{fill:#333;}#render1208629680 .cluster span{color:#333;}#render1208629680 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#render1208629680 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#render1208629680 .default path{fill:#fff!important;stroke:#000000!important;stroke-width:3px!important;}#render1208629680 .default rect{fill:#fff!important;stroke:#000000!important;stroke-width:3px!important;}#render1208629680 .default polygon{fill:#fff!important;stroke:#000000!important;stroke-width:3px!important;}#render1208629680 .default ellipse{fill:#fff!important;stroke:#000000!important;stroke-width:3px!important;}#render1208629680 .default circle{fill:#fff!important;stroke:#000000!important;stroke-width:3px!important;}</style><g><g class="output"><g class="clusters"></g><g class="edgePaths"><g class="edgePath LS-A LE-B" style="opacity: 1;" id="L-A-B"><path class="path" d="M129.359375,27.5L133.52604166666666,27.5C137.69270833333334,27.5,146.02604166666666,27.5,154.359375,27.5C162.69270833333334,27.5,171.02604166666666,27.5,175.19270833333334,27.5L179.359375,27.5" marker-end="url(#arrowhead4311)" style="fill:none"></path><defs><marker id="arrowhead4311" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath LS-B LE-C" style="opacity: 1;" id="L-B-C"><path class="path" d="M313.359375,27.5L317.5260416666667,27.5C321.6927083333333,27.5,330.0260416666667,27.5,338.359375,27.5C346.6927083333333,27.5,355.0260416666667,27.5,359.1927083333333,27.5L363.359375,27.5" marker-end="url(#arrowhead4312)" style="fill:none"></path><defs><marker id="arrowhead4312" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath LS-C LE-D" style="opacity: 1;" id="L-C-D"><path class="path" d="M575.578125,27.5L579.7447916666666,27.5C583.9114583333334,27.5,592.2447916666666,27.5,600.578125,27.5C608.9114583333334,27.5,617.2447916666666,27.5,621.4114583333334,27.5L625.578125,27.5" marker-end="url(#arrowhead4313)" style="fill:none"></path><defs><marker id="arrowhead4313" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath LS-D LE-E" style="opacity: 1;" id="L-D-E"><path class="path" d="M754.0859375,27.5L758.2526041666666,27.5C762.4192708333334,27.5,770.7526041666666,27.5,779.0859375,27.5C787.4192708333334,27.5,795.7526041666666,27.5,799.9192708333334,27.5L804.0859375,27.5" marker-end="url(#arrowhead4314)" style="fill:none"></path><defs><marker id="arrowhead4314" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath LS-E LE-G" style="opacity: 1;" id="L-E-G"><path class="path" d="M989.71875,27.5L993.8854166666666,27.5C998.0520833333334,27.5,1006.3854166666666,27.5,1014.71875,27.5C1023.0520833333334,27.5,1031.3854166666667,27.5,1035.5520833333333,27.5L1039.71875,27.5" marker-end="url(#arrowhead4315)" style="fill:none"></path><defs><marker id="arrowhead4315" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g><g class="edgePath LS-G LE-H" style="opacity: 1;" id="L-G-H"><path class="path" d="M1124.71875,27.5L1128.8854166666667,27.5C1133.0520833333333,27.5,1141.3854166666667,27.5,1149.71875,27.5C1158.0520833333333,27.5,1166.3854166666667,27.5,1170.5520833333333,27.5L1174.71875,27.5" marker-end="url(#arrowhead4316)" style="fill:none"></path><defs><marker id="arrowhead4316" viewBox="0 0 10 10" refX="9" refY="5" markerUnits="strokeWidth" markerWidth="8" markerHeight="6" orient="auto"><path d="M 0 0 L 10 5 L 0 10 z" class="arrowheadPath" style="stroke-width: 1; stroke-dasharray: 1, 0;"></path></marker></defs></g></g><g class="edgeLabels"><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><text><tspan xml:space="preserve" dy="1em" x="1"></tspan></text></g></g><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><text><tspan xml:space="preserve" dy="1em" x="1"></tspan></text></g></g><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><text><tspan xml:space="preserve" dy="1em" x="1"></tspan></text></g></g><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><text><tspan xml:space="preserve" dy="1em" x="1"></tspan></text></g></g><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><text><tspan xml:space="preserve" dy="1em" x="1"></tspan></text></g></g><g class="edgeLabel" style="opacity: 1;" transform=""><g transform="translate(0,0)" class="label"><rect rx="0" ry="0" width="0" height="0"></rect><text><tspan xml:space="preserve" dy="1em" x="1"></tspan></text></g></g></g><g class="nodes"><g class="node default" style="opacity: 1;" id="flowchart-A-4141" transform="translate(68.6796875,27.5)"><rect rx="19.5" ry="19.5" x="-60.6796875" y="-19.5" width="121.359375" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-45.8046875,-9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="1">解析SQL语句</tspan></text></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-B-4142" transform="translate(246.359375,27.5)"><rect rx="5" ry="5" x="-67" y="-19.5" width="134" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-57,-9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="1">提取表的元数据</tspan></text></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-C-4143" transform="translate(469.46875,27.5)"><rect rx="5" ry="5" x="-106.109375" y="-19.5" width="212.21875" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-96.109375,-9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="1">保存原快照的Before Image</tspan></text></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-D-4144" transform="translate(689.83203125,27.5)"><rect rx="5" ry="5" x="-64.25390625" y="-19.5" width="128.5078125" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-54.25390625,-9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="1">执行业务员SQL</tspan></text></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-E-4145" transform="translate(896.90234375,27.5)"><rect rx="5" ry="5" x="-92.81640625" y="-19.5" width="185.6328125" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-82.81640625,-9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="1">保存新快照After Image</tspan></text></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-G-4146" transform="translate(1082.21875,27.5)"><rect rx="5" ry="5" x="-42.5" y="-19.5" width="85" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-32.5,-9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="1">生成行锁</tspan></text></g></g></g><g class="node default" style="opacity: 1;" id="flowchart-H-4147" transform="translate(1351.8203125,27.5)"><rect rx="19.5" ry="19.5" x="-177.1015625" y="-19.5" width="354.203125" height="39" class="label-container"></rect><g class="label" transform="translate(0,0)"><g transform="translate(-162.2265625,-9.5)"><text style=""><tspan xml:space="preserve" dy="1em" x="1">提交业务SQL、undo/redo log、行锁到业务DB</tspan></text></g></g></g></g></g></g></svg></div><h4 id="%E4%BA%8B%E5%8A%A1%E4%BA%8C%E9%98%B6%E6%AE%B5" tabindex="-1">事务二阶段</h4><h5 id="%E4%BA%8C%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4" tabindex="-1">二阶段提交</h5><p>在二阶段如果没有异常，顺利提交的话。以为在一阶段，业务SQL已经提交到了数据库，所以Seata框架只需要将一阶段保存的快照数据和行锁删除掉，完成数据清理即可。</p><h5 id="%E4%BA%8C%E9%98%B6%E6%AE%B5%E5%9B%9E%E6%BB%9A" tabindex="-1">二阶段回滚</h5><p>二阶段如果是回滚的话，seata就需要回滚一阶段已经执行的业务SQL，还原业务数据。其回滚方式便是使用“before image”来还原业务数据。但在还原之前要先校验脏写，对比“数据库当前的业务数据”和after image。如果两份数据完全一致就说明没有脏写，可以还原业务数据，如果不一致就说明有脏写，出现脏写就需要转人工处理。</p>]]>
                    </description>
                    <pubDate>Sun, 24 Oct 2021 15:16:02 CST</pubDate>
                </item>
                <item>
                    <title>
                        <![CDATA[Redis淘汰策略]]>
                    </title>
                    <link>https://zygzyg.com/archives/03037c727b0d40bcb99839f6c8d44753</link>
                    <description>
                            <![CDATA[<h1 id="redis%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5" tabindex="-1">Redis淘汰策略</h1><h2 id="redis%E7%9A%84%E6%9C%80%E5%A4%A7%E5%8D%A0%E7%94%A8%E5%86%85%E5%AD%98" tabindex="-1">Redis的最大占用内存</h2><blockquote><p>如何查看Redis最大占用内存？</p></blockquote><p>在配置文件中可以找到配置项<code>maxmemory &lt;bytes&gt;</code>：</p><pre><code class="language-conf"># 设置内存使用限制为指定的字节数。# 当达到内存限制时，Redis将根据所选的淘汰策略尝试删除键。## 如果Redis无法根据策略删除键，或者策略设置为“noeviction”，Redis将开始# 对使用更多内存的命令（如SET，LPUSH等）回复错误，并将继续回复只读命令（如GET）。## 当使用Redis作为LRU或LFU缓存，或者设置实例的硬内存限制时，此选项通常很有用（使用“noeviction”策略）。## 警告：如果将副本附加到具有maxmemory的实例，# 需要用于馈送副本的输出缓冲区的大小会从已使用的内存计数中减去，# 以便网络问题/重新同步不会触发循环，其中键被清除，# 反过来，副本的输出缓冲区用DEL清除被清除的键，触发更多键的删除，# 依此类推，直到完全清空数据库。## 简而言之...如果您有附加的副本，则建议您设置较低的maxmemory限制，# 以便系统上有一些空闲RAM可供副本输出缓冲区使用# （但是如果策略是“noeviction”，则不需要这样做）。## maxmemory &lt;bytes&gt;</code></pre><p>如果不设置<code>maxmemory</code>或者设置为0（默认就是0），那么在64位操作系统下则不限制内存大小，在32位操作系统下最多能使用3GB的内存。可以使用<code>info memory</code>查看Redis内存使用情况。</p><h2 id="redis%E6%98%AF%E5%A6%82%E4%BD%95%E5%88%A0%E9%99%A4%E6%95%B0%E6%8D%AE%E7%9A%84" tabindex="-1">Redis是如何删除数据的</h2><h3 id="redis%E8%BF%87%E6%9C%9F%E9%94%AE%E7%9A%84%E5%88%A0%E9%99%A4%E7%AD%96%E7%95%A5" tabindex="-1">Redis过期键的删除策略</h3><blockquote><p>如果一个键过期了，那Redis是不是在它过期时马上就从内存中删除它呢？</p></blockquote><h4 id="%E4%B8%89%E7%A7%8D%E5%88%A0%E9%99%A4%E7%AD%96%E7%95%A5" tabindex="-1">三种删除策略</h4><h5 id="%E7%AB%8B%E5%8D%B3%E5%88%A0%E9%99%A4" tabindex="-1">立即删除</h5><p>Redis不可能时时刻刻遍历所有被设置的过期时间的key来检测数据是否已经达到过期时间，然后对它进行删除。立即删除能保证内存中数据的最大新鲜度，因为这样可以保证key在过期后马上被删除，其占用的内存也会随之释放。但是这样对CPU不太友好。因为删除操作会占用CPU的时间，如果刚好碰上了CPU很忙的时候，比如正在做交集或者排序统计的时候，会给CPU造成额外压力，产生大量性能消耗，同时还会影响数据的读取操作。</p><blockquote><p><strong>立即删除</strong>对CPU不友好，用CPU性能换内存空间。</p></blockquote><h5 id="%E6%83%B0%E6%80%A7%E5%88%A0%E9%99%A4" tabindex="-1">惰性删除</h5><p>数据到达过期时间了，不做处理，等到下一次访问该数据的时候，如果没有过期则返回数据，如果发现过期了，则删除数据并返回数据不存在。这样做的缺点就是对内存不友好。如果一个key已经过期了，但是它实际还保存在Redis的内存中，其占用的内存不会释放，比如Redis中有大量的过期键，而这些过期键有恰好没有被访问到的话，那么这些key也许永远也不会被删除（除非手动删除），这种情况甚至可以看作内存泄漏（一堆垃圾数据占用大量内存），服务器又不会主动去释放它们，这样对于依赖内存的Redis来说肯定是不好的。可以使用<code>lazyfree-lazy-eviction yes</code>开启惰性删除。</p><blockquote><p><strong>惰性删除</strong>对内存不友好，用内存空间换CPU性能。</p></blockquote><h5 id="%E5%AE%9A%E6%9C%9F%E5%88%A0%E9%99%A4" tabindex="-1">定期删除</h5><p>定期删除则是上面两种策略的折中，定期删除就是<mark>每隔一段时间执行一次删除过期键的操作</mark>并通过限制删除操作执行时长和频率来减少删除键对CPU的影响。</p><p>周期性轮询Redis库中的时效性，采用<strong>随机抽取</strong>的策略，利用过期数据占比的方式控制删除频度：</p><ol><li>特点1：CPU性能占用设置有峰值，检测频度可自定义设置。</li><li>特点2：内存压力不是很大，长期占用内存的冷数据会被持续清理。</li></ol><p>比如Redis默认每隔100ms就检查是否有过期的key，有的话就删除（🚨注意这里Redis并不是每隔100ms就将所有key检查一次而是随机地抽取来检查）。因此如果只是采用这种策略地话会导致很多key不会被删除。</p><p>定期删除策略地难点就是确定删除操作执行地时长和频率：</p><ul><li>如果删除操作执行的<strong>次数太多</strong>或者执行的<strong>时间太长</strong>，那么就会和立即删除的缺点一样了。</li><li>如果删除操作执行的<strong>次数太少</strong>或者执行的<strong>时间太短</strong>，那么就会和惰性删除的缺点一样了。</li></ul><p>所以如果要使用惰性删除的话，服务器必须根据使用情况，合理地设置删除操作的执行时长和频率。</p><blockquote><p><strong>定期删除</strong>是一个折中的方法，但是也会存在漏网之鱼。</p></blockquote><h2 id="%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5" tabindex="-1">淘汰策略</h2><blockquote><p>上面说到了三种删除策略，都存在一定的缺陷，那么Redis有更好的方案吗？</p></blockquote><h3 id="%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5%E6%9C%89%E5%93%AA%E4%BA%9B" tabindex="-1">淘汰策略有哪些</h3><blockquote><p>在Redis中一共有8种淘汰策略，默认的是<code>noeviction</code>（不删除任何数据）。</p></blockquote><p>可以从两个维度（过期键中筛选或所有键中筛选）以及四个算法（LRU、LFU、Random、ttl）来区分其余7个淘汰策略。</p><table><thead><tr><th style="text-align:center"></th><th style="text-align:center">LRU</th><th style="text-align:center">LFU</th><th style="text-align:center">Random</th><th style="text-align:center">TTL</th></tr></thead><tbody><tr><td style="text-align:center">过期键中筛选</td><td style="text-align:center">volatile-lru</td><td style="text-align:center">volatile-lfu</td><td style="text-align:center">volatile-random</td><td style="text-align:center">volatile-ttl</td></tr><tr><td style="text-align:center">所有键中筛选</td><td style="text-align:center">allkeys-lru</td><td style="text-align:center">allkeys-lfu</td><td style="text-align:center">allkeys-random</td><td style="text-align:center"></td></tr></tbody></table><h3 id="%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5%E7%9A%84%E4%BD%9C%E7%94%A8%E5%88%86%E5%88%AB%E6%98%AF%E4%BB%80%E4%B9%88" tabindex="-1">淘汰策略的作用分别是什么</h3><p>在Redis配置文件中是这样描述淘汰策略的：</p><pre><code class="language-conf"># MAXMEMORY POLICY: how Redis will select what to remove when maxmemory# is reached. You can select one from the following behaviors:## volatile-lru -&gt; Evict using approximated LRU, only keys with an expire set.# allkeys-lru -&gt; Evict any key using approximated LRU.# volatile-lfu -&gt; Evict using approximated LFU, only keys with an expire set.# allkeys-lfu -&gt; Evict any key using approximated LFU.# volatile-random -&gt; Remove a random key having an expire set.# allkeys-random -&gt; Remove a random key, any key.# volatile-ttl -&gt; Remove the key with the nearest expire time (minor TTL)# noeviction -&gt; Don&#39;t evict anything, just return an error on write operations.## LRU means Least Recently Used# LFU means Least Frequently Used## Both LRU, LFU and volatile-ttl are implemented using approximated# randomized algorithms.## Note: with any of the above policies, when there are no suitable keys for# eviction, Redis will return an error on write operations that require# more memory. These are usually commands that create new keys, add data or# modify existing keys. A few examples are: SET, INCR, HSET, LPUSH, SUNIONSTORE,# SORT (due to the STORE argument), and EXEC (if the transaction includes any# command that requires memory).## The default is:## maxmemory-policy noeviction</code></pre><p>MAXMEMORY策略：当达到maxmemory时，Redis将选择怎么删除键。可以从以下行为中选择一个：</p><ul><li><code>noeviction</code>：默认策略，不淘汰数据；大部分写命令都将返回错误（<code>DEL</code>等少数除外）。</li><li><code>allkeys-lru</code>：从所有数据中根据<strong>LRU</strong>算法挑选数据淘汰。</li><li><code>volatile-lru</code>：从设置了过期时间的数据中根据<strong>LRU</strong>算法挑选数据淘汰。</li><li><code>allkeys-random</code>：从所有数据中随机挑选数据淘汰。</li><li><code>volatile-random</code>：从设置了过期时间的数据中随机挑选数据淘汰。</li><li><code>volatile-ttl</code>：移除剩余时间最短（<strong>TTL</strong>最小）的键。</li><li><code>allkeys-lfu</code>：从所有数据中根据<strong>LFU</strong>算法挑选数据淘汰（4.0及以上版本可用）。</li><li><code>volatile-lfu</code>：从设置了过期时间的数据中根据<strong>LFU</strong>算法挑选数据淘汰（4.0及以上版本可用）。</li></ul><p><code>LRU</code>（Least Recently Used）表示最近最少使用，<code>LFU</code>（Least Frequently Used）表示最少使用频率。<code>LRU</code>、<code>LFU</code>和<code>volatile-ttl</code>都使用近似随机算法实现。<br />注意：使用上述任何策略时，当没有适当的键可用于淘汰时，Redis将对需要更多内存的写操作返回错误。这些通常是创建新键、添加数据或修改现有键的命令。一些示例包括：<code>SET</code>、<code>INCR</code>、<code>HSET</code>、<code>LPUSH</code>、<code>SUNIONSTORE</code>、<code>SORT</code>（由于SCORE参数）和<code>EXEC</code>（如果事务包含任何需要内存的命令）。</p><h3 id="%E6%80%8E%E6%A0%B7%E9%80%89%E6%8B%A9%E4%BD%BF%E7%94%A8%E5%93%AA%E7%A7%8D%E6%B7%98%E6%B1%B0%E7%AD%96%E7%95%A5" tabindex="-1">怎样选择使用哪种淘汰策略</h3><ul><li>在所有key都是最近经常使用的情况，那么就选择<code>allkeys-lru</code>，如果不确定使用哪种策略的话，推荐使用<code>allkeys-lru</code>。</li><li>如果所有key的访问频率都是差不多的，那么可以选择<code>allkeys-random</code>策略。</li><li>如果对数据有足够的了解，能够为key指定hint（通过expire/ttl指定），那么可以选择<code>volatile-ttl</code>。</li></ul><h2 id="lru%E4%BB%A5%E5%8F%8Alfu%E7%AE%97%E6%B3%95" tabindex="-1">LRU以及LFU算法</h2><h3 id="lru%E7%AE%97%E6%B3%95" tabindex="-1">LRU算法</h3><h4 id="%E5%8E%9F%E7%94%9F%E7%9A%84lru%E7%AE%97%E6%B3%95" tabindex="-1">原生的LRU算法</h4><p><strong>LRU</strong>（<strong>Least Recently Used</strong>）最近最少使用。优先淘汰最近未被使用的数据，其核心思想是：如果数据最近被访问过，那么将来被访问的几率也更高。<strong>LRU</strong>底层结构是<mark>hash表+双向链表</mark>。hash表用于保证查询操作的时间复杂度是O(1)，双向链表用于保证节点插入和节点删除的时间复杂度是O(1)。</p><p>为什么是双向链表而不是单链表呢？单链表可以实现头部插入新节点、尾部删除旧节点的时间复杂度都是O(1)，但是对于中间节点时间复杂度是O(n)，因为对于中间节点c，我们需要将该节点c移动到头部，此时只知道他的下一个节点，要知道其上一个节点需要遍历整个链表，时间复杂度为O(n)。</p><ul><li><strong>LRU GET操作</strong>：如果节点存在，则将该节点移动到链表头部，并返回节点值。</li><li><strong>LRU PUT操作</strong>：<ol><li>节点不存在，则新增节点，并将该节点放到链表头部。</li><li>节点存在，则更新节点，并将该节点放到链表头部。</li></ol></li></ul><p><img src="/upload/2023/03/LRU%E7%BB%93%E6%9E%84.png" alt="LRU结构" /></p><h4 id="redis%E7%9A%84%E8%BF%91%E4%BC%BClru%E7%AE%97%E6%B3%95" tabindex="-1">Redis的近似LRU算法</h4><blockquote><p>Redis为什么不使用原生的<strong>LRU</strong>算法呢？</p></blockquote><p>Redis不使用原生<strong>LRU</strong>算法的主要原因有：</p><ul><li>原生<strong>LRU</strong>算法需要双向链表来管理数据，需要<strong>额外内存</strong>。</li><li>数据访问时涉及<strong>数据移动，有性能损耗</strong>。</li><li>Redis现有<strong>数据结构需要改造</strong>。</li></ul><p>Redis使用<strong>LRU</strong>算法来淘汰过期的缓存数据，以保持内存的整洁和高效。但是<strong>LRU</strong>算法的实现需要耗费大量的时间和空间，因为它需要跟踪每个缓存数据最后一次被访问的时间戳。为了解决这个问题，Redis使用了一种近似LRU算法，它通过一些简单的逻辑来实现近似LRU算法的效果，同时减少了空间和时间的开销。</p><h5 id="redis%E7%9A%84%E8%BF%91%E4%BC%BClru%E7%AE%97%E6%B3%95%E9%80%BB%E8%BE%91" tabindex="-1">Redis的近似LRU算法逻辑</h5><ol><li><p><strong>第一次淘汰</strong>：随机抽取<code>maxmemory-samples</code>（在配置文件中修改，默认为5）个数据放入待淘汰数据池（<code>evictionPoolEntry</code>）。</p><blockquote><p>修改<code>maxmemory-samples</code>配置为10的话将非常接近原生<strong>LRU</strong>效果，但是更消耗CPU。可以参考<a href="https://redis.io/docs/reference/eviction/#approximated-lru-algorithm" target="_blank">官方的说明</a>。</p></blockquote><p><img src="/upload/2023/03/lru_comparison.png" alt="lru_comparison" /></p></li><li><p><strong>第二次淘汰</strong>：还是随机抽取<code>maxmemory-samples</code>个数据，但是只要数据比待淘汰数据池（<code>evictionPoolEntry</code>）中的任意一个数据的lru小，则将该数据放入至待淘汰数据池。</p></li><li><p><strong>执行淘汰</strong>：挑选待淘汰数据池中lru最小的一条数据进行淘汰。</p></li></ol><h5 id="redis%E7%9A%84%E8%BF%91%E4%BC%BClru%E7%AE%97%E6%B3%95%E7%9A%84%E7%BC%BA%E9%99%B7" tabindex="-1">Redis的近似LRU算法的缺陷</h5><p>假设现在又三个key分别是A、B、C，在一段时间T内A被访问了6次，B被访问了4次，C被访问了2次，如下所示，从左往右表示时间，<code>|</code>表示开始和结束、<code>-</code>表示一个单位的时间。</p><pre><code class="language-">|----A----A----A----A-----A---A-------------||-----B--------B----B----------------B------||-------------------C---------------------C-|</code></pre><p>当系统内存达到限制，触发<strong>LRU</strong>算法的时候，开始淘汰<strong>key</strong>的时候，如果按照LRU算法的逻辑（最近最少使用），那么被淘汰的概率将是<mark>A&gt;B&gt;C</mark>。然而实际上A的访问次数却是最多的，当我们并不希望<strong>A</strong>被淘汰，而是希望淘汰的概率是<mark>C&gt;B&gt;A</mark>的时候，使用<strong>LRU</strong>算法显然不太合适。淘汰算法的本意是保留那些将来最有可能被再次访问的数据，而LRU算法只是预测最近被访问的数据将来最有可能被访问到。所以在<strong>Redis4.0</strong>的时候推出了<strong>LFU</strong>算法（<strong>Least Frequently Used</strong>）。</p><h3 id="lfu%E7%AE%97%E6%B3%95" tabindex="-1">LFU算法</h3><blockquote><p><strong>LFU</strong>（<strong>Least Frequently Used</strong>）是<strong>Redis4.0</strong>引入的淘汰算法，它通过key的访问频率、访问时间比较来淘汰<strong>key</strong>，重点突出的是<mark>Frequently Used</mark>。</p></blockquote><p>因为<strong>LFU</strong>算法是根据数据访问的频率来选择被淘汰数据的，所以<strong>LFU</strong>算法会记录每个数据的访问次数。当一个数据被再次访问时，就会增加该数据的访问次数。但是，访问次数和访问频率这两者并不完全相同。访问频率是指在<mark>一定时间内</mark>的访问次数。比如现在有两个key分别是<strong>A</strong>、<strong>B</strong>。<strong>A</strong>在15min内被访问了60次，<strong>B</strong>在5min内被访问了30次。如果是看访问次数的话，毫无疑问<strong>A</strong>肯定大于<strong>B</strong>，但是如果是计算访问频率的话，<strong>A</strong>被访问的频率则是<mark>4次/min</mark>，而B则是<mark>6次/min</mark>。所以<strong>B</strong>的访问频率是肯定大于<strong>A</strong>的。先被淘汰的<strong>key</strong>应该是<strong>A</strong>而不是<strong>B</strong>。</p><h4 id="lfu%E7%AE%97%E6%B3%95%E7%9A%84%E5%AE%9E%E7%8E%B0%E9%80%BB%E8%BE%91" tabindex="-1">LFU算法的实现逻辑</h4><p>在<code>LFU</code>算法中，为每个key维护一个计数器。每次key被访问的时候，计数器增大。计数器越大，可以说明访问越频繁。但是这样做存在一定的问题，上面也说到了访问次数和访问频率并不完全相同。所以只是简单的增加计数器的方法并不完美。访问模式是会频繁变化的，一段时间内频繁访问的key一段时间之后可能会很少被访问到，只增加计数器并不能体现这种趋势。</p><p>在Redis中key的对象是<code>redisObject</code>，结构如下：</p><pre><code class="language-c">typedef struct redisObject {    unsigned type:4;    unsigned encoding:4;    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or                            * LFU data (least significant 8 bits frequency                            * and most significant 16 bits access time). */    int refcount;    void *ptr;} robj;</code></pre><p>源码中的注释也说到了24bits的<code>lru:LRU_BITS</code>是用来记录<code>LRU time</code>的，然后<strong>LFU</strong>策略同样可以使用这个字段，但是是分为<strong>16bit</strong>和<strong>8bit</strong>来使用，前<strong>16bit</strong>用来记录最近一次计数器降低的时间<strong>Last decr time</strong>，单位是分钟，后<strong>8bit</strong>记录计数器数值<code>counter</code>。</p><pre><code class="language-">16 bits       8 bits+----------------+--------++ Last decr time | LOG_C  |+----------------+--------+</code></pre><p>所以当<strong>LFU</strong>策略筛选数据时，<strong>Redis</strong>会在候选集合中，根据<code>lru:LRU_BITS</code>字段的后8bit选择访问次数最少的数据进行淘汰。当访问次数相同时，再根据<code>lru:LRU_BITS</code>字段的前<strong>16bit</strong>值大小，选择访问时间最久远的数据进行淘汰。可以看到<strong>Redis</strong>只使用了<strong>8bit</strong>来记录数据的访问次数，但是<strong>8bit</strong>记录的最大值是<strong>255</strong>，当访问量大的时候，如果每次访问都加1，那么很快就会达到最大值<strong>255</strong>，这时可能大多数数据都是255的访问次数，这样就都会使用前<strong>16bit</strong>来进行淘汰了，相当于退化成<strong>LRU</strong>了。但是Redis使用了另外一种计数规则，这个规则还可以使用配置项来控制计数器的增加速度。</p><pre><code class="language-conf"># lfu-log-factor 10# lfu-decay-time 1</code></pre><ul><li><p><code>lfu-log-factor</code>：用计数器<code>counter</code>当前的值乘以配置项<code>lfu-log-factor</code>再加1，再取其倒数，得到一个<strong>p</strong>值。然后，把这个<strong>p</strong>值和一个取值范围在(0,1)间的随机数<strong>r</strong>值比大小，只有当<strong>p</strong>&gt;<strong>r</strong>的时候计数器才会加1。也就是<code>lfu-log-factor</code>越大，<code>counter</code>增长的越慢。配置文件中还列举了使用不同的<code>lfu-log-factor</code>时<code>counter</code>增长情况：</p><pre><code class="language-"># +--------+------------+------------+------------+------------+------------+# | factor | 100 hits   | 1000 hits  | 100K hits  | 1M hits    | 10M hits   |# +--------+------------+------------+------------+------------+------------+# | 0      | 104        | 255        | 255        | 255        | 255        |# +--------+------------+------------+------------+------------+------------+# | 1      | 18         | 49         | 255        | 255        | 255        |# +--------+------------+------------+------------+------------+------------+# | 10     | 10         | 18         | 142        | 255        | 255        |# +--------+------------+------------+------------+------------+------------+# | 100    | 8          | 11         | 49         | 143        | 255        |# +--------+------------+------------+------------+------------+------------+</code></pre><p>可以看到<code>counter</code>增长与访问次数呈现对数增长的趋势，随着访问次数越来越大，<code>counter</code>增长的越来越慢。</p></li><li><p><code>lfu-decay-time</code>：用来控制访问次数衰减。<strong>LFU</strong>策略会计算当前时间和数据最近一次访问时间的差值，并把这个差值换算成以分钟为单位。然后再把这个差值除以<code>lfu-decay-time</code>值，所得的结果就是数据<code>counter</code> 要衰减的值。</p></li></ul><blockquote><p><code>lfu-log-factor</code>设置越大，递增概率越低，<code>lfu-decay-time</code>设置越大，衰减速度会越慢。</p></blockquote><p>在应用<strong>LFU</strong>策略时，一般可以将<code>lfu-log-factor</code>设置为10。 如果业务应用中有短时高频访问的数据的话，建议把<code>lfu-decay-time</code>设置为1。可以快速衰减访问次数。</p><h3 id="lru%E5%92%8Clfu%E7%AE%97%E6%B3%95%E7%9A%84%E5%8C%BA%E5%88%AB" tabindex="-1">LRU和LFU算法的区别</h3><table><thead><tr><th style="text-align:center">算法</th><th style="text-align:center">LRU（Least Recently Used）</th><th style="text-align:center">LFU（Least Frequently Used）</th></tr></thead><tbody><tr><td style="text-align:center">核心思想</td><td style="text-align:center">按照最近使用时间淘汰</td><td style="text-align:center">按照使用频率淘汰</td></tr><tr><td style="text-align:center">淘汰策略</td><td style="text-align:center">淘汰最久未被使用的数据</td><td style="text-align:center">淘汰使用频率最低的数据</td></tr><tr><td style="text-align:center">适用场景</td><td style="text-align:center">数据访问具有时间局部性的场景</td><td style="text-align:center">数据访问具有空间局部性的场景</td></tr></tbody></table><p>总体来说，<strong>LRU</strong>算法适合用于数据访问具有时间局部性的场景，即最近访问的数据在不久的将来可能再次被访问，而<strong>LFU</strong>算法适合用于数据访问具有空间局部性的场景，即访问频率高的数据在不久的将来仍然会被频繁访问。</p>]]>
                    </description>
                    <pubDate>Mon, 16 Aug 2021 20:31:36 CST</pubDate>
                </item>
    </channel>
</rss>