<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Leviathan</title>
  <link href="https://leviathan.vip/" />
  <link href="https://leviathan.vip/" rel="self" />
  <id>https://leviathan.vip/</id>
  <updated>2026-01-05T10:00:00Z</updated>
  <author>
    <name>Leviathan</name>
  </author><entry>
    <title>DuckDB ART 索引实现解析</title>
    <link href="https://leviathan.vip/2026/01/05/duckdb-art/" />
    <id>https://leviathan.vip/2026/01/05/duckdb-art/</id>
    <updated>2026-01-05T10:00:00Z</updated>
    <summary type="html"><![CDATA[<h2 id="版本">版本</h2>
<ul>
<li>1.4.0 &ldquo;Andium&rdquo;</li>
</ul>
<h2 id="引言">引言</h2>
<p>在文章 <a href="https://leviathan.vip/2025/09/24/duckdb-executor/">DuckDB 查询执行器架构解析</a> 和 <a href="https://leviathan.vip/2025/05/19/duckdb-logical-plan/">DuckDB 源码分析 - Logical Plan 逻辑计划</a> 中, 我们梳理了 DuckDB 的查询执行流程和逻辑计划生成. 基表扫描在物理层统一为 <strong><code>PhysicalTableScan</code></strong> (顺序扫与索引扫都是如此); 当优化器选用 ART 时, 仍在该算子上走 <strong>Index Scan</strong> 路径 (表扫描初始化阶段先经 ART 得到 <code>row_id</code> 再取行), <code>EXPLAIN</code> 中会显示为 <strong>Index Scan</strong>, 而非单独的 <code>PhysicalIndexScan</code> 算子.</p>
<p>ART (Adaptive Radix Tree) 索引是 DuckDB 中用于加速高选择性查询和确保主键约束的核心数据结构.</p>
<p>MySQL 的存储引擎 InnoDB 是行存引擎, 使用的是 B+tree 索引结构, 索引即数据, 磁盘上的数据文件可以理解为 B+tree 索引结构的直接映射. DuckDB 的 ART 是分离式索引, 具有普遍的索引意义, ART 索引只存 row_id. 如果需要获取整行数据, 查询语句需要通过 ART 获取 row_id, 再通过 RowGroup 在列式的数据文件中查找具体的数据行.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="版本">版本</h2>
<ul>
<li>1.4.0 &ldquo;Andium&rdquo;</li>
</ul>
<h2 id="引言">引言</h2>
<p>在文章 <a href="https://leviathan.vip/2025/09/24/duckdb-executor/">DuckDB 查询执行器架构解析</a> 和 <a href="https://leviathan.vip/2025/05/19/duckdb-logical-plan/">DuckDB 源码分析 - Logical Plan 逻辑计划</a> 中, 我们梳理了 DuckDB 的查询执行流程和逻辑计划生成. 基表扫描在物理层统一为 <strong><code>PhysicalTableScan</code></strong> (顺序扫与索引扫都是如此); 当优化器选用 ART 时, 仍在该算子上走 <strong>Index Scan</strong> 路径 (表扫描初始化阶段先经 ART 得到 <code>row_id</code> 再取行), <code>EXPLAIN</code> 中会显示为 <strong>Index Scan</strong>, 而非单独的 <code>PhysicalIndexScan</code> 算子.</p>
<p>ART (Adaptive Radix Tree) 索引是 DuckDB 中用于加速高选择性查询和确保主键约束的核心数据结构.</p>
<p>MySQL 的存储引擎 InnoDB 是行存引擎, 使用的是 B+tree 索引结构, 索引即数据, 磁盘上的数据文件可以理解为 B+tree 索引结构的直接映射. DuckDB 的 ART 是分离式索引, 具有普遍的索引意义, ART 索引只存 row_id. 如果需要获取整行数据, 查询语句需要通过 ART 获取 row_id, 再通过 RowGroup 在列式的数据文件中查找具体的数据行.</p>
<p>列存场景里另一类常见加速是 <strong>按块/行组的统计信息</strong> (如 min-max、zonemap) 在表扫描时整块跳过; ART 则是 <strong>精确到 row_id</strong> 的辅助结构, 更适合高选择性点查、范围与主键/唯一约束。二者互补, 本文只讨论 ART.</p>
<p>关于 ART 索引的理论基础, 可以参考原始论文:</p>
<ul>
<li><a href="https://db.in.tum.de/~leis/papers/ART.pdf">The Adaptive Radix Tree: ARTful Indexing for Main-Memory Databases</a></li>
</ul>
<p>DuckDB 的官方也有关于 ART 索引的博客介绍:</p>
<ul>
<li><a href="https://duckdb.org/2022/07/27/art-storage">Persistent Storage of Adaptive Radix Trees (ART) in DuckDB</a></li>
</ul>
<p>下文侧重整体结构与关键路径, 细节请对照版本源码与调试自行展开.</p>
<h2 id="art-索引">ART 索引</h2>
<h3 id="adaptive-radix-tree">Adaptive Radix Tree</h3>
<p>ART (Adaptive Radix Tree) 是一种内存高效的基数树 (Radix Tree) 变体, 由 Leis et al. 在 2013 年提出. 与传统的基数树不同, ART 通过自适应节点大小来减少内存浪费, 同时保持 O(k) 的查找复杂度 (k 为键的长度).</p>
<h3 id="art-索引创建时机">ART 索引创建时机</h3>
<h4 id="create-index-语句解析">CREATE INDEX 语句解析</h4>
<p>当用户执行 <code>CREATE INDEX</code> 语句时, DuckDB 会经历以下流程来创建 ART 索引:</p>
<ol>
<li><strong>SQL 解析</strong>: <code>Transformer::TransformCreateIndex</code> (见 <code>transform_create_index.cpp</code>) 解析 CREATE INDEX 语句, 生成 <code>CreateIndexInfo</code></li>
<li><strong>绑定阶段</strong>: <code>IndexBinder::BindCreateIndex</code> 绑定索引定义, 验证表名和列名; catalog 侧由 <strong><code>DuckCatalog::BindCreateIndex</code></strong> 等接入</li>
<li><strong>逻辑计划</strong>: 生成 <code>LogicalCreateIndex</code> 节点</li>
<li><strong>物理计划</strong>: 转换为 <code>PhysicalCreateIndex</code> 算子</li>
</ol>
<h4 id="primary-key-和-unique-约束的自动创建">PRIMARY KEY 和 UNIQUE 约束的自动创建</h4>
<p>DuckDB 会在创建表时自动为 PRIMARY KEY 和 UNIQUE 约束创建 ART 索引. 这个过程发生在约束绑定阶段:</p>
<ol>
<li><strong>约束定义</strong>: <code>CREATE TABLE</code> 中的 PRIMARY KEY/UNIQUE 约束在 <code>Binder::BindCreateTable</code> 中被识别</li>
<li><strong>约束绑定</strong>: <code>Binder::BindConstraint</code> 处理约束, 创建 <code>BoundUniqueConstraint</code></li>
<li><strong>索引创建</strong>: 在 <strong><code>DuckTableEntry</code></strong> 构造表存储 <strong><code>DataTable</code></strong> 时, 对 UNIQUE/PRIMARY KEY 等约束调用 <strong><code>DataTable::AddIndex</code></strong> 创建对应的 ART 索引 (与约束绑定结果对应, 而非单独的 <code>TableStorage::CreateIndexes</code> 之类入口)</li>
</ol>
<h4 id="查询语句中的索引使用">查询语句中的索引使用</h4>
<p>当执行查询语句时, DuckDB 的查询优化器会评估是否可以使用已有的 ART 索引来加速查询:</p>
<ol>
<li><strong>索引选择</strong>: 优化器在生成物理计划时, 会检查查询条件是否匹配已有的索引 (如等值查询, 范围查询等)</li>
<li><strong>索引扫描</strong>: 若优化器选用索引, 仍为 <strong><code>PhysicalTableScan</code></strong> 配合 <strong>Index Scan</strong> 路径: 利用 ART 快速得到 <code>row_id</code>, 再通过 RowGroup 获取完整行数据 (<code>EXPLAIN</code> 中为 Index Scan)</li>
</ol>
<p>需要注意的是, DuckDB 不会在执行普通查询语句时自动创建新索引. 索引的创建需要通过显式的 <code>CREATE INDEX</code> 语句, 或者在表定义时通过 PRIMARY KEY/UNIQUE 约束自动创建. 在执行 <code>CREATE INDEX</code> 语句时, DuckDB 会通过 <code>PhysicalCreateIndex</code> 算子扫描表数据并调用 <code>ART::Build</code> 方法构建 ART 索引结构.</p>
<p>是否走 Index Scan 还取决于选择性、可用索引定义与代价估计; <strong>当前实现里对「索引扫描」的尝试主要针对单列 ART</strong> (复合/多列 ART 的 index scan 在表扫描初始化路径上尚未按传统「最左前缀」那套接好), 勿直接套用 InnoDB 复合索引经验. 排障时可用 <code>EXPLAIN</code> / <code>EXPLAIN ANALYZE</code> 对照计划是否走索引。代价模型与规则细节可单独成篇展开.</p>
<h3 id="art-索引结构">ART 索引结构</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ART</span> <span class="o">:</span> <span class="k">public</span> <span class="n">BoundIndex</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="k">friend</span> <span class="k">class</span> <span class="nc">Leaf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 扫描/查找. */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">bool</span> <span class="n">Scan</span><span class="p">(</span><span class="n">IndexScanState</span> <span class="o">&amp;</span><span class="n">state</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">max_count</span><span class="p">,</span> <span class="n">set</span><span class="o">&lt;</span><span class="n">row_t</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">row_ids</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 追加写. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">ErrorData</span> <span class="nf">Append</span><span class="p">(</span><span class="n">IndexLock</span> <span class="o">&amp;</span><span class="n">l</span><span class="p">,</span> <span class="n">DataChunk</span> <span class="o">&amp;</span><span class="n">chunk</span><span class="p">,</span> <span class="n">Vector</span> <span class="o">&amp;</span><span class="n">row_ids</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 插入写. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">ErrorData</span> <span class="nf">Insert</span><span class="p">(</span><span class="n">IndexLock</span> <span class="o">&amp;</span><span class="n">l</span><span class="p">,</span> <span class="n">DataChunk</span> <span class="o">&amp;</span><span class="n">chunk</span><span class="p">,</span> <span class="n">Vector</span> <span class="o">&amp;</span><span class="n">row_ids</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 删除. */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">void</span> <span class="nf">Delete</span><span class="p">(</span><span class="n">IndexLock</span> <span class="o">&amp;</span><span class="n">lock</span><span class="p">,</span> <span class="n">DataChunk</span> <span class="o">&amp;</span><span class="n">entries</span><span class="p">,</span> <span class="n">Vector</span> <span class="o">&amp;</span><span class="n">row_ids</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 构建 ART 索引. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">ARTConflictType</span> <span class="nf">Build</span><span class="p">(</span><span class="n">unsafe_vector</span><span class="o">&lt;</span><span class="n">ARTKey</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">keys</span><span class="p">,</span> <span class="n">unsafe_vector</span><span class="o">&lt;</span><span class="n">ARTKey</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">row_ids</span><span class="p">,</span> <span class="k">const</span> <span class="n">idx_t</span> <span class="n">row_count</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 将 ART 索引持久化到磁盘. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">IndexStorageInfo</span> <span class="nf">SerializeToDisk</span><span class="p">(</span><span class="n">QueryContext</span> <span class="n">context</span><span class="p">,</span> <span class="k">const</span> <span class="n">case_insensitive_map_t</span><span class="o">&lt;</span><span class="n">Value</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">options</span><span class="p">)</span> <span class="k">override</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p><code>SerializeToDisk</code> 负责把内存中的 ART 落盘; 与 checkpoint、重启后加载、以及 WAL 在崩溃恢复中的职责划分, 官方 <a href="https://duckdb.org/2022/07/27/art-storage">Persistent Storage of Adaptive Radix Trees (ART) in DuckDB</a> 有系统说明, 本文不展开具体文件布局.</p>
<h3 id="节点类型">节点类型</h3>
<p>在传统的 Radix 索引树中, 如果每一层都固定用 256 槽位 (Node256) 来表示&quot;一个字节的分支&quot;, 那么在分支很稀疏时会产生大量空位, 浪费内存并降低 cache 命中率. ART 的思路是: <strong>同一层节点根据&quot;当前分支数&quot;自适应选择不同布局</strong>.</p>
<p>DuckDB 的 ART 实现了完整的自适应基数树节点体系, 并且有所扩展, 下表列出了所有节点类型及其含义:</p>
<p>DuckDB 的 ART 里最核心的四类 &ldquo;自适应分支节点&rdquo; 是:</p>
<ol>
<li><strong>Node4</strong>: 分支数很少时使用.</li>
<li><strong>Node16</strong>: 分支数变多后扩容到 16.</li>
<li><strong>Node48</strong>: 用一个 256 大小的索引表把 byte 映射到 0..47 的紧凑 child 数组.</li>
<li><strong>Node256</strong>: 直接 256 槽位数组, 按 byte O(1) 访问.</li>
</ol>
<p>DuckDB 针对 ART 的节点类型做了扩展, 专门设置了 prefix 节点和存储 row_id 的节点.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* src/include/duckdb/execution/index/art/node.hpp */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 节点类型. */</span>
</span></span><span class="line"><span class="cl"><span class="k">enum</span> <span class="k">class</span> <span class="nc">NType</span> <span class="o">:</span> <span class="kt">uint8_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">PREFIX</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">LEAF</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">NODE_4</span> <span class="o">=</span> <span class="mi">3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">NODE_16</span> <span class="o">=</span> <span class="mi">4</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">NODE_48</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">NODE_256</span> <span class="o">=</span> <span class="mi">6</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">LEAF_INLINED</span> <span class="o">=</span> <span class="mi">7</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">NODE_7_LEAF</span> <span class="o">=</span> <span class="mi">8</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">NODE_15_LEAF</span> <span class="o">=</span> <span class="mi">9</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	<span class="n">NODE_256_LEAF</span> <span class="o">=</span> <span class="mi">10</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>LEAF_INLINED 用来存储完整的 row_id.</p>
<p>NODE_[X]_LEAF 是作为嵌套 ART 节点, 用于存储 row_id 的最后一个字节值. 在嵌套 ART 中, row_id 的前面字节通过 PREFIX 节点或分支节点存储, 最后一个字节存储在 NODE_X_LEAF 中 (X 表示容量: 7, 15 或 256), 以此来节省空间.</p>
<table>
  <thead>
      <tr>
          <th>节点类型</th>
          <th>枚举值</th>
          <th>具体含义</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>PREFIX</strong></td>
          <td><code>NType::PREFIX = 1</code></td>
          <td>路径压缩节点, 存储连续单分支路径的公共前缀字节, 减少树高和内存占用</td>
      </tr>
      <tr>
          <td><strong>LEAF</strong></td>
          <td><code>NType::LEAF = 2</code></td>
          <td>已弃用</td>
      </tr>
      <tr>
          <td><strong>NODE_4</strong></td>
          <td><code>NType::NODE_4 = 3</code></td>
          <td>分支数为 4 的节点</td>
      </tr>
      <tr>
          <td><strong>NODE_16</strong></td>
          <td><code>NType::NODE_16 = 4</code></td>
          <td>分支数为 16 的节点</td>
      </tr>
      <tr>
          <td><strong>NODE_48</strong></td>
          <td><code>NType::NODE_48 = 5</code></td>
          <td>分支数为 48 的节点</td>
      </tr>
      <tr>
          <td><strong>NODE_256</strong></td>
          <td><code>NType::NODE_256 = 6</code></td>
          <td>直接使用 256 槽位数组, 按 byte 值 O(1) 访问, 内存占用最大但查找最快</td>
      </tr>
      <tr>
          <td><strong>LEAF_INLINED</strong></td>
          <td><code>NType::LEAF_INLINED = 7</code></td>
          <td>内联叶子节点, 将单个 row_id 直接存储在 Node 指针中, 避免额外内存分配</td>
      </tr>
      <tr>
          <td><strong>NODE_7_LEAF</strong></td>
          <td><code>NType::NODE_7_LEAF = 8</code></td>
          <td>存储最多 7 个排序的字节值</td>
      </tr>
      <tr>
          <td><strong>NODE_15_LEAF</strong></td>
          <td><code>NType::NODE_15_LEAF = 9</code></td>
          <td>存储最多 15 个排序的字节值</td>
      </tr>
      <tr>
          <td><strong>NODE_256_LEAF</strong></td>
          <td><code>NType::NODE_256_LEAF = 10</code></td>
          <td>使用掩码的方式存储 row_id 最后一个字节</td>
      </tr>
  </tbody>
</table>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* 基础节点, Node 4 和 Node16 采用该结构. */</span>
</span></span><span class="line"><span class="cl"><span class="k">template</span> <span class="o">&lt;</span><span class="kt">uint8_t</span> <span class="n">CAPACITY</span><span class="p">,</span> <span class="n">NType</span> <span class="n">TYPE</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">BaseNode</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">friend</span> <span class="k">class</span> <span class="nc">Node4</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">friend</span> <span class="k">class</span> <span class="nc">Node16</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">friend</span> <span class="k">class</span> <span class="nc">Node48</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="n">BaseNode</span><span class="p">()</span> <span class="o">=</span> <span class="k">delete</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">BaseNode</span><span class="p">(</span><span class="k">const</span> <span class="n">BaseNode</span> <span class="o">&amp;</span><span class="p">)</span> <span class="o">=</span> <span class="k">delete</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">BaseNode</span> <span class="o">&amp;</span><span class="k">operator</span><span class="o">=</span><span class="p">(</span><span class="k">const</span> <span class="n">BaseNode</span> <span class="o">&amp;</span><span class="p">)</span> <span class="o">=</span> <span class="k">delete</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="kt">uint8_t</span> <span class="n">count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">uint8_t</span> <span class="n">key</span><span class="p">[</span><span class="n">CAPACITY</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">	<span class="n">Node</span> <span class="n">children</span><span class="p">[</span><span class="n">CAPACITY</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* Node48 节点结构. */</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Node48</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">friend</span> <span class="k">class</span> <span class="nc">Node16</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">friend</span> <span class="k">class</span> <span class="nc">Node256</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="k">constexpr</span> <span class="n">NType</span> <span class="n">NODE_48</span> <span class="o">=</span> <span class="n">NType</span><span class="o">::</span><span class="n">NODE_48</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="k">constexpr</span> <span class="kt">uint8_t</span> <span class="n">CAPACITY</span> <span class="o">=</span> <span class="mi">48</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="k">constexpr</span> <span class="kt">uint8_t</span> <span class="n">EMPTY_MARKER</span> <span class="o">=</span> <span class="mi">48</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="k">constexpr</span> <span class="kt">uint8_t</span> <span class="n">SHRINK_THRESHOLD</span> <span class="o">=</span> <span class="mi">12</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="kt">uint8_t</span> <span class="n">count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">uint8_t</span> <span class="n">child_index</span><span class="p">[</span><span class="n">Node256</span><span class="o">::</span><span class="n">CAPACITY</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">	<span class="n">Node</span> <span class="n">children</span><span class="p">[</span><span class="n">CAPACITY</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* Node256 节点结构. */</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Node256</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">friend</span> <span class="k">class</span> <span class="nc">Node48</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="k">constexpr</span> <span class="n">NType</span> <span class="n">NODE_256</span> <span class="o">=</span> <span class="n">NType</span><span class="o">::</span><span class="n">NODE_256</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="k">constexpr</span> <span class="kt">uint16_t</span> <span class="n">CAPACITY</span> <span class="o">=</span> <span class="mi">256</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="k">constexpr</span> <span class="kt">uint8_t</span> <span class="n">SHRINK_THRESHOLD</span> <span class="o">=</span> <span class="mi">36</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="n">Node256</span><span class="p">()</span> <span class="o">=</span> <span class="k">delete</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">Node256</span><span class="p">(</span><span class="k">const</span> <span class="n">Node256</span> <span class="o">&amp;</span><span class="p">)</span> <span class="o">=</span> <span class="k">delete</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">Node256</span> <span class="o">&amp;</span><span class="k">operator</span><span class="o">=</span><span class="p">(</span><span class="k">const</span> <span class="n">Node256</span> <span class="o">&amp;</span><span class="p">)</span> <span class="o">=</span> <span class="k">delete</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="kt">uint16_t</span> <span class="n">count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">Node</span> <span class="n">children</span><span class="p">[</span><span class="n">CAPACITY</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="art-接口代码导读-insert-与-scan">ART 接口代码导读: Insert 与 Scan</h2>
<p>这里只展开两个接口:</p>
<ul>
<li><strong>插入</strong>: <code>ART::Insert</code></li>
<li><strong>查找</strong>: <code>TryInitializeScan -&gt; Scan -&gt; SearchEqual -&gt; ARTOperator::Lookup</code></li>
</ul>
<h3 id="artkey-数据结构与含义">ARTKey: 数据结构与含义</h3>
<p>在 ART 里, 索引 key 的最小单位是 <code>ARTKey</code>, 是一段 <strong>按 Byte 可比较</strong> 的 buffer.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* src/include/duckdb/execution/index/art/art_key.hpp */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">ARTKey</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="n">idx_t</span> <span class="n">len</span><span class="p">;</span>       <span class="cm">/* key 的字节长度. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">data_ptr_t</span> <span class="n">data</span><span class="p">;</span> <span class="cm">/* key 的字节内容. */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><h3 id="1-insert-写入索引-artinsert--artappend">1) Insert: 写入索引 (<code>ART::Insert</code> / <code>ART::Append</code>)</h3>
<p><code>Insert</code> 把输入的数据编码成 ART key, 然后把真正的树操作交给 <code>ARTOperator::Insert</code>; 如果中途发生冲突, 再把已插入的部分回滚掉.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* src/execution/index/art/art.cpp */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ErrorData</span> <span class="n">ART</span><span class="o">::</span><span class="n">Insert</span><span class="p">(</span><span class="n">IndexLock</span> <span class="o">&amp;</span><span class="n">l</span><span class="p">,</span> <span class="n">DataChunk</span> <span class="o">&amp;</span><span class="n">chunk</span><span class="p">,</span> <span class="n">Vector</span> <span class="o">&amp;</span><span class="n">row_ids</span><span class="p">,</span> <span class="n">IndexAppendInfo</span> <span class="o">&amp;</span><span class="n">info</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">row_ids</span><span class="p">.</span><span class="n">GetType</span><span class="p">().</span><span class="n">InternalType</span><span class="p">()</span> <span class="o">==</span> <span class="n">ROW_TYPE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="n">row_count</span> <span class="o">=</span> <span class="n">chunk</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 1. 将 Chunk 里面的数据通过 GenerateKeyVectors 转换为 ART key. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">ArenaAllocator</span> <span class="nf">arena</span><span class="p">(</span><span class="n">BufferAllocator</span><span class="o">::</span><span class="n">Get</span><span class="p">(</span><span class="n">db</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">	<span class="n">unsafe_vector</span><span class="o">&lt;</span><span class="n">ARTKey</span><span class="o">&gt;</span> <span class="n">keys</span><span class="p">(</span><span class="n">row_count</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">unsafe_vector</span><span class="o">&lt;</span><span class="n">ARTKey</span><span class="o">&gt;</span> <span class="n">row_id_keys</span><span class="p">(</span><span class="n">row_count</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">GenerateKeyVectors</span><span class="p">(</span><span class="n">arena</span><span class="p">,</span> <span class="n">chunk</span><span class="p">,</span> <span class="n">row_ids</span><span class="p">,</span> <span class="n">keys</span><span class="p">,</span> <span class="n">row_id_keys</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="n">conflict_type</span> <span class="o">=</span> <span class="n">ARTConflictType</span><span class="o">::</span><span class="n">NO_CONFLICT</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">optional_idx</span> <span class="n">conflict_idx</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="n">was_empty</span> <span class="o">=</span> <span class="o">!</span><span class="n">tree</span><span class="p">.</span><span class="n">HasMetadata</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 2. 将 ART key 一次插入 ART tree. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">row_count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">keys</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">Empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="n">conflict_type</span> <span class="o">=</span> <span class="n">ARTOperator</span><span class="o">::</span><span class="n">Insert</span><span class="p">(</span><span class="n">arena</span><span class="p">,</span> <span class="o">*</span><span class="k">this</span><span class="p">,</span> <span class="n">tree</span><span class="p">,</span> <span class="n">keys</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="mi">0</span><span class="p">,</span> <span class="n">row_id_keys</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">GateStatus</span><span class="o">::</span><span class="n">GATE_NOT_SET</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		                                    <span class="n">DeleteIndexInfo</span><span class="p">(</span><span class="n">info</span><span class="p">.</span><span class="n">delete_indexes</span><span class="p">),</span> <span class="n">info</span><span class="p">.</span><span class="n">append_mode</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">conflict_type</span> <span class="o">!=</span> <span class="n">ARTConflictType</span><span class="o">::</span><span class="n">NO_CONFLICT</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">conflict_idx</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 3. 如果遇到了冲突, 需要回滚插入的 ART key. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">conflict_type</span> <span class="o">!=</span> <span class="n">ARTConflictType</span><span class="o">::</span><span class="n">NO_CONFLICT</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">conflict_idx</span><span class="p">.</span><span class="n">IsValid</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">conflict_idx</span><span class="p">.</span><span class="n">GetIndex</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="n">keys</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">Empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">			<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">tree</span><span class="p">.</span><span class="n">GetGateStatus</span><span class="p">()</span> <span class="o">==</span> <span class="n">GateStatus</span><span class="o">::</span><span class="n">GATE_NOT_SET</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="n">ARTOperator</span><span class="o">::</span><span class="n">Delete</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">,</span> <span class="n">tree</span><span class="p">,</span> <span class="n">keys</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">row_id_keys</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nf">ErrorData</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="art-gate-的使用">ART GATE 的使用</h3>
<p>ART GATE 用于处理<strong>同一个索引 key 对应多个 row_id</strong> 的场景 (例如非唯一索引中多个行具有相同的 key 值).</p>
<p>当同一个 key 需要存储多个 row_id 时, DuckDB 会创建一个标记为 <code>GateStatus::GATE_SET</code> 的 GATE 节点, 并在该节点下嵌套一个<strong>以 row_id 为 key 的 ART 子树</strong>来组织这些 row_id. 查找时, Iterator 会进入嵌套 ART 树遍历所有对应的 row_id.</p>
<p>ART 写入几个关键点:</p>
<ul>
<li>对整数/浮点等标量类型, 会用 <code>Radix::EncodeData&lt;T&gt;</code> 编码, 保证字节序比较的顺序与值大小一致.</li>
<li><code>VARCHAR</code> 会对 <code>\\x00/\\x01</code> 做转义并追加 <code>\\0</code> 结尾, 避免前缀比较/边界处理出现歧义.</li>
<li>复合索引 (多列 key) 会把每列编码结果 <code>Concat</code> 拼接成一个更长的字节串.</li>
<li>row_id 也会编码成 <code>ARTKey</code>, 需要时用 <code>GetRowId()</code> 反解回 <code>row_t</code>.</li>
<li>Node 根据插入/删除操作进行节点扩容/缩容操作, 比如 Node4 &lt;-&gt; Node16.</li>
</ul>
<p>索引键中的 <strong>NULL</strong> 如何在字节级编码、<code>IS NULL</code> 与索引对接, 以及复合键各列拼接顺序是否与 Binder 一致, 与 <code>GenerateKeyVectors</code> 同源, 需对照绑定与编码路径单独梳理.</p>
<h3 id="2-scan-查找-tryinitializescan--scan--searchequal">2) Scan 查找: <code>TryInitializeScan</code> / <code>Scan</code> / <code>SearchEqual</code></h3>
<p>DuckDB 里的 ART 查找入口是 <code>TryInitializeScan</code>/<code>Scan</code>:</p>
<ol>
<li><code>TryInitializeScan</code> 负责把 filter_expr 识别成 &ldquo;等值 / 下界 / 上界 / between&rdquo; 等形态, 并返回 scan state.</li>
<li><code>Scan</code> 根据 scan state 分发到 <code>SearchEqual</code> (等值) 或其它范围查找.</li>
<li><code>SearchEqual</code> 内部调用 <code>ARTOperator::Lookup</code> 找到 leaf, 然后用 <code>Iterator</code> 把 leaf 下的 row_id 扫出来.</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* src/execution/index/art/art.cpp */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">ART</span><span class="o">::</span><span class="n">SearchEqual</span><span class="p">(</span><span class="n">ARTKey</span> <span class="o">&amp;</span><span class="n">key</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">max_count</span><span class="p">,</span> <span class="n">set</span><span class="o">&lt;</span><span class="n">row_t</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">row_ids</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 沿着 ART 定位到 leaf 节点. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="n">leaf</span> <span class="o">=</span> <span class="n">ARTOperator</span><span class="o">::</span><span class="n">Lookup</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">,</span> <span class="n">tree</span><span class="p">,</span> <span class="n">key</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">leaf</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 使用 Iterator 扫描获取对应的 row_id. */</span> 
</span></span><span class="line"><span class="cl">	<span class="n">Iterator</span> <span class="nf">it</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">it</span><span class="p">.</span><span class="n">FindMinimum</span><span class="p">(</span><span class="o">*</span><span class="n">leaf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">ARTKey</span> <span class="n">empty_key</span> <span class="o">=</span> <span class="n">ARTKey</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="n">it</span><span class="p">.</span><span class="n">Scan</span><span class="p">(</span><span class="n">empty_key</span><span class="p">,</span> <span class="n">max_count</span><span class="p">,</span> <span class="n">row_ids</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="3-delete-与-insert-对称的路径">3) Delete: 与 Insert 对称的路径</h3>
<p>正文侧重 <code>Insert</code> 与 <code>Scan</code>. 删除侧入口为 <code>ART::Delete</code>, 树操作在 <code>ARTOperator::Delete</code> 中完成; 涉及 GATE 与嵌套 row_id 子树时, 与插入时 <code>GateStatus</code> 的演化对称。唯一约束冲突或批量失败时的回滚, 可与上文 <code>Insert</code> 中「冲突则对已插入键逐条 <code>ARTOperator::Delete</code>」对照阅读。若要逐步调试, 可与 <code>Insert</code> 在同一断点层级下成对跟进。</p>
<p><img src="/images/art.png" alt="ART 索引结构示例"></p>
<p>上图展示了 DuckDB ART 索引的三种典型场景:</p>
<ol>
<li><strong>单个 row_id</strong>: 当 key 唯一时, 直接通过 PREFIX 节点到达 LEAF_INLINED 节点存储 row_id.</li>
<li><strong>多个不同 key</strong>: 当多个 key 共享前缀时, 使用 Node4 节点进行分支, 每个分支继续通过 PREFIX 到达各自的叶子节点.</li>
<li><strong>相同 key 多个 row_id</strong>: 当同一个 key 对应多个 row_id 时, 使用 GATE 节点 (任何节点类型 + GATE_SET 标志) 作为中间层, 然后通过嵌套的 ART 结构来存储多个 row_id. 嵌套 ART 使用完整的 row_id 作为 key, 通过路径压缩, 前面的字节存储在 PREFIX 节点中, 最后一个字节存储在 NODE_X_LEAF 中 (可能是 Node7Leaf, Node15Leaf 或 Node256Leaf, 取决于 row_id 的数量).</li>
</ol>
<p>DuckDB 的 ART 索引只能检索出 row_id, 需要整行数据需要通过 RowGroup 获取.</p>
<h2 id="art-索引的并发问题">ART 索引的并发问题</h2>
<p>在 MySQL 8.0 中引入了 SX 锁来支持 B+tree 索引的读写优化, 虽然同时只能进行一个 SMO 操作, 但是读操作可以和写操作同时进行, 这种优化提高了整体读写性能, 对于大量即时读写更新的场景更加友好.</p>
<p>DuckDB 当前的 ART 索引的并发模式采用的是读写完全互斥, 读读操作也需要串行. 这个限制和 DuckDB 的并发访问模式有关 <a href="https://duckdb.org/docs/stable/connect/concurrency">DuckDB Concurrency</a>:</p>
<blockquote>
<p>DuckDB has two configurable options for concurrency:</p>
<ul>
<li>One process can both read and write to the database.</li>
<li>Multiple processes can read from the database, but no processes can write (access_mode = &lsquo;READ_ONLY&rsquo;).</li>
</ul>
</blockquote>
<p>在同一个进程中, 多个线程可以共享一套数据对象, 可以使用 MVCC + 乐观并发在一个进程里协调多个线程的读写操作, 不需要跨进程同步.</p>
<p>在多进程场景里要么都只读，要么在应用层用锁/队列保证 &ldquo;同一时刻只有一个进程在写&rdquo;.</p>
<p>而关于 ART 索引的并发问题, 读线程沿 ART 的边往下走，若写线程能同时改节点、分裂、合并，就需要：</p>
<ol>
<li>细粒度锁 / 版本号.</li>
<li>读者也串行化 (读读互斥)</li>
</ol>
<p>DuckDB 在 ART 上选了后者, 整体的实现更加简单, 但是性能会有损耗, DuckDB 在博客里也解释了这个问题:</p>
<blockquote>
<p>DuckDB is optimized for bulk operations, so executing many small transactions is not a primary design goal.</p>
</blockquote>
<p>DuckDB 面向大批量的 workload, 所以粗粒度的锁模型是可以接受的.</p>
<p>ART 常驻内存, 占用随键分布与节点类型选择而变; 极端倾斜或宽键可能带来节点膨胀, 需结合 workload 观察。索引维护命令与版本差异以官方文档为准, 并与上节多进程只读 / 单进程写模型一并理解。</p>
<h2 id="关于-return-stdmove">关于 return std::move()</h2>
<p>DuckDB 的 ART 代码中大量使用了 <code>return std::move()</code>, 但其中很多是<strong>不必要的</strong>, 甚至可能导致性能退化:</p>
<h3 id="c-rvonrvo-机制">C++ RVO/NRVO 机制</h3>
<p>C++ 编译器会自动应用两种优化:</p>
<ol>
<li><strong>RVO (Return Value Optimization)</strong>: 对于临时对象, 直接在返回位置构造</li>
<li><strong>NRVO (Named Return Value Optimization)</strong>: 对于局部变量, 直接在返回位置构造, 避免拷贝/移动</li>
</ol>
<p>当返回局部变量时, 编译器会自动应用 NRVO, <strong>不需要</strong> <code>std::move()</code>, 这些局部变量会被编译器自动优化 (NRVO), <code>std::move()</code> 反而可能<strong>阻止 NRVO</strong>, 强制使用移动构造反而造成性能回退.</p>
<h2 id="总结">总结</h2>
<p>通过自适应节点大小, ART index 在内存效率和查找性能之间取得了良好的平衡, 特别适合 AP 列存数据库的场景.</p>
<p>DuckDB 的 ART 索引与 MySQL InnoDB 的 B+tree 索引在设计上存在比较明显的差异, 这是行存引擎和列存引擎的索引需求差异导致的:</p>
<blockquote>
<p>ART 的设计面向内存, 适合内存数据库和 OLAP 场景; B+tree 适合磁盘数据库和 OLTP 场景. InnoDB 发展多年, MySQL 经历几次大版本的重构, 在 InnoDB 整体性能多次迭代的情况下, 其 B+tree 整体的工程实现都较 ART 更为复杂.</p>
</blockquote>
<p>LLM 辅助编程与检索已能高效覆盖「逐行读懂片段」的需求, 源码类文章更合适的重心是结构、调用链与设计取舍, 而非替代阅读器复述每一行.</p>
]]></content>
  </entry><entry>
    <title>DuckDB 的向量化执行</title>
    <link href="https://leviathan.vip/2025/11/22/duckdb-vectorization/" />
    <id>https://leviathan.vip/2025/11/22/duckdb-vectorization/</id>
    <updated>2025-11-22T14:54:10Z</updated>
    <summary type="html"><![CDATA[<h2 id="版本">版本</h2>
<p>v1.4-andium</p>
<h2 id="引言">引言</h2>
<p>我们在 <a href="https://leviathan.vip/2025/09/24/duckdb-executor/">DuckDB 查询执行器架构解析</a> 中梳理了执行器如何把物理计划切成 Pipeline、并在多线程环境中调度。那篇文章更多是站在查询 SQL 的的视角看 push-based 的运作方式，而真正让执行器高效的核心是每个算子都围绕 DataChunk 做向量化处理, 本篇就从执行器最基础的 Source 算子 <code>TableScan</code> 切入, 理解 DuckDB 的向量化执行。</p>
<h2 id="tablescan">TableScan</h2>
<p>在物理计划中，<code>TableScan</code> 是最典型的 Source 算子: 它主要来进行扫表的操作, 来进行数据的读取操作, 没有输入但会批量产出 <code>DataChunk</code> 供整条 Pipeline 消费.</p>
<p>在 <code>MetaPipeline</code> 中，<code>TableScan</code> 不会创建新的子 Pipeline，它只是被加入当前 Pipeline 的 Source 列表, 根据 DuckDB 的执行器框架的算子原子, <code>TableScan</code> 是可以被并行操作的.</p>
<h2 id="datachunk-与-vector">DataChunk 与 Vector</h2>
<h3 id="datachunk-的结构">DataChunk 的结构</h3>
<p>在 DuckDB 的实现中, <code>DataChunk</code> 就是每一批读取数据单元的抽象描述 <code>(src/include/duckdb/common/types/data_chunk.hpp)</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DataChunk</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 批次里每一列都是一个 Vector. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">vector</span><span class="o">&lt;</span><span class="n">Vector</span><span class="o">&gt;</span> <span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 当前批次行数/列数. */</span>
</span></span><span class="line"><span class="cl">	<span class="kr">inline</span> <span class="n">idx_t</span> <span class="nf">size</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">count</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="kr">inline</span> <span class="n">idx_t</span> <span class="nf">ColumnCount</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">data</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="kr">inline</span> <span class="kt">void</span> <span class="nf">SetCardinality</span><span class="p">(</span><span class="n">idx_t</span> <span class="n">count_p</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">count</span> <span class="o">=</span> <span class="n">count_p</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 初始化批次并预分配 STANDARD_VECTOR_SIZE=2048 块. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">DUCKDB_API</span> <span class="kt">void</span> <span class="nf">Initialize</span><span class="p">(</span><span class="n">ClientContext</span> <span class="o">&amp;</span><span class="n">context</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	                           <span class="k">const</span> <span class="n">vector</span><span class="o">&lt;</span><span class="n">LogicalType</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">types</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	                           <span class="n">idx_t</span> <span class="n">capacity</span> <span class="o">=</span> <span class="n">STANDARD_VECTOR_SIZE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="cm">/* 让当前批次引用另一批次的部分列. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">DUCKDB_API</span> <span class="kt">void</span> <span class="nf">ReferenceColumns</span><span class="p">(</span><span class="n">DataChunk</span> <span class="o">&amp;</span><span class="n">other</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	                                 <span class="k">const</span> <span class="n">vector</span><span class="o">&lt;</span><span class="n">column_t</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">column_ids</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="cm">/* 基于 SelectionVector 对批次做切片. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">DUCKDB_API</span> <span class="kt">void</span> <span class="nf">Slice</span><span class="p">(</span><span class="k">const</span> <span class="n">SelectionVector</span> <span class="o">&amp;</span><span class="n">sel_vector</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">count</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="cm">/* 重置批次, 复用底层 VectorCache. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">DUCKDB_API</span> <span class="kt">void</span> <span class="nf">Reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 当前批次行数与预分配容量. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">idx_t</span> <span class="n">count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">idx_t</span> <span class="n">capacity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p><code>Initialize</code> 调用时，每个列向量背后都会绑定一个 <code>VectorCacheBuffer</code>: 向量列对应一块可以容纳 <code>STANDARD_VECTOR_SIZE</code>（默认 2048）行的连续内存; LIST/ARRAY/STRUCT 等嵌套列还会递归地为子列准备自己的缓存和辅助 buffer.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="版本">版本</h2>
<p>v1.4-andium</p>
<h2 id="引言">引言</h2>
<p>我们在 <a href="https://leviathan.vip/2025/09/24/duckdb-executor/">DuckDB 查询执行器架构解析</a> 中梳理了执行器如何把物理计划切成 Pipeline、并在多线程环境中调度。那篇文章更多是站在查询 SQL 的的视角看 push-based 的运作方式，而真正让执行器高效的核心是每个算子都围绕 DataChunk 做向量化处理, 本篇就从执行器最基础的 Source 算子 <code>TableScan</code> 切入, 理解 DuckDB 的向量化执行。</p>
<h2 id="tablescan">TableScan</h2>
<p>在物理计划中，<code>TableScan</code> 是最典型的 Source 算子: 它主要来进行扫表的操作, 来进行数据的读取操作, 没有输入但会批量产出 <code>DataChunk</code> 供整条 Pipeline 消费.</p>
<p>在 <code>MetaPipeline</code> 中，<code>TableScan</code> 不会创建新的子 Pipeline，它只是被加入当前 Pipeline 的 Source 列表, 根据 DuckDB 的执行器框架的算子原子, <code>TableScan</code> 是可以被并行操作的.</p>
<h2 id="datachunk-与-vector">DataChunk 与 Vector</h2>
<h3 id="datachunk-的结构">DataChunk 的结构</h3>
<p>在 DuckDB 的实现中, <code>DataChunk</code> 就是每一批读取数据单元的抽象描述 <code>(src/include/duckdb/common/types/data_chunk.hpp)</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">DataChunk</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 批次里每一列都是一个 Vector. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">vector</span><span class="o">&lt;</span><span class="n">Vector</span><span class="o">&gt;</span> <span class="n">data</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 当前批次行数/列数. */</span>
</span></span><span class="line"><span class="cl">	<span class="kr">inline</span> <span class="n">idx_t</span> <span class="nf">size</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">count</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="kr">inline</span> <span class="n">idx_t</span> <span class="nf">ColumnCount</span><span class="p">()</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">data</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="kr">inline</span> <span class="kt">void</span> <span class="nf">SetCardinality</span><span class="p">(</span><span class="n">idx_t</span> <span class="n">count_p</span><span class="p">)</span> <span class="p">{</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">count</span> <span class="o">=</span> <span class="n">count_p</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 初始化批次并预分配 STANDARD_VECTOR_SIZE=2048 块. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">DUCKDB_API</span> <span class="kt">void</span> <span class="nf">Initialize</span><span class="p">(</span><span class="n">ClientContext</span> <span class="o">&amp;</span><span class="n">context</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	                           <span class="k">const</span> <span class="n">vector</span><span class="o">&lt;</span><span class="n">LogicalType</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">types</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	                           <span class="n">idx_t</span> <span class="n">capacity</span> <span class="o">=</span> <span class="n">STANDARD_VECTOR_SIZE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="cm">/* 让当前批次引用另一批次的部分列. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">DUCKDB_API</span> <span class="kt">void</span> <span class="nf">ReferenceColumns</span><span class="p">(</span><span class="n">DataChunk</span> <span class="o">&amp;</span><span class="n">other</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	                                 <span class="k">const</span> <span class="n">vector</span><span class="o">&lt;</span><span class="n">column_t</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">column_ids</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="cm">/* 基于 SelectionVector 对批次做切片. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">DUCKDB_API</span> <span class="kt">void</span> <span class="nf">Slice</span><span class="p">(</span><span class="k">const</span> <span class="n">SelectionVector</span> <span class="o">&amp;</span><span class="n">sel_vector</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">count</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">	<span class="cm">/* 重置批次, 复用底层 VectorCache. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">DUCKDB_API</span> <span class="kt">void</span> <span class="nf">Reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 当前批次行数与预分配容量. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">idx_t</span> <span class="n">count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">idx_t</span> <span class="n">capacity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p><code>Initialize</code> 调用时，每个列向量背后都会绑定一个 <code>VectorCacheBuffer</code>: 向量列对应一块可以容纳 <code>STANDARD_VECTOR_SIZE</code>（默认 2048）行的连续内存; LIST/ARRAY/STRUCT 等嵌套列还会递归地为子列准备自己的缓存和辅助 buffer.</p>
<p><code>vector&lt;Vector&gt; data</code> 里的每个元素本质上就是 &ldquo;指向一个固定长度的内存缓冲区&rdquo;, TableScan 每次扫描就是把这些缓冲区写满再向上游推送.</p>
<h3 id="vector-的结构">Vector 的结构</h3>
<p>DuckDB 中有几种不同的向量类型，每种类型都有其特定的优化目的:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">enum</span> <span class="k">class</span> <span class="nc">VectorType</span> <span class="o">:</span> <span class="kt">uint8_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">FLAT_VECTOR</span><span class="p">,</span>       <span class="c1">// Flat vectors represent a standard uncompressed vector
</span></span></span><span class="line"><span class="cl">	<span class="n">FSST_VECTOR</span><span class="p">,</span>       <span class="c1">// Contains string data compressed with FSST
</span></span></span><span class="line"><span class="cl">	<span class="n">CONSTANT_VECTOR</span><span class="p">,</span>   <span class="c1">// Constant vector represents a single constant
</span></span></span><span class="line"><span class="cl">	<span class="n">DICTIONARY_VECTOR</span><span class="p">,</span> <span class="c1">// Dictionary vector represents a selection vector on top of another vector
</span></span></span><span class="line"><span class="cl">	<span class="n">SEQUENCE_VECTOR</span>    <span class="c1">// Sequence vector represents a sequence with a start point and an increment
</span></span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><ul>
<li>
<p>FLAT_VECTOR: 标准的向量类型，数据在内存中连续存储，没有压缩</p>
</li>
<li>
<p>FSST_VECTOR: 使用了 Fast Static Symbol Table 压缩算法的向量，这个向量专门用来存储字符串数据. FSST 压缩算法也是 DuckDB 内部成员发表的压缩.</p>
</li>
<li>
<p>CONSTANT_VECTOR: 常量向量, 如果某一列值都是相等的，会直接使用常量向量, 常量向量预定义了一个大小依然是 STANDARD_VECTOR_SIZE 的向量:</p>
</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">static</span> <span class="k">const</span> <span class="n">sel_t</span> <span class="n">ZERO_VECTOR</span><span class="p">[</span><span class="n">STANDARD_VECTOR_SIZE</span><span class="p">];</span></span></span></code></pre></div><p>所有的元素都引用到位置 0, 避免重复创建向量.</p>
<ul>
<li>
<p>DICTIONARY_VECTOR: 字典向量, 通过 SelectionVector 引用另一个向量的数据, 实现零拷贝.</p>
</li>
<li>
<p>SEQUENCE_VECTOR: SEQUENCE_VECTOR 向量是用 3 个元数据来表示任意长度的等差数列, DuckDB 的 <a href="https://duckdb.org/docs/stable/sql/functions/list#rangestart-stop-step">range 函数</a> 需要使用这个向量:</p>
</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">Vector</span><span class="o">::</span><span class="n">Sequence</span><span class="p">(</span><span class="kt">int64_t</span> <span class="n">start</span><span class="p">,</span> <span class="kt">int64_t</span> <span class="n">increment</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">count</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">this</span><span class="o">-&gt;</span><span class="n">vector_type</span> <span class="o">=</span> <span class="n">VectorType</span><span class="o">::</span><span class="n">SEQUENCE_VECTOR</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">this</span><span class="o">-&gt;</span><span class="n">buffer</span> <span class="o">=</span> <span class="n">make_buffer</span><span class="o">&lt;</span><span class="n">VectorBuffer</span><span class="o">&gt;</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">int64_t</span><span class="p">)</span> <span class="o">*</span> <span class="mi">3</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">auto</span> <span class="n">data</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="kt">int64_t</span> <span class="o">*&gt;</span><span class="p">(</span><span class="n">buffer</span><span class="o">-&gt;</span><span class="n">GetData</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">start</span><span class="p">;</span>            <span class="cm">/* 起始值. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">increment</span><span class="p">;</span>        <span class="cm">/* 增量. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="kt">int64_t</span><span class="p">(</span><span class="n">count</span><span class="p">);</span>   <span class="cm">/* 元素个数. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">validity</span><span class="p">.</span><span class="n">Reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">auxiliary</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>DuckDB 会在运行时根据数据特征和操作场景动态选择最优的向量类型.</p>
<h3 id="selectionvector-的延迟物化">SelectionVector 的延迟物化</h3>
<p>DuckDB 向量化执行的核心优化之一是利用 SelectionVector 实现延迟物化，避免不必要的内存拷贝.</p>
<p>在 DataChunk 上 &ldquo;记录哪些行有效&rdquo; 是 <code>SelectionVector</code> 的职责. 它本质上是一个 <code>sel_t*</code> 索引数组,每一位代表当前 DataChunk 的某一行.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">SelectionVector</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">SelectionVector</span><span class="p">()</span> <span class="o">:</span> <span class="n">sel_vector</span><span class="p">(</span><span class="k">nullptr</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">	<span class="k">explicit</span> <span class="nf">SelectionVector</span><span class="p">(</span><span class="n">idx_t</span> <span class="n">count</span><span class="p">)</span> <span class="p">{</span> <span class="n">Initialize</span><span class="p">(</span><span class="n">count</span><span class="p">);</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 初始化. */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">void</span> <span class="nf">Initialize</span><span class="p">(</span><span class="n">idx_t</span> <span class="n">count</span> <span class="o">=</span> <span class="n">STANDARD_VECTOR_SIZE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="kt">void</span> <span class="nf">Initialize</span><span class="p">(</span><span class="n">sel_t</span> <span class="o">*</span><span class="n">sel</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 写/读某个位置的行号. */</span>
</span></span><span class="line"><span class="cl">	<span class="kr">inline</span> <span class="kt">void</span> <span class="nf">set_index</span><span class="p">(</span><span class="n">idx_t</span> <span class="n">idx</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">loc</span><span class="p">)</span> <span class="p">{</span> <span class="n">sel_vector</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">=</span> <span class="n">UnsafeNumericCast</span><span class="o">&lt;</span><span class="n">sel_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">loc</span><span class="p">);</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="kr">inline</span> <span class="n">idx_t</span> <span class="nf">get_index</span><span class="p">(</span><span class="n">idx_t</span> <span class="n">idx</span><span class="p">)</span> <span class="k">const</span> <span class="p">{</span> <span class="k">return</span> <span class="n">sel_vector</span> <span class="o">?</span> <span class="n">sel_vector</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">:</span> <span class="n">idx</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="n">sel_t</span> <span class="o">*</span><span class="n">sel_vector</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>如果每个算子在处理后都需要复制满足条件的数据行, 这将导致内存带宽的浪费, 频繁的内存拷贝也会消耗大量的 CPU 影响整体的性能.</p>
<p>DuckDB 的 SelectionVector 机制通过解决这个问题：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="cm">/* 假设有一个包含 2048 行的 DataChunk. */</span>
</span></span><span class="line"><span class="cl"><span class="n">DataChunk</span> <span class="n">chunk</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">chunk</span><span class="p">.</span><span class="n">Initialize</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="p">{</span><span class="n">LogicalType</span><span class="o">::</span><span class="n">INTEGER</span><span class="p">,</span> <span class="n">LogicalType</span><span class="o">::</span><span class="n">VARCHAR</span><span class="p">},</span> <span class="mi">2048</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* TableScan 填充数据后, 所有行都有效：row_ids = [0, 1, 2, ..., 2047]. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 第一个 Filter: WHERE status = &#39;VALID&#39;, 不复制数据，只创建 SelectionVector 记录满足条件的行号. */</span>
</span></span><span class="line"><span class="cl"><span class="n">SelectionVector</span> <span class="nf">sel1</span><span class="p">(</span><span class="mi">800</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">sel1</span><span class="p">.</span><span class="n">set_index</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">sel1</span><span class="p">.</span><span class="n">set_index</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">7</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">sel1</span><span class="p">.</span><span class="n">set_index</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">12</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 第二个 Filter: WHERE amount &gt; 1000, 基于 sel1 进一步过滤，生成 sel2. */</span>
</span></span><span class="line"><span class="cl"><span class="n">SelectionVector</span> <span class="nf">sel2</span><span class="p">(</span><span class="mi">800</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">idx_t</span> <span class="n">sel2_count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 遍历 sel1 中的所有有效行, 记录满足第二个 Where 条件的行. */</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">800</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">auto</span> <span class="n">row_idx</span> <span class="o">=</span> <span class="n">sel1</span><span class="p">.</span><span class="n">get_index</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">amount_data</span><span class="p">[</span><span class="n">row_idx</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">1000</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">sel2</span><span class="p">.</span><span class="n">set_index</span><span class="p">(</span><span class="n">sel2_count</span><span class="o">++</span><span class="p">,</span> <span class="n">row_idx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 假设最终 200 行同时满足两个条件: sel2 = [7, 9, 12, ...] (200 个索引，都是原始 DataChunk 的行号). */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* Aggregate 算子读取数据时, 通过 SelectionVector 获取实际行号只访问有效行. */</span></span></span></code></pre></div><h2 id="tablescan-的扫描准备">TableScan 的扫描准备</h2>
<p>在进入状态管理之前，先看 DuckDB 内置表函数 <code>TableScanFunction::GetFunction()</code>（<code>src/function/table/table_scan.cpp</code>）。它返回一个 <code>TableFunction</code> 对象，里面挂着一系列函数指针，驱动整个表扫描生命周期：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">TableFunction</span> <span class="n">TableScanFunction</span><span class="o">::</span><span class="n">GetFunction</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">TableFunction</span> <span class="nf">scan_function</span><span class="p">(</span><span class="s">&#34;seq_scan&#34;</span><span class="p">,</span> <span class="p">{},</span> <span class="n">TableScanFunc</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">init_local</span> <span class="o">=</span> <span class="n">TableScanInitLocal</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">init_global</span> <span class="o">=</span> <span class="n">TableScanInitGlobal</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">statistics</span> <span class="o">=</span> <span class="n">TableScanStatistics</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">dependency</span> <span class="o">=</span> <span class="n">TableScanDependency</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">cardinality</span> <span class="o">=</span> <span class="n">TableScanCardinality</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">projection_pushdown</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">filter_pushdown</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">filter_prune</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">sampling_pushdown</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">late_materialization</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">serialize</span> <span class="o">=</span> <span class="n">TableScanSerialize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">deserialize</span> <span class="o">=</span> <span class="n">TableScanDeserialize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">pushdown_expression</span> <span class="o">=</span> <span class="n">TableScanPushdownExpression</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">get_virtual_columns</span> <span class="o">=</span> <span class="n">TableScanGetVirtualColumns</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">get_row_id_columns</span> <span class="o">=</span> <span class="n">TableScanGetRowIdColumns</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">scan_function</span><span class="p">.</span><span class="n">set_scan_order</span> <span class="o">=</span> <span class="n">SetScanOrder</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="n">scan_function</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>可以把它理解成一张“函数指针表”：<code>TableScanFunc</code> 负责真正读取数据，<code>TableScanInitLocal/TableScanInitGlobal</code> 生成 Local/GlobalState, 其他回调负责统计以及各种 pushdown , 裁剪特性. 这种写法和 Linux 内核中的操作表类似，执行器只要在合适的阶段调用相应回调, 就能驱动任意表函数.</p>
<p>理解 TableScan 的向量化，需要清楚在每个线程上维护的状态：</p>
<ul>
<li>
<p><code>TableScanGlobalSourceState</code>: <code>PhysicalTableScan</code> 在执行器中的全局状态，用来管理 table function 的 <code>init_global</code> 结果、动态过滤器、in/out 参数.</p>
</li>
<li>
<p><code>TableScanLocalState</code>: table function 内部为每个线程创建的私有上下文，持有 <code>TableScanState scan_state</code>（记录当前 RowGroup、偏移、过滤信息）、<code>DataChunk all_columns</code>（可选，缓存包含过滤列在内的全列数据）以及扫描过程中用到的一些本地缓冲信息.</p>
</li>
</ul>
<p>基于 LocalState，每个线程就可以在 <code>GetDataInternal</code> 里重复使用 <code>scan_state</code> 和 <code>all_columns</code>，批量的把 RowGroup 数据搬到 DataChunk 里，而不必为每次扫描重新分配/初始化临时结构.</p>
<h2 id="tablescan-的向量化读取">TableScan 的向量化读取</h2>
<p>状态准备完毕后，真正的批量扫描发生在 <code>TableScanFunc</code> (<code>src/function/table/table_scan.cpp</code>).</p>
<p>先明确一下调用链：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">PipelineExecutor</span><span class="o">::</span><span class="n">FetchFromSource</span>      <span class="c1">// 执行器在 Pipline 中 拉取数据
</span></span></span><span class="line"><span class="cl">    <span class="o">-&gt;</span> <span class="n">PhysicalTableScan</span><span class="o">::</span><span class="n">GetData</span><span class="p">(...)</span> <span class="c1">// 调用算子的 Source 接口
</span></span></span><span class="line"><span class="cl">        <span class="o">-&gt;</span> <span class="n">PhysicalTableScan</span><span class="o">::</span><span class="n">GetDataInternal</span><span class="p">(...)</span>
</span></span><span class="line"><span class="cl">            <span class="o">-&gt;</span> <span class="n">TableFunctionInput</span> <span class="n">data</span><span class="p">(...)</span>
</span></span><span class="line"><span class="cl">            <span class="o">-&gt;</span> <span class="n">function</span><span class="p">.</span><span class="n">function</span><span class="p">(...)</span> <span class="c1">// TableScanFunc，真正扫描数据
</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">TableScanFunc</span><span class="p">(</span><span class="n">ClientContext</span> <span class="o">&amp;</span><span class="n">context</span><span class="p">,</span> <span class="n">TableFunctionInput</span> <span class="o">&amp;</span><span class="n">data_p</span><span class="p">,</span> <span class="n">DataChunk</span> <span class="o">&amp;</span><span class="n">output</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 拿到当前线程的 LocalState（包含 scan_state、all_columns 等临时结构） */</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="o">&amp;</span><span class="n">l_state</span> <span class="o">=</span> <span class="n">data_p</span><span class="p">.</span><span class="n">local_state</span><span class="o">-&gt;</span><span class="n">Cast</span><span class="o">&lt;</span><span class="n">TableScanLocalState</span><span class="o">&gt;</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">	<span class="n">l_state</span><span class="p">.</span><span class="n">scan_state</span><span class="p">.</span><span class="n">options</span><span class="p">.</span><span class="n">force_fetch_row</span> <span class="o">=</span> <span class="n">ClientConfig</span><span class="o">::</span><span class="n">GetConfig</span><span class="p">(</span><span class="n">context</span><span class="p">).</span><span class="n">force_fetch_row</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">do</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* 查询被中断，随时退出扫描循环 */</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">context</span><span class="p">.</span><span class="n">interrupted</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">throw</span> <span class="n">InterruptException</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">bind_data</span><span class="p">.</span><span class="n">is_create_index</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="cm">/* CREATE INDEX 特殊路径：只扫描已提交且未永久删除的行 */</span>
</span></span><span class="line"><span class="cl">			<span class="n">storage</span><span class="p">.</span><span class="n">CreateIndexScan</span><span class="p">(</span><span class="n">l_state</span><span class="p">.</span><span class="n">scan_state</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			                        <span class="n">TableScanType</span><span class="o">::</span><span class="n">TABLE_SCAN_COMMITTED_ROWS_OMIT_PERMANENTLY_DELETED</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">CanRemoveFilterColumns</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="cm">/* 启用 late materialization：先扫描到 all_columns，再引用需要的列 */</span>
</span></span><span class="line"><span class="cl">			<span class="n">l_state</span><span class="p">.</span><span class="n">all_columns</span><span class="p">.</span><span class="n">Reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">			<span class="n">storage</span><span class="p">.</span><span class="n">Scan</span><span class="p">(</span><span class="n">tx</span><span class="p">,</span> <span class="n">l_state</span><span class="p">.</span><span class="n">all_columns</span><span class="p">,</span> <span class="n">l_state</span><span class="p">.</span><span class="n">scan_state</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="n">output</span><span class="p">.</span><span class="n">ReferenceColumns</span><span class="p">(</span><span class="n">l_state</span><span class="p">.</span><span class="n">all_columns</span><span class="p">,</span> <span class="n">projection_ids</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="cm">/* 直接扫描到输出 DataChunk */</span>
</span></span><span class="line"><span class="cl">			<span class="n">storage</span><span class="p">.</span><span class="n">Scan</span><span class="p">(</span><span class="n">tx</span><span class="p">,</span> <span class="n">output</span><span class="p">,</span> <span class="n">l_state</span><span class="p">.</span><span class="n">scan_state</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">output</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span><span class="p">;</span> <span class="cm">/* 本次批次已有数据，交给执行器 */</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 当前 RowGroup 耗尽，向存储层申请下一个 morsel（可能来自 local storage） */</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">next</span> <span class="o">=</span> <span class="n">storage</span><span class="p">.</span><span class="n">NextParallelScan</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">state</span><span class="p">,</span> <span class="n">l_state</span><span class="p">.</span><span class="n">scan_state</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">next</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span><span class="p">;</span> <span class="cm">/* 没有新的 RowGroup，扫描结束 */</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>TableScanFunc 内的 <code>storage.Scan(...)</code>/<code>storage.CreateIndexScan(...)</code> 最终都会落到存储层的 <code>RowGroup::TemplatedScan</code>. 这层主要做的是 &ldquo;把当前 RowGroup 的一块数据搬进 DataChunk&rdquo;, 和向量化框架本身关系不大：它会按 <code>STANDARD_VECTOR_SIZE</code> 决定本批次的行数，先检查采样/Zonemap/可见性生成 selection vector，必要时预取数据块；若无过滤则整列扫描，有过滤时先扫带过滤列生成 sel，再用 sel 抓取其余列；最后设置批次行数并推进到下一个 vector 块.</p>
<h2 id="duckdb-的向量操作">DuckDB 的向量操作</h2>
<p>DuckDB 真正的向量化操作并没有手写 SIMD 指令, 而是依赖编译器自动向量化:</p>
<p>在 Release 编译模式下使用 -O3 默认包含了自动向量化 (比如 GCC 目前使用 <code>-O3</code> 就默认启动了 <code>-ftree-vectorize</code>), 具体细节可以参考文章 <a href="https://gcc.gnu.org/projects/tree-ssa/vectorization.html">Auto-vectorization in GCC</a></p>
<p>大部分向量化操作通过 <code>struct VectorOperations</code> 提供统一接口, 底层使用 <code>UnaryExecutor</code>、<code>BinaryExecutor</code> 等模板类作为 Executor 来具体实现实际的向量化执行:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">VectorOperations <span class="o">(</span>公共接口<span class="o">)</span>
</span></span><span class="line"><span class="cl">    ↓ 调用
</span></span><span class="line"><span class="cl">Executor 模板类 <span class="o">(</span>具体实现<span class="o">)</span>
</span></span><span class="line"><span class="cl">    ↓ 调用
</span></span><span class="line"><span class="cl">ExecuteLoop <span class="o">(</span>循环, 借助编译器向量化<span class="o">)</span></span></span></code></pre></div><p>比如判断相等的操作:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* 公共接口. */</span>
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">VectorOperations</span><span class="o">::</span><span class="n">Equals</span><span class="p">(</span><span class="n">Vector</span> <span class="o">&amp;</span><span class="n">left</span><span class="p">,</span> <span class="n">Vector</span> <span class="o">&amp;</span><span class="n">right</span><span class="p">,</span> <span class="n">Vector</span> <span class="o">&amp;</span><span class="n">result</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">count</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 调用ComparisonExecutor 执行 duckdb::Equals 的 Operation 来做实际的相等比较. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">ComparisonExecutor</span><span class="o">::</span><span class="n">Execute</span><span class="o">&lt;</span><span class="n">duckdb</span><span class="o">::</span><span class="n">Equals</span><span class="o">&gt;</span><span class="p">(</span><span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">,</span> <span class="n">result</span><span class="p">,</span> <span class="n">count</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>ComparisonExecutor::Execute</code> 最后调用 <code>BinaryExecutor::ExecuteGenericLoop()</code> 进行 for 循环来依次判断是否相等:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">template</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">LEFT_TYPE</span><span class="p">,</span> <span class="k">class</span> <span class="nc">RIGHT_TYPE</span><span class="p">,</span> <span class="k">class</span> <span class="nc">RESULT_TYPE</span><span class="p">,</span> <span class="k">class</span> <span class="nc">OPWRAPPER</span><span class="p">,</span> <span class="k">class</span> <span class="nc">OP</span><span class="p">,</span> <span class="k">class</span> <span class="nc">FUNC</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="n">ExecuteGenericLoop</span><span class="p">(</span><span class="k">const</span> <span class="n">LEFT_TYPE</span> <span class="o">*</span><span class="n">__restrict</span> <span class="n">ldata</span><span class="p">,</span> <span class="k">const</span> <span class="n">RIGHT_TYPE</span> <span class="o">*</span><span class="n">__restrict</span> <span class="n">rdata</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                               <span class="n">RESULT_TYPE</span> <span class="o">*</span><span class="n">__restrict</span> <span class="n">result_data</span><span class="p">,</span> <span class="k">const</span> <span class="n">SelectionVector</span> <span class="o">*</span><span class="n">__restrict</span> <span class="n">lsel</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                               <span class="k">const</span> <span class="n">SelectionVector</span> <span class="o">*</span><span class="n">__restrict</span> <span class="n">rsel</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">count</span><span class="p">,</span> <span class="n">ValidityMask</span> <span class="o">&amp;</span><span class="n">lvalidity</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                               <span class="n">ValidityMask</span> <span class="o">&amp;</span><span class="n">rvalidity</span><span class="p">,</span> <span class="n">ValidityMask</span> <span class="o">&amp;</span><span class="n">result_validity</span><span class="p">,</span> <span class="n">FUNC</span> <span class="n">fun</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">lvalidity</span><span class="p">.</span><span class="n">AllValid</span><span class="p">()</span> <span class="o">||</span> <span class="o">!</span><span class="n">rvalidity</span><span class="p">.</span><span class="n">AllValid</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">lindex</span> <span class="o">=</span> <span class="n">lsel</span><span class="o">-&gt;</span><span class="n">get_index</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">rindex</span> <span class="o">=</span> <span class="n">rsel</span><span class="o">-&gt;</span><span class="n">get_index</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="n">lvalidity</span><span class="p">.</span><span class="n">RowIsValid</span><span class="p">(</span><span class="n">lindex</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">rvalidity</span><span class="p">.</span><span class="n">RowIsValid</span><span class="p">(</span><span class="n">rindex</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="k">auto</span> <span class="n">lentry</span> <span class="o">=</span> <span class="n">ldata</span><span class="p">[</span><span class="n">lindex</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">				<span class="k">auto</span> <span class="n">rentry</span> <span class="o">=</span> <span class="n">rdata</span><span class="p">[</span><span class="n">rindex</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">				<span class="n">result_data</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">OPWRAPPER</span><span class="o">::</span><span class="k">template</span> <span class="n">Operation</span><span class="o">&lt;</span><span class="n">FUNC</span><span class="p">,</span> <span class="n">OP</span><span class="p">,</span> <span class="n">LEFT_TYPE</span><span class="p">,</span> <span class="n">RIGHT_TYPE</span><span class="p">,</span> <span class="n">RESULT_TYPE</span><span class="o">&gt;</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">				    <span class="n">fun</span><span class="p">,</span> <span class="n">lentry</span><span class="p">,</span> <span class="n">rentry</span><span class="p">,</span> <span class="n">result_validity</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="n">result_validity</span><span class="p">.</span><span class="n">SetInvalid</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">lentry</span> <span class="o">=</span> <span class="n">ldata</span><span class="p">[</span><span class="n">lsel</span><span class="o">-&gt;</span><span class="n">get_index</span><span class="p">(</span><span class="n">i</span><span class="p">)];</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">rentry</span> <span class="o">=</span> <span class="n">rdata</span><span class="p">[</span><span class="n">rsel</span><span class="o">-&gt;</span><span class="n">get_index</span><span class="p">(</span><span class="n">i</span><span class="p">)];</span>
</span></span><span class="line"><span class="cl">			<span class="n">result_data</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">OPWRAPPER</span><span class="o">::</span><span class="k">template</span> <span class="n">Operation</span><span class="o">&lt;</span><span class="n">FUNC</span><span class="p">,</span> <span class="n">OP</span><span class="p">,</span> <span class="n">LEFT_TYPE</span><span class="p">,</span> <span class="n">RIGHT_TYPE</span><span class="p">,</span> <span class="n">RESULT_TYPE</span><span class="o">&gt;</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">			    <span class="n">fun</span><span class="p">,</span> <span class="n">lentry</span><span class="p">,</span> <span class="n">rentry</span><span class="p">,</span> <span class="n">result_validity</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>这里的 <code>OPWRAPPER::template Operation</code> 就是 duckdb::Equals 的 Operation, 这里 <code>OPWRAPPER</code> 是一个模板参数决定的类型, <code>Operation()</code> 是它的成员模板函数，这里需要显式用 <code>template</code> 关键字告诉编译器调用的是成员模板.</p>
<p>比如 Add 相加操作, 也是使用 <code>BinaryExecutor::ExecuteGenericLoop()</code> 进行 for 循环操作调用</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">AddOperator</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">template</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">TA</span><span class="p">,</span> <span class="k">class</span> <span class="nc">TB</span><span class="p">,</span> <span class="k">class</span> <span class="nc">TR</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="kr">inline</span> <span class="n">TR</span> <span class="n">Operation</span><span class="p">(</span><span class="n">TA</span> <span class="n">left</span><span class="p">,</span> <span class="n">TB</span> <span class="n">right</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="n">left</span> <span class="o">+</span> <span class="n">right</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><h2 id="duckdb-向量计算的模板技巧">DuckDB 向量计算的模板技巧</h2>
<p>DuckDB 的向量计算的核心都围绕一个核心: 零成本抽象, 把运行时决策移到编译期, 借助编译器生成最优的代码.</p>
<ol>
<li>编译期常量计算</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">static</span> <span class="k">constexpr</span> <span class="kt">uint16_t</span> <span class="n">COMPRESSED_SEGMENT_SIZE</span> <span class="o">=</span> <span class="mi">256</span><span class="p">;</span></span></span></code></pre></div><p>DuckDB 大量使用 <code>constexpr</code> 来定义变量为编译期常量, 避免了运行时的计算开销.</p>
<ol start="2">
<li>通过 Wrapper 模板统一不同函数签名</li>
</ol>
<p>在 DuckDB 的一元算子框架里, 只有一个统一入口 <code>Operation()</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">UnaryOperatorWrapper</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">template</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">OP</span><span class="p">,</span> <span class="k">class</span> <span class="nc">INPUT_TYPE</span><span class="p">,</span> <span class="k">class</span> <span class="nc">RESULT_TYPE</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="kr">inline</span> <span class="n">RESULT_TYPE</span> <span class="n">Operation</span><span class="p">(</span><span class="n">INPUT_TYPE</span> <span class="n">input</span><span class="p">,</span> <span class="n">ValidityMask</span> <span class="o">&amp;</span><span class="n">mask</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">idx</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">dataptr</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="n">OP</span><span class="o">::</span><span class="k">template</span> <span class="n">Operation</span><span class="o">&lt;</span><span class="n">INPUT_TYPE</span><span class="p">,</span> <span class="n">RESULT_TYPE</span><span class="o">&gt;</span><span class="p">(</span><span class="n">input</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">UnaryLambdaWrapper</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">template</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">FUNC</span><span class="p">,</span> <span class="k">class</span> <span class="nc">INPUT_TYPE</span><span class="p">,</span> <span class="k">class</span> <span class="nc">RESULT_TYPE</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="kr">inline</span> <span class="n">RESULT_TYPE</span> <span class="n">Operation</span><span class="p">(</span><span class="n">INPUT_TYPE</span> <span class="n">input</span><span class="p">,</span> <span class="n">ValidityMask</span> <span class="o">&amp;</span><span class="n">mask</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">idx</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">dataptr</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">fun</span> <span class="o">=</span> <span class="p">(</span><span class="n">FUNC</span> <span class="o">*</span><span class="p">)</span><span class="n">dataptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="p">(</span><span class="o">*</span><span class="n">fun</span><span class="p">)(</span><span class="n">input</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">GenericUnaryWrapper</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">template</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">OP</span><span class="p">,</span> <span class="k">class</span> <span class="nc">INPUT_TYPE</span><span class="p">,</span> <span class="k">class</span> <span class="nc">RESULT_TYPE</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="kr">inline</span> <span class="n">RESULT_TYPE</span> <span class="n">Operation</span><span class="p">(</span><span class="n">INPUT_TYPE</span> <span class="n">input</span><span class="p">,</span> <span class="n">ValidityMask</span> <span class="o">&amp;</span><span class="n">mask</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">idx</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">dataptr</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="n">OP</span><span class="o">::</span><span class="k">template</span> <span class="n">Operation</span><span class="o">&lt;</span><span class="n">INPUT_TYPE</span><span class="p">,</span> <span class="n">RESULT_TYPE</span><span class="o">&gt;</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">mask</span><span class="p">,</span> <span class="n">idx</span><span class="p">,</span> <span class="n">dataptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">UnaryLambdaWrapperWithNulls</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">template</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">FUNC</span><span class="p">,</span> <span class="k">class</span> <span class="nc">INPUT_TYPE</span><span class="p">,</span> <span class="k">class</span> <span class="nc">RESULT_TYPE</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="kr">inline</span> <span class="n">RESULT_TYPE</span> <span class="n">Operation</span><span class="p">(</span><span class="n">INPUT_TYPE</span> <span class="n">input</span><span class="p">,</span> <span class="n">ValidityMask</span> <span class="o">&amp;</span><span class="n">mask</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">idx</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">dataptr</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">fun</span> <span class="o">=</span> <span class="p">(</span><span class="n">FUNC</span> <span class="o">*</span><span class="p">)</span><span class="n">dataptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="p">(</span><span class="o">*</span><span class="n">fun</span><span class="p">)(</span><span class="n">input</span><span class="p">,</span> <span class="n">mask</span><span class="p">,</span> <span class="n">idx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">template</span> <span class="o">&lt;</span><span class="k">class</span> <span class="nc">INPUT_TYPE</span><span class="p">,</span> <span class="k">class</span> <span class="nc">RESULT_TYPE</span><span class="p">,</span> <span class="k">class</span> <span class="nc">OPWRAPPER</span><span class="p">,</span> <span class="k">class</span> <span class="nc">OP</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="n">ExecuteLoop</span><span class="p">(</span><span class="k">const</span> <span class="n">INPUT_TYPE</span> <span class="o">*</span><span class="n">__restrict</span> <span class="n">ldata</span><span class="p">,</span> <span class="n">RESULT_TYPE</span> <span class="o">*</span><span class="n">__restrict</span> <span class="n">result_data</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">count</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	                               <span class="k">const</span> <span class="n">SelectionVector</span> <span class="o">*</span><span class="n">__restrict</span> <span class="n">sel_vector</span><span class="p">,</span> <span class="n">ValidityMask</span> <span class="o">&amp;</span><span class="n">mask</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	                               <span class="n">ValidityMask</span> <span class="o">&amp;</span><span class="n">result_mask</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">dataptr</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">adds_nulls</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef DEBUG
</span></span></span><span class="line"><span class="cl">		<span class="c1">// ldata may point to a compressed dictionary buffer which can be smaller than ldata + count
</span></span></span><span class="line"><span class="cl">		<span class="n">idx_t</span> <span class="n">max_index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">sel_vector</span><span class="o">-&gt;</span><span class="n">get_index</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="n">max_index</span> <span class="o">=</span> <span class="n">MaxValue</span><span class="p">(</span><span class="n">max_index</span><span class="p">,</span> <span class="n">idx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="n">ASSERT_RESTRICT</span><span class="p">(</span><span class="n">ldata</span><span class="p">,</span> <span class="n">ldata</span> <span class="o">+</span> <span class="n">max_index</span><span class="p">,</span> <span class="n">result_data</span><span class="p">,</span> <span class="n">result_data</span> <span class="o">+</span> <span class="n">count</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">mask</span><span class="p">.</span><span class="n">AllValid</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="k">auto</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">sel_vector</span><span class="o">-&gt;</span><span class="n">get_index</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">				<span class="k">if</span> <span class="p">(</span><span class="n">mask</span><span class="p">.</span><span class="n">RowIsValidUnsafe</span><span class="p">(</span><span class="n">idx</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="n">result_data</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">					    <span class="n">OPWRAPPER</span><span class="o">::</span><span class="k">template</span> <span class="n">Operation</span><span class="o">&lt;</span><span class="n">OP</span><span class="p">,</span> <span class="n">INPUT_TYPE</span><span class="p">,</span> <span class="n">RESULT_TYPE</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ldata</span><span class="p">[</span><span class="n">idx</span><span class="p">],</span> <span class="n">result_mask</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">dataptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">				<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="n">result_mask</span><span class="p">.</span><span class="n">SetInvalid</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">				<span class="p">}</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="k">auto</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">sel_vector</span><span class="o">-&gt;</span><span class="n">get_index</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">				<span class="n">result_data</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">				    <span class="n">OPWRAPPER</span><span class="o">::</span><span class="k">template</span> <span class="n">Operation</span><span class="o">&lt;</span><span class="n">OP</span><span class="p">,</span> <span class="n">INPUT_TYPE</span><span class="p">,</span> <span class="n">RESULT_TYPE</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ldata</span><span class="p">[</span><span class="n">idx</span><span class="p">],</span> <span class="n">result_mask</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">dataptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span></span></span></code></pre></div><p>DuckDB 用 OPWRAPPER 把 4 种不同形态的函数 (标准一元运算符、lambda、需要自己处理 null / 需要外部 state 的 OP、会回写 null 的 lambda) 统一成一个函数签名, 根据 OPWRAPPER 可以直接调用不同的算子，完成不同的形式的计算.</p>
<ol start="3">
<li>编译期分支消除</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">template</span> <span class="o">&lt;</span><span class="kt">bool</span> <span class="n">HAS_RSEL</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">HAS_SEL_VECTOR</span><span class="p">,</span> <span class="k">class</span> <span class="nc">T</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">INPUT_IS_ALREADY_HASH</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">TightLoopHash</span><span class="p">(</span><span class="k">const</span> <span class="n">T</span> <span class="o">*</span><span class="n">__restrict</span> <span class="n">ldata</span><span class="p">,</span> <span class="n">hash_t</span> <span class="o">*</span><span class="n">__restrict</span> <span class="n">result_data</span><span class="p">,</span> <span class="k">const</span> <span class="n">SelectionVector</span> <span class="o">*</span><span class="n">rsel</span><span class="p">,</span> <span class="n">idx_t</span> <span class="n">count</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                   <span class="k">const</span> <span class="n">SelectionVector</span> <span class="o">*</span><span class="n">__restrict</span> <span class="n">sel_vector</span><span class="p">,</span> <span class="k">const</span> <span class="n">ValidityMask</span> <span class="o">&amp;</span><span class="n">mask</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">mask</span><span class="p">.</span><span class="n">AllValid</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="cm">/* HAS_RSEL, HAS_SEL_VECTOR 都是模板的参数, 所以可以在编译期间确定具体的值, 编译器会进行分支消除的优化. */</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">ridx</span> <span class="o">=</span> <span class="n">HAS_RSEL</span> <span class="o">?</span> <span class="n">rsel</span><span class="o">-&gt;</span><span class="n">get_index_unsafe</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="o">:</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">HAS_SEL_VECTOR</span> <span class="o">?</span> <span class="n">sel_vector</span><span class="o">-&gt;</span><span class="n">get_index_unsafe</span><span class="p">(</span><span class="n">ridx</span><span class="p">)</span> <span class="o">:</span> <span class="n">ridx</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="n">result_data</span><span class="p">[</span><span class="n">ridx</span><span class="p">]</span> <span class="o">=</span> <span class="n">INPUT_IS_ALREADY_HASH</span> <span class="o">?</span> <span class="n">CachedHashOp</span><span class="o">::</span><span class="n">Operation</span><span class="p">(</span><span class="n">ldata</span><span class="p">[</span><span class="n">idx</span><span class="p">])</span>
</span></span><span class="line"><span class="cl">			                                          <span class="o">:</span> <span class="n">HashOp</span><span class="o">::</span><span class="n">Operation</span><span class="p">(</span><span class="n">ldata</span><span class="p">[</span><span class="n">idx</span><span class="p">],</span> <span class="o">!</span><span class="n">mask</span><span class="p">.</span><span class="n">RowIsValidUnsafe</span><span class="p">(</span><span class="n">idx</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">ridx</span> <span class="o">=</span> <span class="n">HAS_RSEL</span> <span class="o">?</span> <span class="n">rsel</span><span class="o">-&gt;</span><span class="n">get_index_unsafe</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="o">:</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">HAS_SEL_VECTOR</span> <span class="o">?</span> <span class="n">sel_vector</span><span class="o">-&gt;</span><span class="n">get_index_unsafe</span><span class="p">(</span><span class="n">ridx</span><span class="p">)</span> <span class="o">:</span> <span class="n">ridx</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="n">result_data</span><span class="p">[</span><span class="n">ridx</span><span class="p">]</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">			    <span class="n">INPUT_IS_ALREADY_HASH</span> <span class="o">?</span> <span class="n">CachedHashOp</span><span class="o">::</span><span class="n">Operation</span><span class="p">(</span><span class="n">ldata</span><span class="p">[</span><span class="n">idx</span><span class="p">])</span> <span class="o">:</span> <span class="n">duckdb</span><span class="o">::</span><span class="n">Hash</span><span class="o">&lt;</span><span class="n">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">ldata</span><span class="p">[</span><span class="n">idx</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>从 C++17 开始支持了 <code>if constexpr</code>, 作用也是编译器分支消除:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">template</span> <span class="o">&lt;</span><span class="kt">bool</span> <span class="n">HAS_SEL</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">func</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nf">constexpr</span> <span class="p">(</span><span class="n">HAS_SEL</span><span class="p">)</span> <span class="p">{</span>  <span class="c1">// C++17 特性
</span></span></span><span class="line"><span class="cl">        <span class="c1">// ...
</span></span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="总结">总结</h2>
<p>DuckDB 的向量计算采用向量化执行, 核心思路是批量处理与编译期优化:</p>
<ul>
<li>
<p>批量处理: 一次处理 2048 行数据, 利用编译器生成 SIMD 指令来减少循环开销.</p>
</li>
<li>
<p>零拷贝优化: 借助 SelectionVector 避免复制数据.</p>
</li>
<li>
<p>支持 5 种向量类型覆盖不同场景, 根据数据特征不同使用不同的向量类型.</p>
</li>
<li>
<p>编译期优化: 大量的模板元编程在编译期生成针对不同场景的代码, 消除运行时分支. 用编译期决策替代运行时决策, 通过模板在编译期为不同场景生成最优代码.</p>
</li>
</ul>
]]></content>
  </entry><entry>
    <title>DuckDB 查询执行器架构解析</title>
    <link href="https://leviathan.vip/2025/09/24/duckdb-executor/" />
    <id>https://leviathan.vip/2025/09/24/duckdb-executor/</id>
    <updated>2025-09-24T14:38:12Z</updated>
    <summary type="html"><![CDATA[<h2 id="引言">引言</h2>
<p>DuckDB 的执行器采用了 Push-based Vectorized Execution Model, 与传统的 Pull-based 模型相比, Push 模型在并行化,内存效率都有着显著优势.</p>
<p>传统的执行模型火山模型 (Volcano Pull Model) 存在缓存局部性差、并行度低的问题，其中最为关键的是算子内部缺乏水平并行能力；若想让多个线程同时处理同一算子的不同数据分片，需要借助 EXCHANGE 等算子复制工作管道。至于垂直并行，Volcano 模型本身就采用“上游拉取下游”方式串接算子，这一层面并不是瓶颈。</p>
<p>这在目前的单机多核的趋势下存在很大的性能问题, 关于 Pull-based vs Push-based, Volcano vs Pipeline 网上有大量的资料讨论过这个问题:</p>
<ul>
<li><a href="https://dsdsd.da.cwi.nl/slides/dsdsd-duckdb-push-based-execution.pdf">Push-Based Execution in DuckDB</a></li>
<li><a href="https://arxiv.org/pdf/1610.09166">Push vs. Pull-Based Loop Fusion in Query Engines</a></li>
<li><a href="https://github.com/ClickHouse/ClickHouse/issues/34045">What&rsquo;s the difference between Clickhouse&rsquo;s pipeline execution and Volcano model?</a></li>
<li><a href="https://io-meter.com/2020/01/04/olap-distributed/">OLAP 任务的并发执行与调度</a></li>
<li><a href="http://tanweime.com/2023/04/30/pipeline%E6%89%A7%E8%A1%8C%E5%BC%95%E6%93%8E%E4%BB%A5%E5%8F%8A%E4%B8%80%E4%BA%9B%E5%B7%A5%E7%A8%8B%E4%BC%98%E5%8C%96/">pipeline执行引擎以及一些工程优化</a></li>
<li><a href="https://www.selectdb.com/blog/409">从 Volcano 火山模型到 Pipeline 执行模型, Apache Doris 执行模型的迭代</a></li>
<li><a href="https://15721.courses.cs.cmu.edu/spring2016/papers/p743-leis.pdf">Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework for the Many-Core Age</a></li>
</ul>
<p>DuckDB 的整个执行器都围绕着 Pipeline 的模型来设计, Pipeline 提供了结构化的并行理念, 将复杂的算子抽象成一个 DAG 有向无环图, 在整个 DAG 任务流中根据依赖关系可以将复杂的查询拆分为多个 Pipeline, 互不依赖的 Pipeline 可以并行执行.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="引言">引言</h2>
<p>DuckDB 的执行器采用了 Push-based Vectorized Execution Model, 与传统的 Pull-based 模型相比, Push 模型在并行化,内存效率都有着显著优势.</p>
<p>传统的执行模型火山模型 (Volcano Pull Model) 存在缓存局部性差、并行度低的问题，其中最为关键的是算子内部缺乏水平并行能力；若想让多个线程同时处理同一算子的不同数据分片，需要借助 EXCHANGE 等算子复制工作管道。至于垂直并行，Volcano 模型本身就采用“上游拉取下游”方式串接算子，这一层面并不是瓶颈。</p>
<p>这在目前的单机多核的趋势下存在很大的性能问题, 关于 Pull-based vs Push-based, Volcano vs Pipeline 网上有大量的资料讨论过这个问题:</p>
<ul>
<li><a href="https://dsdsd.da.cwi.nl/slides/dsdsd-duckdb-push-based-execution.pdf">Push-Based Execution in DuckDB</a></li>
<li><a href="https://arxiv.org/pdf/1610.09166">Push vs. Pull-Based Loop Fusion in Query Engines</a></li>
<li><a href="https://github.com/ClickHouse/ClickHouse/issues/34045">What&rsquo;s the difference between Clickhouse&rsquo;s pipeline execution and Volcano model?</a></li>
<li><a href="https://io-meter.com/2020/01/04/olap-distributed/">OLAP 任务的并发执行与调度</a></li>
<li><a href="http://tanweime.com/2023/04/30/pipeline%E6%89%A7%E8%A1%8C%E5%BC%95%E6%93%8E%E4%BB%A5%E5%8F%8A%E4%B8%80%E4%BA%9B%E5%B7%A5%E7%A8%8B%E4%BC%98%E5%8C%96/">pipeline执行引擎以及一些工程优化</a></li>
<li><a href="https://www.selectdb.com/blog/409">从 Volcano 火山模型到 Pipeline 执行模型, Apache Doris 执行模型的迭代</a></li>
<li><a href="https://15721.courses.cs.cmu.edu/spring2016/papers/p743-leis.pdf">Morsel-Driven Parallelism: A NUMA-Aware Query Evaluation Framework for the Many-Core Age</a></li>
</ul>
<p>DuckDB 的整个执行器都围绕着 Pipeline 的模型来设计, Pipeline 提供了结构化的并行理念, 将复杂的算子抽象成一个 DAG 有向无环图, 在整个 DAG 任务流中根据依赖关系可以将复杂的查询拆分为多个 Pipeline, 互不依赖的 Pipeline 可以并行执行.</p>
<p>DuckDB 采用 Morsel-Driven Parallelism 并行模型, 将数据划分为小的 &ldquo;morsels&rdquo;, 然后动态地将这些 morsels 分配给工作线程, 实现自适应的任务调度和更好的负载均衡. 这种机制解决了传统并行执行中的负载均衡问题, 特别是在处理数据倾斜的情况下能够动态调整任务分配.</p>
<h2 id="执行器">执行器</h2>
<p><img src="/images/duckdb-executor_1.png" alt="duckdb-executor_1.png"></p>
<p>在之前的文章<a href="https://www.leviathan.vip/2025/05/19/duckdb-logical-plan/">DuckDB 源码分析 - Logical Plan 逻辑计划</a>我们介绍了 DuckDB 的逻辑计划, 逻辑计划转换为物理计划后经过执行器被转换为高效的并行执行 Pipeline.</p>
<h3 id="执行器核心抽象与数据结构">执行器核心抽象与数据结构</h3>
<p>逻辑计划构建完成以后进行物理计划的构建, 然后我们根据物理计划开始构建整个执行任务流. 物理计划生成的核心组件包括物理计划生成器 (PhysicalPlanGenerator), 物理操作符 (PhysicalOperator) 和物理计划 (PhysicalPlan). 这些组件协同工作, 将逻辑计划转换为可执行的物理计划.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* src/main/client_context.cpp */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">PreparedStatementData</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="n">ClientContext</span><span class="o">::</span><span class="n">CreatePreparedStatementInternal</span><span class="p">(</span><span class="n">ClientContextLock</span> <span class="o">&amp;</span><span class="n">lock</span><span class="p">,</span> <span class="k">const</span> <span class="n">string</span> <span class="o">&amp;</span><span class="n">query</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                               <span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">SQLStatement</span><span class="o">&gt;</span> <span class="n">statement</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                               <span class="n">optional_ptr</span><span class="o">&lt;</span><span class="n">case_insensitive_map_t</span><span class="o">&lt;</span><span class="n">BoundParameterData</span><span class="o">&gt;&gt;</span> <span class="n">values</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="n">enable_optimizer</span> <span class="o">&amp;&amp;</span> <span class="n">logical_plan</span><span class="o">-&gt;</span><span class="n">RequireOptimizer</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">profiler</span><span class="p">.</span><span class="n">StartPhase</span><span class="p">(</span><span class="n">MetricsType</span><span class="o">::</span><span class="n">ALL_OPTIMIZERS</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 进行逻辑计划的优化. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">Optimizer</span> <span class="nf">optimizer</span><span class="p">(</span><span class="o">*</span><span class="n">logical_planner</span><span class="p">.</span><span class="n">binder</span><span class="p">,</span> <span class="o">*</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">logical_plan</span> <span class="o">=</span> <span class="n">optimizer</span><span class="p">.</span><span class="n">Optimize</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">logical_plan</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">logical_plan</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">profiler</span><span class="p">.</span><span class="n">EndPhase</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef DEBUG
</span></span></span><span class="line"><span class="cl">		<span class="n">logical_plan</span><span class="o">-&gt;</span><span class="n">Verify</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 将逻辑计划转为物理计划. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">profiler</span><span class="p">.</span><span class="n">StartPhase</span><span class="p">(</span><span class="n">MetricsType</span><span class="o">::</span><span class="n">PHYSICAL_PLANNER</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">PhysicalPlanGenerator</span> <span class="nf">physical_planner</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">result</span><span class="o">-&gt;</span><span class="n">physical_plan</span> <span class="o">=</span> <span class="n">physical_planner</span><span class="p">.</span><span class="n">Plan</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">logical_plan</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">	<span class="n">profiler</span><span class="p">.</span><span class="n">EndPhase</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">	<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">result</span><span class="o">-&gt;</span><span class="n">physical_plan</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><img src="/images/duckdb-executor_2.png" alt="duckdb-executor_2.png"></p>
<h2 id="pipeline-流水线机制">Pipeline 流水线机制</h2>
<h3 id="基本概念">基本概念</h3>
<ul>
<li>
<p>执行模型: DuckDB 采用向量化 (DataChunk 为单位)的流水线执行. 每条 Pipeline 由 &ldquo;一个 Source + 若干个中间 PhysicalOperator + 一个 Sink&rdquo; 构成.</p>
</li>
<li>
<p>数据流向: Source 产出数据块 -&gt; 中间算子按批变换 -&gt; Sink 消费并汇总/产出最终结果或中间结果 (如哈希表).</p>
</li>
<li>
<p>调度体系: Pipeline 被拆分为可并行的任务, 由任务调度器在多线程环境下执行; PipelineExecutor 负责在本地线程上驱动这条管道.</p>
</li>
<li>
<p>MetaPipeline</p>
</li>
</ul>
<blockquote>
<p>MetaPipeline 是 &ldquo;pipeline 的编排组合&rdquo;, 定义 pipeline 的元数据,参数,依赖关系. 围绕同一个 Sink, 把多个以不同 Source 开始但共享同一 Sink 的 Pipeline 组织成一组, 并在组内与组间管理依赖与完成事件. MetaPipeline 还负责管理 Pipeline 之间的执行顺序和复杂依赖关系, 特别是在处理 Join 策略时协调 Build 侧和 Probe 侧的执行.</p>
</blockquote>
<ul>
<li>Pipeline</li>
</ul>
<blockquote>
<p>一连串的物理计划操作符被切割成可并行,可调度的执行单元, 这个执行单元就是 pipeline. 明确一条从 Source 到 Sink 的线性数据流, 中间是一串可融合的算子.</p>
</blockquote>
<p>一般情况下，MetaPipeline 围绕同一个 Sink 组织执行，通常只有一个基础 Pipeline (base pipeline)。多数算子(Projection/Filter/TableScan 等中间或 Source 算子)不会在该 MetaPipeline 下再生成额外 Pipeline。
但是也有例外，比如 Join 算子。</p>
<p><strong>PhysicalOperator</strong>: 物理算子</p>
<p><strong>Source</strong>: 源端算子, 作为整个 pipeline 的起点, 每个算子具体是 Source 算子还是 Sink 算子是初始化定义的, 比如 PhysicalTableScan 就是 Source 算子.</p>
<p><strong>Sink</strong>: Pipeline Breaker 的算子.</p>
<p>有的算子即是 Source 算子, 也是 Sink 算子. 这类算子被称为 Pipeline Breaker, 它们会将一个 Pipeline 分割成多个独立的 Pipeline. 典型的 Pipeline Breaker 包括 Hash Join,Aggregation 等需要分阶段执行的算子.</p>
<h3 id="pipeline-构建">Pipeline 构建</h3>
<p>调度过程由 Pipeline 和 PipelineExecutor 管理. 一个查询可能被划分为多个相互依赖的 Pipeline. PipelineExecutor 负责协调这些流水线的执行.</p>
<p>在 DuckDB 中，每一个查询语句都会被包装成以 PhysicalResultCollector 作为 root (顶层 Sink) 的物理计划，PhysicalResultCollector::GetResultCollector 会选择具体的 Collector 变体并作为 root operator 交给执行器初始化。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">Executor</span><span class="o">::</span><span class="n">InitializeInternal</span><span class="p">(</span><span class="n">PhysicalOperator</span> <span class="o">&amp;</span><span class="n">plan</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 从当前 ClientContext 获取全局的任务调度器. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="o">&amp;</span><span class="n">scheduler</span> <span class="o">=</span> <span class="n">TaskScheduler</span><span class="o">::</span><span class="n">GetScheduler</span><span class="p">(</span><span class="n">context</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">lock_guard</span><span class="o">&lt;</span><span class="n">mutex</span><span class="o">&gt;</span> <span class="n">elock</span><span class="p">(</span><span class="n">executor_lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">physical_plan</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">plan</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">this</span><span class="o">-&gt;</span><span class="n">profiler</span> <span class="o">=</span> <span class="n">ClientData</span><span class="o">::</span><span class="n">Get</span><span class="p">(</span><span class="n">context</span><span class="p">).</span><span class="n">profiler</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="n">profiler</span><span class="o">-&gt;</span><span class="n">Initialize</span><span class="p">(</span><span class="n">plan</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">this</span><span class="o">-&gt;</span><span class="n">producer</span> <span class="o">=</span> <span class="n">scheduler</span><span class="p">.</span><span class="n">CreateProducer</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 构建所有的 pipelines. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">PipelineBuildState</span> <span class="n">state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 从 root 的 MetaPipeline 开始. */</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">root_pipeline</span> <span class="o">=</span> <span class="n">make_shared_ptr</span><span class="o">&lt;</span><span class="n">MetaPipeline</span><span class="o">&gt;</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">,</span> <span class="n">state</span><span class="p">,</span> <span class="k">nullptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 调用 Build 从物理计划根节点递归构建所有相关的 Pipeline, 并按 Pipeline 构建原则建立 Pipeline Breaker 与依赖. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">root_pipeline</span><span class="o">-&gt;</span><span class="n">Build</span><span class="p">(</span><span class="o">*</span><span class="n">physical_plan</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 调用 Ready 为所有 pipelines 生成并准备事件序列 (initialize -&gt;  event -&gt;  prepare finish -&gt;  finish -&gt; complete),
</span></span></span><span class="line"><span class="cl"><span class="cm">		 * 使它们可被调度执行. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">root_pipeline</span><span class="o">-&gt;</span><span class="n">Ready</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// set root pipelines, i.e., all pipelines that end in the final sink
</span></span></span><span class="line"><span class="cl">		<span class="n">root_pipeline</span><span class="o">-&gt;</span><span class="n">GetPipelines</span><span class="p">(</span><span class="n">root_pipelines</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">root_pipeline_idx</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// collect all meta-pipelines from the root pipeline
</span></span></span><span class="line"><span class="cl">		<span class="cm">/* 收集所有待调度的 MetaPipeline (递归搜索). */</span>
</span></span><span class="line"><span class="cl">		<span class="n">vector</span><span class="o">&lt;</span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">MetaPipeline</span><span class="o">&gt;&gt;</span> <span class="n">to_schedule</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="n">root_pipeline</span><span class="o">-&gt;</span><span class="n">GetMetaPipelines</span><span class="p">(</span><span class="n">to_schedule</span><span class="p">,</span> <span class="nb">true</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// number of &#39;PipelineCompleteEvent&#39;s is equal to the number of meta pipelines, so we have to set it here
</span></span></span><span class="line"><span class="cl">		<span class="n">total_pipelines</span> <span class="o">=</span> <span class="n">to_schedule</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// collect all pipelines from the root pipelines (recursively) for the progress bar and verify them
</span></span></span><span class="line"><span class="cl">		<span class="n">root_pipeline</span><span class="o">-&gt;</span><span class="n">GetPipelines</span><span class="p">(</span><span class="n">pipelines</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// finally, verify and schedule
</span></span></span><span class="line"><span class="cl">		<span class="n">VerifyPipelines</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="n">ScheduleEvents</span><span class="p">(</span><span class="n">to_schedule</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>我们构建一个样例数据, 使用 EXPLAIN 来理解物理计划如何转换成 pipelines 集合的:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- 建表与数据
</span></span></span><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">products</span><span class="p">(</span><span class="n">id</span><span class="w"> </span><span class="nb">INTEGER</span><span class="p">,</span><span class="w"> </span><span class="n">category</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">products</span><span class="w"> </span><span class="k">VALUES</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;A&#39;</span><span class="p">),</span><span class="w"> </span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;B&#39;</span><span class="p">),</span><span class="w"> </span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;C&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">sales</span><span class="p">(</span><span class="n">product_id</span><span class="w"> </span><span class="nb">INTEGER</span><span class="p">,</span><span class="w"> </span><span class="n">amount</span><span class="w"> </span><span class="n">DOUBLE</span><span class="p">,</span><span class="w"> </span><span class="n">sale_date</span><span class="w"> </span><span class="nb">DATE</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">sales</span><span class="w"> </span><span class="k">VALUES</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">10</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nb">DATE</span><span class="w"> </span><span class="s1">&#39;2024-01-10&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">20</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nb">DATE</span><span class="w"> </span><span class="s1">&#39;2023-12-31&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">30</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nb">DATE</span><span class="w"> </span><span class="s1">&#39;2024-06-15&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">40</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nb">DATE</span><span class="w"> </span><span class="s1">&#39;2024-07-01&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="nb">DATE</span><span class="w"> </span><span class="s1">&#39;2025-01-01&#39;</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">-- 查询
</span></span></span><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">category</span><span class="p">,</span><span class="w"> </span><span class="k">SUM</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">amount</span><span class="p">)</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="n">total</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">FROM</span><span class="w"> </span><span class="n">sales</span><span class="w"> </span><span class="n">s</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">JOIN</span><span class="w"> </span><span class="n">products</span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="k">ON</span><span class="w"> </span><span class="n">s</span><span class="p">.</span><span class="n">product_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">id</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">WHERE</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">category</span><span class="w"> </span><span class="k">IN</span><span class="w"> </span><span class="p">(</span><span class="s1">&#39;A&#39;</span><span class="p">,</span><span class="s1">&#39;B&#39;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">AND</span><span class="w"> </span><span class="n">s</span><span class="p">.</span><span class="n">sale_date</span><span class="w"> </span><span class="k">BETWEEN</span><span class="w"> </span><span class="nb">DATE</span><span class="w"> </span><span class="s1">&#39;2024-01-01&#39;</span><span class="w"> </span><span class="k">AND</span><span class="w"> </span><span class="nb">DATE</span><span class="w"> </span><span class="s1">&#39;2024-12-31&#39;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">GROUP</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">category</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">ORDER</span><span class="w"> </span><span class="k">BY</span><span class="w"> </span><span class="n">total</span><span class="w"> </span><span class="k">DESC</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">LIMIT</span><span class="w"> </span><span class="mi">10</span><span class="p">;</span></span></span></code></pre></div><p>下图是 PhysicalOperator 描述的物理计划:</p>
<img src="/images/duckdb-executor_3.png" alt="DuckDB物理计划图" style="max-width: 100%; height: auto; width: 450px;">
<ol>
<li>
<p>入口: PhysicalResultCollector</p>
<p>PhysicalResultCollector 是 Sink 操作符, 负责收集查询结果.</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">PhysicalResultCollector</span><span class="o">::</span><span class="n">BuildPipelines</span><span class="p">(</span><span class="n">Pipeline</span> <span class="o">&amp;</span><span class="n">current</span><span class="p">,</span> <span class="n">MetaPipeline</span> <span class="o">&amp;</span><span class="n">meta_pipeline</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 1. 重置为 sink 状态. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">sink_state</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 2. ResultCollector 没有直接子节点, 通过 plan 字段引用(PhysicalOperator &amp;plan). */</span>
</span></span><span class="line"><span class="cl">    <span class="n">D_ASSERT</span><span class="p">(</span><span class="n">children</span><span class="p">.</span><span class="n">empty</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 3. 将自己设置为当前 pipeline 的 Sink. */</span>
</span></span><span class="line"><span class="cl">    <span class="k">auto</span> <span class="o">&amp;</span><span class="n">state</span> <span class="o">=</span> <span class="n">meta_pipeline</span><span class="p">.</span><span class="n">GetState</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">state</span><span class="p">.</span><span class="n">SetPipelineSink</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="o">*</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 4. 创建子 MetaPipeline 来构建实际的查询计划. */</span>
</span></span><span class="line"><span class="cl">    <span class="k">auto</span> <span class="o">&amp;</span><span class="n">child_meta_pipeline</span> <span class="o">=</span> <span class="n">meta_pipeline</span><span class="p">.</span><span class="n">CreateChildMetaPipeline</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="o">*</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">child_meta_pipeline</span><span class="p">.</span><span class="n">Build</span><span class="p">(</span><span class="n">plan</span><span class="p">);</span>  <span class="cm">/* plan = PhysicalExplainAnalyze. */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><ol start="2">
<li>
<p>PhysicalExplainAnalyze 操作符</p>
<p>PhysicalExplainAnalyze 使用默认的 BuildPipelines() 逻辑:</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">PhysicalOperator</span><span class="o">::</span><span class="n">BuildPipelines</span><span class="p">(</span><span class="n">Pipeline</span> <span class="o">&amp;</span><span class="n">current</span><span class="p">,</span> <span class="n">MetaPipeline</span> <span class="o">&amp;</span><span class="n">meta_pipeline</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">op_state</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="o">&amp;</span><span class="n">state</span> <span class="o">=</span> <span class="n">meta_pipeline</span><span class="p">.</span><span class="n">GetState</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">IsSink</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* 1. PhysicalExplainAnalyze 既是 Sink 操作符也是 Source 操作符. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">sink_state</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 2. 将自己设置为当前 Pipeline 的 Source. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">state</span><span class="p">.</span><span class="n">SetPipelineSource</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="o">*</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 3. 创建一个新的 MetaPipeline 来递归子操作符. */</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="o">&amp;</span><span class="n">child_meta_pipeline</span> <span class="o">=</span> <span class="n">meta_pipeline</span><span class="p">.</span><span class="n">CreateChildMetaPipeline</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="o">*</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">child_meta_pipeline</span><span class="p">.</span><span class="n">Build</span><span class="p">(</span><span class="n">children</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">get</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><ol start="3">
<li>
<p>PhysicalTopN 操作符</p>
<p>PhysicalTopN 既是 Source 又是 Sink，也使用默认的 BuildPipelines() 逻辑:</p>
</li>
<li>
<p>PhysicalProjection 操作符</p>
<p>PhysicalProjection 既不是 Sink 也不是 Source，但是也使用默认的 BuildPipelines() 逻辑:</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">PhysicalOperator</span><span class="o">::</span><span class="n">BuildPipelines</span><span class="p">(</span><span class="n">Pipeline</span> <span class="o">&amp;</span><span class="n">current</span><span class="p">,</span> <span class="n">MetaPipeline</span> <span class="o">&amp;</span><span class="n">meta_pipeline</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">op_state</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">   
</span></span><span class="line"><span class="cl">	<span class="cm">/* Projection 不是Sink, 不是Source, 有子节点.  */</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="o">&amp;</span><span class="n">state</span> <span class="o">=</span> <span class="n">meta_pipeline</span><span class="p">.</span><span class="n">GetState</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 添加到当前 pipeline ( pipeline.operators.push_back(op)) 并递归. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">state</span><span class="p">.</span><span class="n">AddPipelineOperator</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="o">*</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">children</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">get</span><span class="p">().</span><span class="n">BuildPipelines</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">meta_pipeline</span><span class="p">);</span>  <span class="cm">/* 调用 PhysicalPerfectHashGroupBy 继续构建 pipeline. */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><ol start="5">
<li>
<p>PhysicalPerfectHashAggregate 操作符</p>
<p>PhysicalPerfectHashAggregate 既是 Sink 也是 Source，使用默认的 BuildPipelines() 逻辑:</p>
</li>
</ol>
<p>从 PhysicalPerfectHashAggregate 下来有连续两个 PhysicalProjection 操作符, 然后来到了 Hash Join 操作符.</p>
<ol start="6">
<li>
<p>PhysicalHashJoin 操作符</p>
<p>PhysicalHashJoin 是一个比较复杂的操作符, 它会创建 Build 侧和 Probe 侧两个不同的 Pipeline:</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="c1">//===--------------------------------------------------------------------===//
</span></span></span><span class="line"><span class="cl"><span class="c1">// Pipeline Construction
</span></span></span><span class="line"><span class="cl"><span class="c1">//===--------------------------------------------------------------------===//
</span></span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">PhysicalJoin</span><span class="o">::</span><span class="n">BuildJoinPipelines</span><span class="p">(</span><span class="n">Pipeline</span> <span class="o">&amp;</span><span class="n">current</span><span class="p">,</span> <span class="n">MetaPipeline</span> <span class="o">&amp;</span><span class="n">meta_pipeline</span><span class="p">,</span> <span class="n">PhysicalOperator</span> <span class="o">&amp;</span><span class="n">op</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                      <span class="kt">bool</span> <span class="n">build_rhs</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">op</span><span class="p">.</span><span class="n">op_state</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">	<span class="n">op</span><span class="p">.</span><span class="n">sink_state</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 把 Join 操作符加入当前 pipeline. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="o">&amp;</span><span class="n">state</span> <span class="o">=</span> <span class="n">meta_pipeline</span><span class="p">.</span><span class="n">GetState</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">	<span class="n">state</span><span class="p">.</span><span class="n">AddPipelineOperator</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">op</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">vector</span><span class="o">&lt;</span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">Pipeline</span><span class="o">&gt;&gt;</span> <span class="n">pipelines_so_far</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">meta_pipeline</span><span class="p">.</span><span class="n">GetPipelines</span><span class="p">(</span><span class="n">pipelines_so_far</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="o">&amp;</span><span class="n">last_pipeline</span> <span class="o">=</span> <span class="o">*</span><span class="n">pipelines_so_far</span><span class="p">.</span><span class="n">back</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">vector</span><span class="o">&lt;</span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">Pipeline</span><span class="o">&gt;&gt;</span> <span class="n">dependencies</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">optional_ptr</span><span class="o">&lt;</span><span class="n">MetaPipeline</span><span class="o">&gt;</span> <span class="n">last_child_ptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">build_rhs</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* 构建右子树 (Build 侧) 的 MetaPipeline. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// on the RHS (build side), we construct a child MetaPipeline with this operator as its sink
</span></span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="o">&amp;</span><span class="n">child_meta_pipeline</span> <span class="o">=</span> <span class="n">meta_pipeline</span><span class="p">.</span><span class="n">CreateChildMetaPipeline</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">op</span><span class="p">,</span> <span class="n">MetaPipelineType</span><span class="o">::</span><span class="n">JOIN_BUILD</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 根据 PhysicalOperator 递归构建右边的 pipelines. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">child_meta_pipeline</span><span class="p">.</span><span class="n">Build</span><span class="p">(</span><span class="n">op</span><span class="p">.</span><span class="n">children</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">op</span><span class="p">.</span><span class="n">children</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">get</span><span class="p">().</span><span class="n">CanSaturateThreads</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="n">GetClientContext</span><span class="p">()))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// if the build side can saturate all available threads,
</span></span></span><span class="line"><span class="cl">			<span class="c1">// we don&#39;t just make the LHS pipeline depend on the RHS, but recursively all LHS children too.
</span></span></span><span class="line"><span class="cl">			<span class="c1">// this prevents breadth-first plan evaluation
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">			<span class="cm">/* 这个 if 判断是一个性能优化和执行顺序控制的关键逻辑.
</span></span></span><span class="line"><span class="cl"><span class="cm">			 * 如果 Build 阶段能占满所有可用线程, 要防止广度优先(breadth-first)的策略被先行执行,
</span></span></span><span class="line"><span class="cl"><span class="cm">			 * 也就是左子树所有的 pipelines 都要依赖右子树, 让 Build 阶段先完成再来调度左边的 Probe 阶段. */</span>
</span></span><span class="line"><span class="cl">			<span class="n">child_meta_pipeline</span><span class="p">.</span><span class="n">GetPipelines</span><span class="p">(</span><span class="n">dependencies</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="n">last_child_ptr</span> <span class="o">=</span> <span class="n">meta_pipeline</span><span class="p">.</span><span class="n">GetLastChild</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// continue building the current pipeline on the LHS (probe side)
</span></span></span><span class="line"><span class="cl">	<span class="cm">/* 继续当前 MetaPipeline, 构建左子树 (Probe 侧) 的 pipelines. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">op</span><span class="p">.</span><span class="n">children</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">get</span><span class="p">().</span><span class="n">BuildPipelines</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">meta_pipeline</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">last_child_ptr</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// the pointer was set, set up the dependencies
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 让左子树中所有后续创建的子 MetaPipeline 的所有 Pipeline, 都依赖于右表 Build 侧的所有 Pipeline. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">meta_pipeline</span><span class="p">.</span><span class="n">AddRecursiveDependencies</span><span class="p">(</span><span class="n">dependencies</span><span class="p">,</span> <span class="o">*</span><span class="n">last_child_ptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 特殊 Join 类型处理. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">switch</span> <span class="p">(</span><span class="n">op</span><span class="p">.</span><span class="n">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">PhysicalOperatorType</span><span class="o">::</span><span class="nl">POSITIONAL_JOIN</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// Positional joins are always outer
</span></span></span><span class="line"><span class="cl">		<span class="n">meta_pipeline</span><span class="p">.</span><span class="n">CreateChildPipeline</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">op</span><span class="p">,</span> <span class="n">last_pipeline</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">case</span> <span class="n">PhysicalOperatorType</span><span class="o">::</span><span class="nl">CROSS_PRODUCT</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">		<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="c1">// Join can become a source operator if it&#39;s RIGHT/OUTER, or if the hash join goes out-of-core
</span></span></span><span class="line"><span class="cl">	<span class="cm">/* 某些 Join 操作会主动产生额外的输出数据 (Source角色), 所以需要创建一个额外的 Pipeline 来处理这些输出. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">op</span><span class="p">.</span><span class="n">Cast</span><span class="o">&lt;</span><span class="n">PhysicalJoin</span><span class="o">&gt;</span><span class="p">().</span><span class="n">IsSource</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">meta_pipeline</span><span class="p">.</span><span class="n">CreateChildPipeline</span><span class="p">(</span><span class="n">current</span><span class="p">,</span> <span class="n">op</span><span class="p">,</span> <span class="n">last_pipeline</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>PhysicalHashJoin 算子会创建 Build 阶段和 Probe 阶段两个不同的 Pipeline:</p>
<ol>
<li>Build 阶段(右子树): 负责构建哈希表</li>
<li>Probe 阶段(左子树): 负责探测哈希表</li>
</ol>
<p>Build 阶段必须在 Probe 阶段之前完成, 两个阶段之间有明确的依赖关系. 这种设计确保了在探测哈希表之前, 哈希表已经完全构建完成.</p>
<ol start="7">
<li>PhysicalTableScan 操作符</li>
</ol>
<p>Join 操作符经过几个投影算子 + PhysicalFilter 算子以后就来到了 PhysicalTableScan 操作符, PhysicalTableScan 只是一个 Source 算子, 使用默认的 BuildPipelines() 逻辑:</p>
<img src="/images/duckdb-executor_4.svg" alt="DuckDB执行计划图4" style="max-width: 100%; height: auto; width: 200px;">
<p>这条 SQL 在当前物理计划下有 4 个 MetaPipelines:</p>
<p>1: ExplainAnalyze (顶层 Sink+Source, 用于统计与包装输出).
2: TopN (最终排序与限流的 Sink + Source, 承接聚合结果).
3: PerfectHashGroupBy (聚合 Sink, 承接 Join 后的数据).
4: HashJoin.</p>
<p>MetaPipelines 之间是存在依赖关系，依赖关系和构建顺序是相反的，父 MetaPipeline 依赖子 MetaPipeline，依赖关系的构建时机是在父 MetaPipeline 构建子 MetaPipeline 时确立:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">MetaPipeline</span> <span class="o">&amp;</span><span class="n">MetaPipeline</span><span class="o">::</span><span class="n">CreateChildMetaPipeline</span><span class="p">(</span><span class="n">Pipeline</span> <span class="o">&amp;</span><span class="n">current</span><span class="p">,</span> <span class="n">PhysicalOperator</span> <span class="o">&amp;</span><span class="n">op</span><span class="p">,</span> <span class="n">MetaPipelineType</span> <span class="n">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">children</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">make_shared_ptr</span><span class="o">&lt;</span><span class="n">MetaPipeline</span><span class="o">&gt;</span><span class="p">(</span><span class="n">executor</span><span class="p">,</span> <span class="n">state</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">op</span><span class="p">,</span> <span class="n">type</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="o">&amp;</span><span class="n">child_meta_pipeline</span> <span class="o">=</span> <span class="o">*</span><span class="n">children</span><span class="p">.</span><span class="n">back</span><span class="p">().</span><span class="n">get</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// store the parent
</span></span></span><span class="line"><span class="cl">	<span class="n">child_meta_pipeline</span><span class="p">.</span><span class="n">parent</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">current</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// child MetaPipeline must finish completely before this MetaPipeline can start
</span></span></span><span class="line"><span class="cl">	<span class="cm">/* 当前的 pipeline 依赖子 MetaPipeline 的 base pipeline 完成. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">current</span><span class="p">.</span><span class="n">AddDependency</span><span class="p">(</span><span class="n">child_meta_pipeline</span><span class="p">.</span><span class="n">GetBasePipeline</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// child meta pipeline is part of the recursive CTE too
</span></span></span><span class="line"><span class="cl">	<span class="n">child_meta_pipeline</span><span class="p">.</span><span class="n">recursive_cte</span> <span class="o">=</span> <span class="n">recursive_cte</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="n">child_meta_pipeline</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="pipeline-任务调度和执行">Pipeline 任务调度和执行</h3>
<p>从 PhysicalTableScan 开始, Source 算子每次产出一批数据, 单位就是 DataChunk, 中间算子按批消费/产生 DataChunk. Sink 算子消费 DataChunk, 如果它同时充当 Source 时, 再以 DataChunk 形式向下游提供结果.</p>
<p>在构建完成 Pipelines 流以后, DuckDB 开始进入 Pipelines 的执行流程:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">Executor</span><span class="o">::</span><span class="n">InitializeInternal</span><span class="p">(</span><span class="n">PhysicalOperator</span> <span class="o">&amp;</span><span class="n">plan</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 从当前 ClientContext 获取全局的任务调度器. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="o">&amp;</span><span class="n">scheduler</span> <span class="o">=</span> <span class="n">TaskScheduler</span><span class="o">::</span><span class="n">GetScheduler</span><span class="p">(</span><span class="n">context</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">lock_guard</span><span class="o">&lt;</span><span class="n">mutex</span><span class="o">&gt;</span> <span class="n">elock</span><span class="p">(</span><span class="n">executor_lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">physical_plan</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">plan</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">this</span><span class="o">-&gt;</span><span class="n">profiler</span> <span class="o">=</span> <span class="n">ClientData</span><span class="o">::</span><span class="n">Get</span><span class="p">(</span><span class="n">context</span><span class="p">).</span><span class="n">profiler</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="n">profiler</span><span class="o">-&gt;</span><span class="n">Initialize</span><span class="p">(</span><span class="n">plan</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">this</span><span class="o">-&gt;</span><span class="n">producer</span> <span class="o">=</span> <span class="n">scheduler</span><span class="p">.</span><span class="n">CreateProducer</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 构建所有的 pipelines. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">PipelineBuildState</span> <span class="n">state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 从 root 的 MetaPipeline 开始. */</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">root_pipeline</span> <span class="o">=</span> <span class="n">make_shared_ptr</span><span class="o">&lt;</span><span class="n">MetaPipeline</span><span class="o">&gt;</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">,</span> <span class="n">state</span><span class="p">,</span> <span class="k">nullptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 调用 Build 从物理计划根节点递归构建所有相关的 Pipeline,并按 Pipeline 构建原则建立 Pipeline Breaker 与依赖关系. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">root_pipeline</span><span class="o">-&gt;</span><span class="n">Build</span><span class="p">(</span><span class="o">*</span><span class="n">physical_plan</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 整个 MetaPipeline 树上的所有 Pipeline 切换到 &#34;可执行态&#34;, 并将每条 Pipeline 的算子链调整为执行所需的顺序. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">root_pipeline</span><span class="o">-&gt;</span><span class="n">Ready</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// set root pipelines, i.e., all pipelines that end in the final sink
</span></span></span><span class="line"><span class="cl">		<span class="n">root_pipeline</span><span class="o">-&gt;</span><span class="n">GetPipelines</span><span class="p">(</span><span class="n">root_pipelines</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">root_pipeline_idx</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// collect all meta-pipelines from the root pipeline
</span></span></span><span class="line"><span class="cl">		<span class="cm">/* 收集所有待调度的 MetaPipeline (递归搜索). */</span>
</span></span><span class="line"><span class="cl">		<span class="n">vector</span><span class="o">&lt;</span><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">MetaPipeline</span><span class="o">&gt;&gt;</span> <span class="n">to_schedule</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="n">root_pipeline</span><span class="o">-&gt;</span><span class="n">GetMetaPipelines</span><span class="p">(</span><span class="n">to_schedule</span><span class="p">,</span> <span class="nb">true</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// number of &#39;PipelineCompleteEvent&#39;s is equal to the number of meta pipelines, so we have to set it here
</span></span></span><span class="line"><span class="cl">		<span class="n">total_pipelines</span> <span class="o">=</span> <span class="n">to_schedule</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// collect all pipelines from the root pipelines (recursively) for the progress bar and verify them
</span></span></span><span class="line"><span class="cl">		<span class="cm">/* 收集所有待调度的 pipelines (递归搜索). */</span>
</span></span><span class="line"><span class="cl">		<span class="n">root_pipeline</span><span class="o">-&gt;</span><span class="n">GetPipelines</span><span class="p">(</span><span class="n">pipelines</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="c1">// finally, verify and schedule
</span></span></span><span class="line"><span class="cl">		<span class="n">VerifyPipelines</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* 为所有 MetaPipeline 及其内的 Pipeline 构建 &#34;事件调度栈&#34; (Event Stack). */</span>
</span></span><span class="line"><span class="cl">		<span class="n">ScheduleEvents</span><span class="p">(</span><span class="n">to_schedule</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>DuckDB 采用一个事件驱动的模型来完成 Pipeline 的执行, Pipeline Event 和 PipelineTask 是两个核心组件, 它们协同工作来实现高效的查询执行.</p>
<p>一个 Pipeline 对应多个 Event:</p>
<ul>
<li>PipelineInitializeEvent: 负责初始化 Pipeline</li>
<li>PipelineEvent: 负责调度 Pipeline 的主要执行任务</li>
<li>PipelinePrepareFinishEvent: 负责准备完成阶段</li>
<li>PipelineFinishEvent: 负责完成阶段</li>
<li>PipelineCompleteEvent: 负责整个 Pipeline 的完成</li>
</ul>
<p>ScheduleEvents() 是将构建完成的 Pipelines 转换为事件驱动执行的核心函数, 每条 Pipeline 都会生成一个事件栈 (关于 PipelineEvent 的一个组合), 依赖关系是:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="c1">// dependencies: initialize -&gt; event -&gt; prepare finish -&gt; finish -&gt; complete
</span></span></span><span class="line"><span class="cl"><span class="n">base_stack</span><span class="p">.</span><span class="n">pipeline_event</span><span class="p">.</span><span class="n">AddDependency</span><span class="p">(</span><span class="n">base_stack</span><span class="p">.</span><span class="n">pipeline_initialize_event</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">base_stack</span><span class="p">.</span><span class="n">pipeline_prepare_finish_event</span><span class="p">.</span><span class="n">AddDependency</span><span class="p">(</span><span class="n">base_stack</span><span class="p">.</span><span class="n">pipeline_event</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">base_stack</span><span class="p">.</span><span class="n">pipeline_finish_event</span><span class="p">.</span><span class="n">AddDependency</span><span class="p">(</span><span class="n">base_stack</span><span class="p">.</span><span class="n">pipeline_prepare_finish_event</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">base_stack</span><span class="p">.</span><span class="n">pipeline_complete_event</span><span class="p">.</span><span class="n">AddDependency</span><span class="p">(</span><span class="n">base_stack</span><span class="p">.</span><span class="n">pipeline_finish_event</span><span class="p">);</span></span></span></code></pre></div><p>每个 PipelineEvent 通过以下流程创建 PipelineTask:</p>
<ol>
<li>调用 PipelineEvent::Schedule() 方法</li>
<li>调用 Pipeline::Schedule() 方法</li>
<li>判断是否可以并行执行:
<ul>
<li>如果可以并行, 则调用 LaunchScanTasks() 创建多个 PipelineTask</li>
<li>如果不能并行, 则调用 ScheduleSequentialTask() 创建单个 PipelineTask</li>
</ul>
</li>
<li>通过 event-&gt;SetTasks() 将创建的PipelineTask提交给TaskScheduler</li>
</ol>
<p>因为有一部分算子其实是可以并行执行的, 所以 Pipeline 和 PipelineTask 并不是完全一一对应的关系, 一个 Pipeline 可以根据估算拆分多个 PipelineTask 并行执行:</p>
<p>比如 PhysicalTableScan 算子所在的 Pipeline:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">Pipeline <span class="o">(</span>扫描大表<span class="o">)</span>
</span></span><span class="line"><span class="cl">├── Source: PhysicalTableScan <span class="o">(</span>估算4个并行分片<span class="o">)</span>
</span></span><span class="line"><span class="cl">├── Operators: <span class="o">[</span>...<span class="o">]</span>
</span></span><span class="line"><span class="cl">└── Sink: <span class="o">[</span>...<span class="o">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">执行时分解为:
</span></span><span class="line"><span class="cl">PipelineTask-1 <span class="o">(</span>处理分片1<span class="o">)</span>
</span></span><span class="line"><span class="cl">PipelineTask-2 <span class="o">(</span>处理分片2<span class="o">)</span>  
</span></span><span class="line"><span class="cl">PipelineTask-3 <span class="o">(</span>处理分片3<span class="o">)</span>
</span></span><span class="line"><span class="cl">PipelineTask-4 <span class="o">(</span>处理分片4<span class="o">)</span></span></span></code></pre></div><ul>
<li>一个 Pipeline 可以对应多个 PipelineTask.</li>
<li>共享内容: PipelineTask 共享同一条 Pipeline 的算子链和必要的全局状态，但每个任务都会维护独立的本地 Source/Operator/Sink 状态。</li>
</ul>
<p>PipelineTask 的执行:</p>
<ol>
<li>PipelineTask 继承自 ExecutorTask, 负责实际执行 Pipeline 工作</li>
<li>创建PipelineExecutor: 每个 PipelineTask 会创建一个 PipelineExecutor 实例</li>
<li>执行Pipeline: 调用 PipelineExecutor 执行具体的查询逻辑</li>
<li>执行完成后调用 event-&gt;FinishTask() 通知 PipelineEvent</li>
</ol>
<h3 id="pipeline-并行执行">Pipeline 并行执行</h3>
<p>我们已经探讨了 Pipeline 的整个生成流程，了解了 Pipeline 如何被分解为多个 PipelineTask，并通过 PipelineEvent 进行调度管理。这些 PipelineTask 最终由 DuckDB 的 TaskScheduler 来真正执行。</p>
<p>DuckDB 在启动时创建一个全局的 TaskScheduler, 创建的 Worker 数量是参数决定的:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">scheduler</span><span class="o">-&gt;</span><span class="n">SetThreads</span><span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="n">options</span><span class="p">.</span><span class="n">maximum_threads</span><span class="p">,</span> <span class="n">config</span><span class="p">.</span><span class="n">options</span><span class="p">.</span><span class="n">external_threads</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">scheduler</span><span class="o">-&gt;</span><span class="n">RelaunchThreads</span><span class="p">();</span></span></span></code></pre></div><p><img src="/images/duckdb-executor_5.png" alt="duckdb-executor_5.png"></p>
<p>每个 Worker 线程调度函数 <code>TaskScheduler::ExecuteForever()</code> 处理全局任务队列中的任务, 任务队列是一个 multi-producer, multi-consumer 线程安全的无锁队列.
需要注意的是使用一个 multi-producer, multi-consumer 线程安全的无锁队列, 多个工作线程可以从同一个队列中获取任务, 所以这里隐含了负载均衡.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">TaskScheduler</span><span class="o">::</span><span class="n">ExecuteForever</span><span class="p">(</span><span class="n">atomic</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="o">*</span><span class="n">marker</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="cp">#ifndef DUCKDB_NO_THREADS
</span></span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="k">constexpr</span> <span class="k">const</span> <span class="kt">int64_t</span> <span class="n">INITIAL_FLUSH_WAIT</span> <span class="o">=</span> <span class="mi">500000</span><span class="p">;</span> <span class="c1">// initial wait time of 0.5s (in mus) before flushing
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="o">&amp;</span><span class="n">config</span> <span class="o">=</span> <span class="n">DBConfig</span><span class="o">::</span><span class="n">GetConfig</span><span class="p">(</span><span class="n">db</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">Task</span><span class="o">&gt;</span> <span class="n">task</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 线程主循环. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">while</span> <span class="p">(</span><span class="o">*</span><span class="n">marker</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">queue</span><span class="o">-&gt;</span><span class="n">Dequeue</span><span class="p">(</span><span class="n">task</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="cm">/* 从无锁任务队列中获取任务. */</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">process_mode</span> <span class="o">=</span> <span class="n">config</span><span class="p">.</span><span class="n">options</span><span class="p">.</span><span class="n">scheduler_process_partial</span> <span class="o">?</span> <span class="n">TaskExecutionMode</span><span class="o">::</span><span class="nl">PROCESS_PARTIAL</span>
</span></span><span class="line"><span class="cl">			                                                             <span class="p">:</span> <span class="n">TaskExecutionMode</span><span class="o">::</span><span class="n">PROCESS_ALL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="cm">/* 执行任务. */</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">execute_result</span> <span class="o">=</span> <span class="n">task</span><span class="o">-&gt;</span><span class="n">Execute</span><span class="p">(</span><span class="n">process_mode</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">			<span class="cm">/* 根据执行结果处理任务. */</span>
</span></span><span class="line"><span class="cl">			<span class="k">switch</span> <span class="p">(</span><span class="n">execute_result</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">case</span> <span class="n">TaskExecutionResult</span><span class="o">::</span><span class="nl">TASK_FINISHED</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">			<span class="k">case</span> <span class="n">TaskExecutionResult</span><span class="o">::</span><span class="nl">TASK_ERROR</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">				<span class="n">task</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>  <span class="cm">/* 任务完成, 释放资源. */</span>
</span></span><span class="line"><span class="cl">				<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="k">case</span> <span class="n">TaskExecutionResult</span><span class="o">::</span><span class="nl">TASK_NOT_FINISHED</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="cm">/* 任务未完成, 重新入队. */</span>
</span></span><span class="line"><span class="cl">				<span class="k">auto</span> <span class="o">&amp;</span><span class="n">token</span> <span class="o">=</span> <span class="o">*</span><span class="n">task</span><span class="o">-&gt;</span><span class="n">token</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="n">queue</span><span class="o">-&gt;</span><span class="n">Enqueue</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">task</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">				<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">			<span class="k">case</span> <span class="n">TaskExecutionResult</span><span class="o">::</span><span class="nl">TASK_BLOCKED</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">				<span class="cm">/* 任务被阻塞, 取消调度. */</span>
</span></span><span class="line"><span class="cl">				<span class="n">task</span><span class="o">-&gt;</span><span class="n">Deschedule</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">				<span class="n">task</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">				<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">queue</span><span class="o">-&gt;</span><span class="n">GetTasksInQueue</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// failed to dequeue but there are still tasks remaining - signal again to retry
</span></span></span><span class="line"><span class="cl">			<span class="n">queue</span><span class="o">-&gt;</span><span class="n">semaphore</span><span class="p">.</span><span class="n">signal</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>Pipeline 执行的核心函数是 <code>PipelineExecutor::Execute()</code>, 负责驱动数据从源到终点的完整流程:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">PipelineExecuteResult</span> <span class="n">PipelineExecutor</span><span class="o">::</span><span class="n">Execute</span><span class="p">(</span><span class="n">idx_t</span> <span class="n">max_chunks</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 确保当前 Pipeline 具有 Sink (数据接收端). */</span>
</span></span><span class="line"><span class="cl">	<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">pipeline</span><span class="p">.</span><span class="n">sink</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 获取源数据, 如果这个 Pipeline 没有中间操作符, 直接使用 final_chunk,
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * 否则使用第一个中间数据块 intermediate_chunks[0]. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="o">&amp;</span><span class="n">source_chunk</span> <span class="o">=</span> <span class="n">pipeline</span><span class="p">.</span><span class="n">operators</span><span class="p">.</span><span class="n">empty</span><span class="p">()</span> <span class="o">?</span> <span class="nl">final_chunk</span> <span class="p">:</span> <span class="o">*</span><span class="n">intermediate_chunks</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ExecutionBudget 用来限制单次调用 Execute() 最多处理多少个 Chunk 的数据, 超过以后会结束这轮任务. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">ExecutionBudget</span> <span class="nf">chunk_budget</span><span class="p">(</span><span class="n">max_chunks</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 主执行循环. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">do</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 检查客户端是否已中断. */</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">context</span><span class="p">.</span><span class="n">client</span><span class="p">.</span><span class="n">interrupted</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">throw</span> <span class="nf">InterruptException</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 根据不同状态选择执行路径, 这些状态符 (exhausted_source, done_flushing..) 初始化都是 false. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">OperatorResultType</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">exhausted_source</span> <span class="o">&amp;&amp;</span> <span class="n">done_flushing</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">remaining_sink_chunk</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">next_batch_blocked</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">		    <span class="n">in_process_operators</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="cm">/* 所有条件都满足, 退出循环. */</span>
</span></span><span class="line"><span class="cl">			<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">remaining_sink_chunk</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// The pipeline was interrupted by the Sink. We should retry sinking the final chunk.
</span></span></span><span class="line"><span class="cl">			<span class="cm">/* Sink 操作符之前是被阻塞的, 重新尝试将最终数据块推送到 Sink. */</span>
</span></span><span class="line"><span class="cl">			<span class="n">result</span> <span class="o">=</span> <span class="n">ExecutePushInternal</span><span class="p">(</span><span class="n">final_chunk</span><span class="p">,</span> <span class="n">chunk_budget</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">result</span> <span class="o">!=</span> <span class="n">OperatorResultType</span><span class="o">::</span><span class="n">HAVE_MORE_OUTPUT</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="n">remaining_sink_chunk</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="o">!</span><span class="n">in_process_operators</span><span class="p">.</span><span class="n">empty</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">started_flushing</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// Operator(s) in the pipeline have returned `HAVE_MORE_OUTPUT` in the last Execute call
</span></span></span><span class="line"><span class="cl">			<span class="c1">// the operators have to be called with the same input chunk to produce the rest of the output
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">			<span class="cm">/* PhysicalOperator 在上次执行中返回了 HAVE_MORE_OUTPUT, 需要再次调用这个操作符产生剩下的输出结果. */</span>
</span></span><span class="line"><span class="cl">			<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">source_chunk</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="n">result</span> <span class="o">=</span> <span class="n">ExecutePushInternal</span><span class="p">(</span><span class="n">source_chunk</span><span class="p">,</span> <span class="n">chunk_budget</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">exhausted_source</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">next_batch_blocked</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">done_flushing</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// The source was exhausted, try flushing all operators
</span></span></span><span class="line"><span class="cl">			<span class="cm">/* 数据源已消费完, 尝试刷新支持缓存的操作符. */</span>
</span></span><span class="line"><span class="cl">			<span class="k">auto</span> <span class="n">flush_completed</span> <span class="o">=</span> <span class="n">TryFlushCachingOperators</span><span class="p">(</span><span class="n">chunk_budget</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="n">flush_completed</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="n">done_flushing</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="k">if</span> <span class="p">(</span><span class="n">remaining_sink_chunk</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="k">return</span> <span class="n">PipelineExecuteResult</span><span class="o">::</span><span class="n">INTERRUPTED</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">chunk_budget</span><span class="p">.</span><span class="n">IsDepleted</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">					<span class="k">return</span> <span class="n">PipelineExecuteResult</span><span class="o">::</span><span class="n">NOT_FINISHED</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="p">}</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="o">!</span><span class="n">exhausted_source</span> <span class="o">||</span> <span class="n">next_batch_blocked</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="cm">/* 通过 Pipeline 的 Source PhysicalOperator 获取新数据并处理. */</span>
</span></span><span class="line"><span class="cl">			<span class="n">SourceResultType</span> <span class="n">source_result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">next_batch_blocked</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="c1">// &#34;Regular&#34; path: fetch a chunk from the source and push it through the pipeline
</span></span></span><span class="line"><span class="cl">				<span class="n">source_chunk</span><span class="p">.</span><span class="n">Reset</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">				<span class="n">source_result</span> <span class="o">=</span> <span class="n">FetchFromSource</span><span class="p">(</span><span class="n">source_chunk</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">				<span class="k">if</span> <span class="p">(</span><span class="n">source_result</span> <span class="o">==</span> <span class="n">SourceResultType</span><span class="o">::</span><span class="n">BLOCKED</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="cm">/* 如果数据源被阻塞, 返回中断状态. */</span>
</span></span><span class="line"><span class="cl">					<span class="k">return</span> <span class="n">PipelineExecuteResult</span><span class="o">::</span><span class="n">INTERRUPTED</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">				<span class="cm">/* 如果数据源已经消费完成, 标记 FINISHED. */</span>
</span></span><span class="line"><span class="cl">				<span class="k">if</span> <span class="p">(</span><span class="n">source_result</span> <span class="o">==</span> <span class="n">SourceResultType</span><span class="o">::</span><span class="n">FINISHED</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="n">exhausted_source</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="p">}</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="n">required_partition_info</span><span class="p">.</span><span class="n">AnyRequired</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="k">auto</span> <span class="n">next_batch_result</span> <span class="o">=</span> <span class="n">NextBatch</span><span class="p">(</span><span class="n">source_chunk</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">				<span class="n">next_batch_blocked</span> <span class="o">=</span> <span class="n">next_batch_result</span> <span class="o">==</span> <span class="n">SinkNextBatchType</span><span class="o">::</span><span class="n">BLOCKED</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="k">if</span> <span class="p">(</span><span class="n">next_batch_blocked</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">					<span class="k">return</span> <span class="n">PipelineExecuteResult</span><span class="o">::</span><span class="n">INTERRUPTED</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">				<span class="p">}</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="n">exhausted_source</span> <span class="o">&amp;&amp;</span> <span class="n">source_chunk</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="c1">// To ensure that we&#39;re not early-terminating the pipeline
</span></span></span><span class="line"><span class="cl">				<span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">			<span class="cm">/* 将数据块推送到 Pipeline 中的 PhysicalOperator 处理. */</span>
</span></span><span class="line"><span class="cl">			<span class="n">result</span> <span class="o">=</span> <span class="n">ExecutePushInternal</span><span class="p">(</span><span class="n">source_chunk</span><span class="p">,</span> <span class="n">chunk_budget</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">throw</span> <span class="nf">InternalException</span><span class="p">(</span><span class="s">&#34;Unexpected state reached in pipeline executor&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">==</span> <span class="n">OperatorResultType</span><span class="o">::</span><span class="n">BLOCKED</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="cm">/* 处理 Sink 操作中断, 如果 Sink 操作符被阻塞, 设置 remaining_sink_chunk = true, 返回中断状态. */</span>
</span></span><span class="line"><span class="cl">			<span class="n">remaining_sink_chunk</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="n">PipelineExecuteResult</span><span class="o">::</span><span class="n">INTERRUPTED</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="o">==</span> <span class="n">OperatorResultType</span><span class="o">::</span><span class="n">FINISHED</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="cm">/* 整个 Pipeline 已经处理完成, 直接返回. */</span>
</span></span><span class="line"><span class="cl">			<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">chunk_budget</span><span class="p">.</span><span class="n">Next</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 检查 Pipeline 是否完成:
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * 1. Source 数据尚未被完全消费.
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * 2. 缓存操作符的刷新操作没有执行.
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * 3. 整个 Pipeline 还没有完成. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">((</span><span class="o">!</span><span class="n">exhausted_source</span> <span class="o">||</span> <span class="o">!</span><span class="n">done_flushing</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">IsFinished</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="n">PipelineExecuteResult</span><span class="o">::</span><span class="n">NOT_FINISHED</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 整个 Pipeline 已经完成, 执行最终化操作. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nf">PushFinalize</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>用户的主线程在将查询分解成多个 PipelineTask 下发给 TaskScheduler 后, 并不会陷入空闲等待状态, 而是负责监控整个查询任务的执行过程 PendingQueryResult::ExecuteInternal(), 其中除了任务过程中的异常处理和协调任务完成的获取查询结果 (ClientContext::FetchResultInternal()), 主线程也会择机选择 TaskScheduler 中属于自身查询的 Task 来执行.</p>
<h2 id="总结">总结</h2>
<p>DuckDB 的执行器概念和细节繁多，但是 DuckDB 的开发者将整个执行流程进行了清晰的抽象，各个模块互相交互又不至于耦合过深，这体现了非常优秀的架构设计能力。学习 DuckDB 的代码设计，不仅仅是为了理解执行器的整个流程，对于系统工程师来说学习这种设计理念更为重要:</p>
<ol>
<li>
<p>流水线的并行化：在系统架构中，Worker Pool 是一个常见的设计，但是大部分的 Worker Pool 都是以 Task 为运行粒度，Task 内部并不会再次拆分，这一方面无法利用多核处理器，在内存方面需要一次性加载所有的数据。DuckDB 将一个 Task 抽象成完全的流水线，在处理一个复杂的 SQL 时，比如多表连接，将其进一步拆分成多个小任务，使用 DAG 来组织任务，整个处理过程就变得清晰可控。拆分成流水线以后，DuckDB 还能将复杂的算子，比如一个 SCAN 操作进行并行的处理。</p>
</li>
<li>
<p>负载均衡：DuckDB 使用无锁队列直接就解决了任务分配的负载问题，共享队列模型本质上避免了 stealing 的需要，并且整个调度器的线程数量也可以动态的根据负载进行调整，尽可能的充分利用计算资源。</p>
</li>
<li>
<p>精细的任务控制：DuckDB 支持将小任务进行更精细化的拆分：</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">enum</span> <span class="k">class</span> <span class="nc">TaskExecutionMode</span> <span class="o">:</span> <span class="kt">uint8_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">PROCESS_ALL</span><span class="p">,</span> <span class="cm">/* 完全执行任务。 */</span>
</span></span><span class="line"><span class="cl">	<span class="n">PROCESS_PARTIAL</span> <span class="cm">/* 部分执行任务。 */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>DuckDB 通过 <code>scheduler_process_partial</code> 配置选项来控制默认的执行模式，默认情况下，这个选项是 <code>false</code>，但在某些验证模式下可以设置为 <code>true</code>。在大多数场景下，DuckDB 的 TaskScheduler 都是使用 <code>PROCESS_ALL</code> 模式，但是主线程（用户线程）是使用 <code>PROCESS_PARTIAL</code>，如果使用了 <code>PROCESS_PARTIAL</code> mode，通过 ExecutionBudget 会限制每批处理的 Chunk 数量，主线程还有协调任务和监控异常的额外工作，所以长期陷入任务执行是不友好的设计。</p>
<ol start="4">
<li>依赖关系的处理：DuckDB 引入了 MetaPipeline 的概念，代表一组具有相同 Sink 的流水线，这种设计允许更精细地管理复杂查询中多个流水线之间的依赖关系。流水线内部依赖：同一 MetaPipeline 内不同流水线之间的依赖，流水线间依赖：不同 MetaPipeline 之间的依赖，特殊操作符依赖：如 Join 操作的构建侧和探测侧之间的依赖。整个依赖体系层层递进，实现较为优雅.</li>
</ol>
]]></content>
  </entry><entry>
    <title>MySQL 参数 innodb_force_recovery 的使用和风险点</title>
    <link href="https://leviathan.vip/2025/09/10/innodb-srv-force-recovery/" />
    <id>https://leviathan.vip/2025/09/10/innodb-srv-force-recovery/</id>
    <updated>2025-09-10T14:03:27Z</updated>
    <summary type="html"><![CDATA[<h2 id="背景">背景</h2>
<p>在使用 InnoDB 存储引擎的 MySQL 环境中, <code>innodb_force_recovery</code> 是排障过程中十分关键的参数:</p>
<ol>
<li>对于业务连续性要求极高的场景, 长时间停机往往难以接受. 适当设置 <code>innodb_force_recovery</code> 能够帮助实例快速恢复, 临时维持服务能力.</li>
<li>当遭遇严重的 MySQL 内核 Bug, 例如数据页 (Page) 或 Undo Log 损坏时, 常规的 crash recovery 可能无法完成, 导致实例无法启动. 此时借助该参数可以让数据库以“带病”模式运行.</li>
<li><code>innodb_force_recovery</code> 的取值范围为 0-6 (默认 0), 数值越大代表跳过的恢复步骤越多, 高级别也包含低级别的功能. 例如设置为 1 表示跳过损坏数据页, 设置为 2 则在此基础上阻止部分后台线程 (如 master thread 与 purge thread) 启动.</li>
</ol>
<h2 id="参数-innodb_force_recovery">参数 innodb_force_recovery</h2>
<p>参数 innodb_force_recovery 的使用方式非常直接, 在配置文件中新增或调整该参数即可:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="o">[</span>mysqld<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">innodb_force_recovery</span> <span class="o">=</span> <span class="m">1</span></span></span></code></pre></div><p>在 MySQL 8.0 版本中, 只要 innodb_force_recovery 大于 0, 所有已有表的 ALTER TABLE 等 DDL 操作都会被禁止:</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="背景">背景</h2>
<p>在使用 InnoDB 存储引擎的 MySQL 环境中, <code>innodb_force_recovery</code> 是排障过程中十分关键的参数:</p>
<ol>
<li>对于业务连续性要求极高的场景, 长时间停机往往难以接受. 适当设置 <code>innodb_force_recovery</code> 能够帮助实例快速恢复, 临时维持服务能力.</li>
<li>当遭遇严重的 MySQL 内核 Bug, 例如数据页 (Page) 或 Undo Log 损坏时, 常规的 crash recovery 可能无法完成, 导致实例无法启动. 此时借助该参数可以让数据库以“带病”模式运行.</li>
<li><code>innodb_force_recovery</code> 的取值范围为 0-6 (默认 0), 数值越大代表跳过的恢复步骤越多, 高级别也包含低级别的功能. 例如设置为 1 表示跳过损坏数据页, 设置为 2 则在此基础上阻止部分后台线程 (如 master thread 与 purge thread) 启动.</li>
</ol>
<h2 id="参数-innodb_force_recovery">参数 innodb_force_recovery</h2>
<p>参数 innodb_force_recovery 的使用方式非常直接, 在配置文件中新增或调整该参数即可:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="o">[</span>mysqld<span class="o">]</span>
</span></span><span class="line"><span class="cl"><span class="nv">innodb_force_recovery</span> <span class="o">=</span> <span class="m">1</span></span></span></code></pre></div><p>在 MySQL 8.0 版本中, 只要 innodb_force_recovery 大于 0, 所有已有表的 ALTER TABLE 等 DDL 操作都会被禁止:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ERROR <span class="m">1881</span> <span class="o">(</span>HY000<span class="o">)</span>: Operation not allowed when innodb_force_recovery &gt; 0.</span></span></code></pre></div><ul>
<li>当 innodb_force_recovery 小于等于 3 时, 仍允许执行 DROP TABLE 与 CREATE TABLE.</li>
<li>当 innodb_force_recovery 小于 4 时, DROP TABLE 操作仍可执行.</li>
<li>当该参数设置为 4 或更高值时, InnoDB 会被迫进入只读模式, 包括 DROP TABLE、CREATE TABLE 在内的所有 DDL 操作都会被拒绝.</li>
</ul>
<h3 id="6-个恢复级别详解">6 个恢复级别详解</h3>
<h4 id="innodb_force_recovery--0">innodb_force_recovery = 0</h4>
<ul>
<li><strong>作用</strong>: 正常模式, 不进行强制恢复.</li>
<li><strong>使用场景</strong>: 默认值, 正常运行时使用.</li>
<li><strong>风险</strong>: 无风险.</li>
<li><strong>注意事项</strong>: 无.</li>
</ul>
<h4 id="innodb_force_recovery--1-srv_force_ignore_corrupt">innodb_force_recovery = 1 (SRV_FORCE_IGNORE_CORRUPT)</h4>
<ul>
<li><strong>作用</strong>: 在 crash recovery 阶段不做额外处理, 数据库启动后跳过损坏的数据页.</li>
<li><strong>使用场景</strong>: 适用于检测到个别数据页损坏、但整体表空间仍可访问的场景.</li>
<li><strong>风险</strong>: 逻辑上依赖被跳过数据页的业务可能报错或返回不完整数据.</li>
</ul>
<h4 id="innodb_force_recovery--2-srv_force_no_background">innodb_force_recovery = 2 (SRV_FORCE_NO_BACKGROUND)</h4>
<ul>
<li><strong>作用</strong>: 阻止后台线程启动, 包括:
<ul>
<li>master 线程</li>
<li>undo log purge 线程</li>
</ul>
</li>
<li><strong>使用场景</strong>: undo log purge 过程中频繁崩溃, 或后台线程运行导致实例无法恢复.</li>
<li><strong>风险</strong>: undo log 堆积, 长期运行会造成空间占用与性能下降.</li>
</ul>
<h4 id="innodb_force_recovery--3-srv_force_no_trx_undo">innodb_force_recovery = 3 (SRV_FORCE_NO_TRX_UNDO)</h4>
<ul>
<li><strong>作用</strong>: 禁止执行事务回滚, 忽略未提交事务对应的 undo log.</li>
<li><strong>使用场景</strong>: 在事务回滚阶段反复崩溃、或 undo log 本身存在损坏时使用.</li>
<li><strong>风险</strong>: 未提交事务无法回滚, 相关表数据可能出现不一致或脏读.</li>
</ul>
<h4 id="innodb_force_recovery--4-srv_force_no_ibuf_merge">innodb_force_recovery = 4 (SRV_FORCE_NO_IBUF_MERGE)</h4>
<ul>
<li><strong>作用</strong>:
<ul>
<li>阻止 ibuf 合并操作</li>
<li>将 InnoDB 强制切换到只读模式</li>
</ul>
</li>
<li><strong>使用场景</strong>: ibuf 合并过程中实例崩溃, 或二级索引页损坏.</li>
<li><strong>风险</strong>:
<ul>
<li>所有写入被禁止, 只能导出数据.</li>
<li>二级索引可能不完整, 后续需要全部重建以消除隐患.</li>
</ul>
</li>
</ul>
<h4 id="innodb_force_recovery--5-srv_force_no_undo_log_scan">innodb_force_recovery = 5 (SRV_FORCE_NO_UNDO_LOG_SCAN)</h4>
<ul>
<li><strong>作用</strong>:
<ul>
<li>启动时跳过 undo log 扫描, 未提交事务在启动后会被视为已经提交.</li>
<li>在 crash 前尚未完成的 DDL 不再回滚; 自 MySQL 8.0 起 DDL 为原子操作, 但此级别可能遗留临时文件.</li>
<li>InnoDB 维持只读状态.</li>
</ul>
</li>
<li><strong>使用场景</strong>: undo log 文件损坏或扫描 undo log 会触发崩溃.</li>
<li><strong>风险</strong>: 数据可能出现严重不一致, 需尽快导出并校验.</li>
<li><strong>注意事项</strong>: 启动后请尽量只做只读操作, 立即备份关键数据.</li>
</ul>
<h4 id="innodb_force_recovery--6-srv_force_no_log_redo">innodb_force_recovery = 6 (SRV_FORCE_NO_LOG_REDO)</h4>
<ul>
<li><strong>作用</strong>: 阻止 redo log 前滚, 在 crash recovery 阶段几乎跳过所有操作.</li>
<li><strong>使用场景</strong>: 当 1-5 级别均无法启动实例时的最后尝试, 仅用于导出数据.</li>
<li><strong>风险</strong>: 已提交但未刷盘的事务全部丢失且不可恢复, 数据完整性无法保证.</li>
<li><strong>注意事项</strong>: 启动后立即导出数据并计划重建实例, 切勿继续在该实例上承载业务.</li>
</ul>
<p><strong>innodb_force_recovery 的使用策略应当以 1 为起点逐级调高, 或在明确问题根源的情况下直接选择对应级别来跳过相关步骤.</strong></p>
<h2 id="数据恢复">数据恢复</h2>
<p>无论 innodb_force_recovery 设置为何值, 它都无法真正修复数据, 只是暂时绕过错误让实例得以启动. 一旦确认存在数据损坏或其他严重内核 Bug, 在成功拉起实例后仍需第一时间完成数据恢复与校验.</p>
<h3 id="使用-mysqldump-工具备份数据">使用 mysqldump 工具备份数据</h3>
<p>在启用了 innodb_force_recovery 的实例上建议尽量不要进行写操作, 应第一时间完成逻辑备份, 以免损坏进一步扩散。最常见的做法是借助 <code>mysqldump</code> 导出关键库表。可以先运行以下命令快速获取指定库的数据:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">mysqldump --single-transaction --databases db_name &gt; db_name.sql</span></span></code></pre></div><p>常用参数说明如下:</p>
<ul>
<li><code>mysqldump</code>: MySQL 自带的逻辑备份工具, 会以 SQL 的形式导出表结构与数据.</li>
<li><code>--single-transaction</code>: 在同一个事务里读取所有表, 保证逻辑一致性 (仅适用于 InnoDB).</li>
<li><code>--databases db_name</code>: 指定要导出的数据库, 可重复多次; 若要导出单表可使用 <code>db_name tbl_name</code> 的形式.</li>
<li><code>&gt; db_name.sql</code>: 将导出结果重定向到文件, 便于后续数据恢复.</li>
</ul>
<p>导出完成后, 建议先将疑似损坏的表重命名以免覆盖原始文件, 然后再导入备份:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="c1">-- 在 mysql 客户端中执行
</span></span></span><span class="line"><span class="cl"><span class="k">RENAME</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">db_name</span><span class="p">.</span><span class="n">tbl_name</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="n">db_name</span><span class="p">.</span><span class="n">tbl_name_bak</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">SOURCE</span><span class="w"> </span><span class="o">/</span><span class="n">path</span><span class="o">/</span><span class="k">to</span><span class="o">/</span><span class="n">db_name</span><span class="p">.</span><span class="k">sql</span><span class="p">;</span></span></span></code></pre></div><p>如此一来可以保留损坏前的数据文件作为对照, 并通过 <code>SOURCE</code> 命令直接回放刚才导出的 SQL 脚本, 快速恢复表结构与数据。</p>
<h3 id="innodb-表检查">InnoDB 表检查</h3>
<p>无论是在备份之前还是恢复之后, 都可以通过 CHECK TABLE 语句确认表空间是否损坏. 具体语法可参考官方文档: <a href="https://dev.mysql.com/doc/refman/8.0/en/check-table.html">https://dev.mysql.com/doc/refman/8.0/en/check-table.html</a></p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">CHECK</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">tbl_name</span><span class="p">;</span></span></span></code></pre></div><ol>
<li>在执行 CHECK TABLE 之后, 建议对输出结果逐项评估。</li>
<li>如果发现 <code>status</code> 列返回 <code>corrupt</code>, 应结合备份结果、二进制日志或从库数据进行修复与比对, 必要时重建表结构或回放备份, 确保恢复后的数据一致性。</li>
<li>需要注意的是, CHECK TABLE 主要针对页级与索引结构的物理损坏, 对于由业务错误、未提交事务或应用层造成的逻辑不一致无能为力。它也不会检查 .ibd 文件中的空间 ID、索引 ID 等元信息是否与数据字典匹配, 因此面对手动拷贝表空间文件或元数据错位问题时难以及时发出告警。</li>
<li>同时在大表上执行检查时可能会触发表锁或额外 I/O, 不宜在高峰期频繁运行。</li>
</ol>
]]></content>
  </entry><entry>
    <title>DuckDB 源码分析 - Logical Plan 逻辑计划</title>
    <link href="https://leviathan.vip/2025/05/19/duckdb-logical-plan/" />
    <id>https://leviathan.vip/2025/05/19/duckdb-logical-plan/</id>
    <updated>2025-05-19T10:37:31Z</updated>
    <summary type="html"><![CDATA[<h2 id="版本">版本</h2>
<ul>
<li>v1.3-ossivalis</li>
</ul>
<h2 id="背景">背景</h2>
<p>DuckDB 是一个面向分析型工作负载的嵌入式数据库，引擎采用 C++ 编写，整体模块划分清晰，源码注释也非常到位，十分适合作为学习 AP 型数据库的入门项目。为了追踪逻辑计划在引擎中的完整生命周期，本文基于 <code>v1.3-ossivalis</code> 分支，编译 Debug 版本并配合 GDB 进行断点调试，便于观察每个阶段的内部状态。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone -b v1.3-ossivalis https://github.com/duckdb/duckdb
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">GEN</span><span class="o">=</span>ninja make debug -j <span class="m">8</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">./duckdb
</span></span><span class="line"><span class="cl">v1.3.0-dev2068 022f826ddf
</span></span><span class="line"><span class="cl">Enter <span class="s2">&#34;.help&#34;</span> <span class="k">for</span> usage hints.
</span></span><span class="line"><span class="cl">Connected to a transient in-memory database.
</span></span><span class="line"><span class="cl">Use <span class="s2">&#34;.open FILENAME&#34;</span> to reopen on a persistent database.
</span></span><span class="line"><span class="cl">D</span></span></code></pre></div><h2 id="sql-parser">SQL Parser</h2>
<p>DuckDB 使用 PostgreSQL 的语法解析器来完成词法/语法分析阶段，随后借助 DuckDB 内部的 <code>Transformer</code> 将 PostgreSQL 的解析树转换成 DuckDB 的语法树（<code>Parser::ParseQuery</code> 返回的 <code>ParserResult</code>）。这一阶段的职责仅限于构造 SQL 的抽象语法树（AST），但不涉及语义校验。</p>
<p>在 Parser 段落调试时，可以配合 <code>PRAGMA parser</code> 将 SQL 转换成 JSON，或者直接在 <code>Parser::ParseQuery</code> 附近打断点，观察 <code>statements</code> 向量的增长过程。DuckDB 的 AST 会保留原始 SQL 的很多细节(例如关键字位置、原始字符串)。</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="版本">版本</h2>
<ul>
<li>v1.3-ossivalis</li>
</ul>
<h2 id="背景">背景</h2>
<p>DuckDB 是一个面向分析型工作负载的嵌入式数据库，引擎采用 C++ 编写，整体模块划分清晰，源码注释也非常到位，十分适合作为学习 AP 型数据库的入门项目。为了追踪逻辑计划在引擎中的完整生命周期，本文基于 <code>v1.3-ossivalis</code> 分支，编译 Debug 版本并配合 GDB 进行断点调试，便于观察每个阶段的内部状态。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">git clone -b v1.3-ossivalis https://github.com/duckdb/duckdb
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">GEN</span><span class="o">=</span>ninja make debug -j <span class="m">8</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">./duckdb
</span></span><span class="line"><span class="cl">v1.3.0-dev2068 022f826ddf
</span></span><span class="line"><span class="cl">Enter <span class="s2">&#34;.help&#34;</span> <span class="k">for</span> usage hints.
</span></span><span class="line"><span class="cl">Connected to a transient in-memory database.
</span></span><span class="line"><span class="cl">Use <span class="s2">&#34;.open FILENAME&#34;</span> to reopen on a persistent database.
</span></span><span class="line"><span class="cl">D</span></span></code></pre></div><h2 id="sql-parser">SQL Parser</h2>
<p>DuckDB 使用 PostgreSQL 的语法解析器来完成词法/语法分析阶段，随后借助 DuckDB 内部的 <code>Transformer</code> 将 PostgreSQL 的解析树转换成 DuckDB 的语法树（<code>Parser::ParseQuery</code> 返回的 <code>ParserResult</code>）。这一阶段的职责仅限于构造 SQL 的抽象语法树（AST），但不涉及语义校验。</p>
<p>在 Parser 段落调试时，可以配合 <code>PRAGMA parser</code> 将 SQL 转换成 JSON，或者直接在 <code>Parser::ParseQuery</code> 附近打断点，观察 <code>statements</code> 向量的增长过程。DuckDB 的 AST 会保留原始 SQL 的很多细节(例如关键字位置、原始字符串)。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">Parser</span> <span class="nf">parser</span><span class="p">(</span><span class="n">db</span><span class="o">-&gt;</span><span class="n">con</span><span class="o">-&gt;</span><span class="n">context</span><span class="o">-&gt;</span><span class="n">GetParserOptions</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* parser.ParseQuery() 会解析 SQL 语句, 将 PG 格式的 parse tree 转为 DuckDB 的 tree. */</span>
</span></span><span class="line"><span class="cl"><span class="n">parser</span><span class="p">.</span><span class="n">ParseQuery</span><span class="p">(</span><span class="n">query</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* SQL 的 binder, 逻辑计划生成, 调用优化器进行查询计划的优化, 物理计划生成. */</span>
</span></span><span class="line"><span class="cl"><span class="k">auto</span> <span class="n">pending</span> <span class="o">=</span> <span class="n">db</span><span class="o">-&gt;</span><span class="n">con</span><span class="o">-&gt;</span><span class="n">PendingQuery</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">statements</span><span class="p">.</span><span class="n">back</span><span class="p">()),</span> <span class="nb">false</span><span class="p">);</span></span></span></code></pre></div><h2 id="binder">Binder</h2>
<p>DuckDB 选择将绑定（Binding）与逻辑计划的生成耦合在 <code>Planner</code> 模块中完成。与许多传统数据库类似，绑定阶段既是&quot;语义审计员&quot;，也是逻辑计划构造的起点，主要负责以下几类工作：</p>
<ul>
<li>校验 SELECT 列是否存在于作用域内；</li>
<li>确认 WHERE、GROUP BY、ORDER BY 等子句的字段或表达式是否合法；</li>
<li>为表、列、函数等对象分配 <code>Binding</code> 信息，便于后续阶段定位到具体的 Catalog 条目。</li>
</ul>
<p><code>Binder::Bind</code> 会将 <code>QueryNode</code> 绑定为 <code>BoundQueryNode</code>，同时生成逻辑计划的根节点。调试时可以在此处打断点，观察 <code>BoundStatement::plan</code> 如何从空指针逐步填充：</p>
<ul>
<li><code>Binder</code> 维护着当前作用域的 <code>BindContext</code>，每当遇到子查询或 CTE 时都会临时切换上下文并在返回时合并结果；</li>
<li>常量表达式会在绑定阶段尝试下折叠，从而避免在逻辑计划阶段重复构造相同的表达式树；</li>
<li>如果 SQL 含有参数（<code>$1</code>、<code>?</code> 等），Binder 也会在此阶段推导出参数类型，并写入 <code>PreparedStatementData</code> 供执行阶段使用。</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">BoundStatement</span> <span class="n">Binder</span><span class="o">::</span><span class="n">Bind</span><span class="p">(</span><span class="n">QueryNode</span> <span class="o">&amp;</span><span class="n">node</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">BoundStatement</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">type</span> <span class="o">!=</span> <span class="n">QueryNodeType</span><span class="o">::</span><span class="n">CTE_NODE</span> <span class="o">&amp;&amp;</span> <span class="c1">// Issue #13850 - Don&#39;t auto-materialize if users materialize (for now)
</span></span></span><span class="line"><span class="cl">	    <span class="o">!</span><span class="n">Optimizer</span><span class="o">::</span><span class="n">OptimizerDisabled</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">OptimizerType</span><span class="o">::</span><span class="n">MATERIALIZED_CTE</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">context</span><span class="p">.</span><span class="n">config</span><span class="p">.</span><span class="n">enable_optimizer</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">	    <span class="n">OptimizeCTEs</span><span class="p">(</span><span class="n">node</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">switch</span> <span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="n">QueryNodeType</span><span class="o">::</span><span class="nl">SELECT_NODE</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">			<span class="n">result</span> <span class="o">=</span> <span class="n">BindWithCTE</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">Cast</span><span class="o">&lt;</span><span class="n">SelectNode</span><span class="o">&gt;</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">			<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="n">QueryNodeType</span><span class="o">::</span><span class="nl">RECURSIVE_CTE_NODE</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">			<span class="n">result</span> <span class="o">=</span> <span class="n">BindWithCTE</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">Cast</span><span class="o">&lt;</span><span class="n">RecursiveCTENode</span><span class="o">&gt;</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">			<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">case</span> <span class="n">QueryNodeType</span><span class="o">::</span><span class="nl">CTE_NODE</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">			<span class="n">result</span> <span class="o">=</span> <span class="n">BindWithCTE</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">Cast</span><span class="o">&lt;</span><span class="n">CTENode</span><span class="o">&gt;</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">			<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">			<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">type</span> <span class="o">==</span> <span class="n">QueryNodeType</span><span class="o">::</span><span class="n">SET_OPERATION_NODE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="n">result</span> <span class="o">=</span> <span class="n">BindWithCTE</span><span class="p">(</span><span class="n">node</span><span class="p">.</span><span class="n">Cast</span><span class="o">&lt;</span><span class="n">SetOperationNode</span><span class="o">&gt;</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">			<span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* 进行 binder 操作. */</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">bound_node</span> <span class="o">=</span> <span class="n">BindNode</span><span class="p">(</span><span class="n">node</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="n">result</span><span class="p">.</span><span class="n">names</span> <span class="o">=</span> <span class="n">bound_node</span><span class="o">-&gt;</span><span class="n">names</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="n">result</span><span class="p">.</span><span class="n">types</span> <span class="o">=</span> <span class="n">bound_node</span><span class="o">-&gt;</span><span class="n">types</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 逻辑计划生成. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">result</span><span class="p">.</span><span class="n">plan</span> <span class="o">=</span> <span class="n">CreatePlan</span><span class="p">(</span><span class="o">*</span><span class="n">bound_node</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="logical-plan-逻辑计划">Logical Plan 逻辑计划</h2>
<p>逻辑计划由一棵 <code>LogicalOperator</code> 树组成，每个算子节点描述一次逻辑运算（投影、过滤、JOIN、聚合等）。DuckDB 在算子层面贯彻了“结构即语义”的设计：算子类型、子节点列表与表达式集合共同定义了逻辑意图。<code>LogicalOperator</code> 的骨架如下：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">LogicalOperator</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">explicit</span> <span class="n">LogicalOperator</span><span class="p">(</span><span class="n">LogicalOperatorType</span> <span class="n">type</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">LogicalOperator</span><span class="p">(</span><span class="n">LogicalOperatorType</span> <span class="n">type</span><span class="p">,</span> <span class="n">vector</span><span class="o">&lt;</span><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">Expression</span><span class="o">&gt;&gt;</span> <span class="n">expressions</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">virtual</span> <span class="o">~</span><span class="n">LogicalOperator</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* 当前逻辑算子类型. */</span>
</span></span><span class="line"><span class="cl">        <span class="n">LogicalOperatorType</span> <span class="n">type</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* 子逻辑算子. */</span>
</span></span><span class="line"><span class="cl">        <span class="n">vector</span><span class="o">&lt;</span><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">LogicalOperator</span><span class="o">&gt;&gt;</span> <span class="n">children</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* 当前算子持有的表达式. */</span>
</span></span><span class="line"><span class="cl">        <span class="n">vector</span><span class="o">&lt;</span><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">Expression</span><span class="o">&gt;&gt;</span> <span class="n">expressions</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>为了便于调试，我们构造一张测试表，并插入一些示例数据。这些数据既覆盖了 NULL、数组、唯一约束等场景，也方便在断点状态下观察列裁剪、谓词传播等细节：</p>
<p>在阅读 <code>LogicalOperator</code> 时可以先关注以下几个小技巧：</p>
<ul>
<li>结合 <code>LogicalOperator::ToString</code> 输出的树形结构与下文的 ASCII 图，可以快速确认算子嵌套是否符合预期；</li>
<li>当逻辑计划出现循环依赖或缺失列时，<code>Verify</code> 会立刻报错，因此开发调试阶段推荐始终在 Debug 模式下运行。</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">D</span><span class="w"> </span><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">test_table</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">id</span><span class="w"> </span><span class="nb">INTEGER</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">name</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">age</span><span class="w"> </span><span class="nb">INTEGER</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">is_active</span><span class="w"> </span><span class="nb">BOOLEAN</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">created_at</span><span class="w"> </span><span class="k">TIMESTAMP</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">salary</span><span class="w"> </span><span class="nb">DECIMAL</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">tags</span><span class="w"> </span><span class="nb">TEXT</span><span class="p">[],</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">category</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">UNIQUE</span><span class="p">(</span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">category</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">D</span><span class="w"> </span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">test_table</span><span class="w"> </span><span class="p">(</span><span class="n">id</span><span class="p">,</span><span class="w"> </span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">age</span><span class="p">,</span><span class="w"> </span><span class="n">is_active</span><span class="p">,</span><span class="w"> </span><span class="n">created_at</span><span class="p">,</span><span class="w"> </span><span class="n">salary</span><span class="p">,</span><span class="w"> </span><span class="n">tags</span><span class="p">,</span><span class="w"> </span><span class="n">category</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">VALUES</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Alice&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">30</span><span class="p">,</span><span class="w"> </span><span class="k">TRUE</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;2024-01-01 10:00:00&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">50000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="nb">ARRAY</span><span class="p">[</span><span class="s1">&#39;dev&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;manager&#39;</span><span class="p">],</span><span class="w"> </span><span class="s1">&#39;A&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Bob&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">25</span><span class="p">,</span><span class="w"> </span><span class="k">FALSE</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;2024-01-02 11:00:00&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">45000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="nb">ARRAY</span><span class="p">[</span><span class="s1">&#39;designer&#39;</span><span class="p">],</span><span class="w"> </span><span class="s1">&#39;B&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Charlie&#39;</span><span class="p">,</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w"> </span><span class="k">TRUE</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;2024-01-03 12:00:00&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">60000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;A&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;David&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">40</span><span class="p">,</span><span class="w"> </span><span class="k">TRUE</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;2024-01-04 13:00:00&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">70000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="nb">ARRAY</span><span class="p">[</span><span class="s1">&#39;dev&#39;</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;lead&#39;</span><span class="p">],</span><span class="w"> </span><span class="s1">&#39;C&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">(</span><span class="mi">5</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Eve&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">35</span><span class="p">,</span><span class="w"> </span><span class="k">FALSE</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;2024-01-05 14:00:00&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">55000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="nb">ARRAY</span><span class="p">[</span><span class="s1">&#39;marketing&#39;</span><span class="p">],</span><span class="w"> </span><span class="s1">&#39;B&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Frank&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">28</span><span class="p">,</span><span class="w"> </span><span class="k">TRUE</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;2024-01-06 15:00:00&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">48000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="nb">ARRAY</span><span class="p">[</span><span class="s1">&#39;dev&#39;</span><span class="p">],</span><span class="w"> </span><span class="s1">&#39;A&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">(</span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Grace&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">22</span><span class="p">,</span><span class="w"> </span><span class="k">TRUE</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;2024-01-07 16:00:00&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">40000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="k">NULL</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;C&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">(</span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Hank&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">33</span><span class="p">,</span><span class="w"> </span><span class="k">FALSE</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;2024-01-08 17:00:00&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">49000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="nb">ARRAY</span><span class="p">[</span><span class="s1">&#39;designer&#39;</span><span class="p">],</span><span class="w"> </span><span class="s1">&#39;B&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">(</span><span class="mi">9</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Ivy&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">27</span><span class="p">,</span><span class="w"> </span><span class="k">TRUE</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;2024-01-09 18:00:00&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">52000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="nb">ARRAY</span><span class="p">[</span><span class="s1">&#39;dev&#39;</span><span class="p">],</span><span class="w"> </span><span class="s1">&#39;A&#39;</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;Jack&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">31</span><span class="p">,</span><span class="w"> </span><span class="k">TRUE</span><span class="p">,</span><span class="w"> </span><span class="s1">&#39;2024-01-10 19:00:00&#39;</span><span class="p">,</span><span class="w"> </span><span class="mi">58000</span><span class="p">.</span><span class="mi">00</span><span class="p">,</span><span class="w"> </span><span class="nb">ARRAY</span><span class="p">[</span><span class="s1">&#39;lead&#39;</span><span class="p">],</span><span class="w"> </span><span class="s1">&#39;C&#39;</span><span class="p">);</span></span></span></code></pre></div><p>开启 <code>PRAGMA explain_output = 'all'</code> 后可以同时查看逻辑计划与物理计划。如下查询的未优化逻辑计划包含三个算子，我们可以借助它来快速验证逻辑树结构与列绑定是否符合预期：</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">D</span><span class="w"> </span><span class="n">PRAGMA</span><span class="w"> </span><span class="n">explain_output</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;all&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">D</span><span class="w"> </span><span class="k">EXPLAIN</span><span class="w"> </span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">test_table</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">age</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">30</span><span class="p">;</span></span></span></code></pre></div><pre tabindex="0"><code>┌─────────────────────────────┐
│┌───────────────────────────┐│
││ Unoptimized Logical Plan  ││
│└───────────────────────────┘│
└─────────────────────────────┘
┌───────────────────────────┐
│         PROJECTION        │
│    ────────────────────   │
│        Expressions:       │
│             id            │
│            name           │
│            age            │
│         is_active         │
│         created_at        │
│           salary          │
│            tags           │
│          category         │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│           FILTER          │
│    ────────────────────   │
│        Expressions:       │
│(age &gt; CAST(30 AS INTEGER))│
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│          SEQ_SCAN         │
│    ────────────────────   │
│     Table: test_table     │
│   Type: Sequential Scan   │
└───────────────────────────┘</code></pre><ul>
<li><code>LOGICAL_PROJECTION</code>：对应 SELECT 列表，负责输出最终的列集；</li>
<li><code>LOGICAL_FILTER</code>：表达式为 <code>age &gt; 30</code>，对扫描结果做行级过滤；</li>
<li><code>LOGICAL_GET</code>：数据源算子，从 <code>test_table</code> 顺序扫描数据。</li>
</ul>
<p>更多算子类型可以参考 <a href="https://github.com/duckdb/duckdb/blob/v1.3-ossivalis/src/include/duckdb/common/enums/logical_operator_type.hpp">logical_operator_type.hpp</a>。</p>
<h2 id="logical-plan-生成">Logical Plan 生成</h2>
<p>逻辑计划生成的核心入口是 <code>Binder::CreatePlan(BoundSelectNode &amp;statement)</code>。函数内部按照 SQL 子句的语义顺序构建算子树，整体遵循“自下而上拼装子树、再逐层加壳”的思路：</p>
<ol>
<li><strong>FROM 子句</strong>：<code>BoundSelectNode::from_table</code> 在绑定阶段已经转换成相应的 <code>LogicalGet</code> 或其他子查询算子，作为整个算子树的初始根节点。</li>
<li><strong>SAMPLE</strong>：若存在 <code>USING SAMPLE</code> 语法，将 <code>LogicalSample</code> 包裹在当前根节点外层。</li>
<li><strong>WHERE</strong>：使用 <code>PlanFilter</code> 将过滤谓词包装为 <code>LogicalFilter</code>，并挂在现有根节点之上。</li>
<li><strong>GROUP BY / 聚合</strong>：当存在聚合或分组时，提前遍历子查询并生成 <code>LogicalAggregate</code>；Grouping Sets、Grouping Functions 等高级语法也在此处处理。</li>
<li><strong>HAVING</strong>：再次生成 <code>LogicalFilter</code>，与 WHERE 类似，只是输入来自聚合结果。</li>
<li><strong>窗口函数与 QUALIFY</strong>：依次生成 <code>LogicalWindow</code> 和 <code>LogicalFilter</code>，并处理其中可能嵌套的子查询。</li>
<li><strong>UNNEST</strong>：针对 LATERAL 或 UNNEST 场景，构造 <code>LogicalUnnest</code>。</li>
<li><strong>SELECT 列表与投影</strong>：遍历 <code>select_list</code> 处理子查询后，创建 <code>LogicalProjection</code> 作为新的根节点。</li>
<li><strong>ORDER BY / LIMIT / DISTINCT</strong>：调用 <code>VisitQueryNode</code> 追加排序、去重、限制行数等算子。</li>
<li><strong>列裁剪</strong>：若 <code>need_prune</code> 标记为 True，插入额外的 <code>LogicalProjection</code> 以仅保留需要的列。</li>
</ol>
<p>对应源码片段如下 (省略与主题无关的细节):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">LogicalOperator</span><span class="o">&gt;</span> <span class="n">Binder</span><span class="o">::</span><span class="n">CreatePlan</span><span class="p">(</span><span class="n">BoundSelectNode</span> <span class="o">&amp;</span><span class="n">statement</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">LogicalOperator</span><span class="o">&gt;</span> <span class="n">root</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">from_table</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 在通用查询中，物理存储表的 LogicalOperator 是 LogicalOperatorType::LOGICAL_GET,
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * LOGICAL_GET 会在 SELECT 语句 binder 阶段就被生成. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">root</span> <span class="o">=</span> <span class="n">CreatePlan</span><span class="p">(</span><span class="o">*</span><span class="n">statement</span><span class="p">.</span><span class="n">from_table</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">root</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 处理 samples 语句: DuckDB 支持采样语法
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * SELECT * FROM tbl USING SAMPLE 10% (选择表大约 10% 的样本). */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">sample_options</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">root</span> <span class="o">=</span> <span class="n">make_uniq</span><span class="o">&lt;</span><span class="n">LogicalSample</span><span class="o">&gt;</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">sample_options</span><span class="p">),</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">root</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 处理 where 条件. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">where_clause</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">root</span> <span class="o">=</span> <span class="n">PlanFilter</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">where_clause</span><span class="p">),</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">root</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 聚合算子. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">statement</span><span class="p">.</span><span class="n">aggregates</span><span class="p">.</span><span class="n">empty</span><span class="p">()</span> <span class="o">||</span> <span class="o">!</span><span class="n">statement</span><span class="p">.</span><span class="n">groups</span><span class="p">.</span><span class="n">group_expressions</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">statement</span><span class="p">.</span><span class="n">groups</span><span class="p">.</span><span class="n">group_expressions</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="c1">// visit the groups
</span></span></span><span class="line"><span class="cl">			<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="nl">group</span> <span class="p">:</span> <span class="n">statement</span><span class="p">.</span><span class="n">groups</span><span class="p">.</span><span class="n">group_expressions</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="n">PlanSubqueries</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">root</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// now visit all aggregate expressions
</span></span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="nl">expr</span> <span class="p">:</span> <span class="n">statement</span><span class="p">.</span><span class="n">aggregates</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">PlanSubqueries</span><span class="p">(</span><span class="n">expr</span><span class="p">,</span> <span class="n">root</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// finally create the aggregate node with the group_index and aggregate_index as obtained from the binder
</span></span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">aggregate</span> <span class="o">=</span> <span class="n">make_uniq</span><span class="o">&lt;</span><span class="n">LogicalAggregate</span><span class="o">&gt;</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">group_index</span><span class="p">,</span> <span class="n">statement</span><span class="p">.</span><span class="n">aggregate_index</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		                                             <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">aggregates</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		<span class="n">aggregate</span><span class="o">-&gt;</span><span class="n">groups</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">groups</span><span class="p">.</span><span class="n">group_expressions</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">aggregate</span><span class="o">-&gt;</span><span class="n">groupings_index</span> <span class="o">=</span> <span class="n">statement</span><span class="p">.</span><span class="n">groupings_index</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="n">aggregate</span><span class="o">-&gt;</span><span class="n">grouping_sets</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">groups</span><span class="p">.</span><span class="n">grouping_sets</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">aggregate</span><span class="o">-&gt;</span><span class="n">grouping_functions</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">grouping_functions</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="n">aggregate</span><span class="o">-&gt;</span><span class="n">AddChild</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">root</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		<span class="n">root</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">aggregate</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="o">!</span><span class="n">statement</span><span class="p">.</span><span class="n">groups</span><span class="p">.</span><span class="n">grouping_sets</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// edge case: we have grouping sets but no groups or aggregates
</span></span></span><span class="line"><span class="cl">		<span class="c1">// this can only happen if we have e.g. select 1 from tbl group by ();
</span></span></span><span class="line"><span class="cl">		<span class="c1">// just output a dummy scan
</span></span></span><span class="line"><span class="cl">		<span class="n">root</span> <span class="o">=</span> <span class="n">make_uniq_base</span><span class="o">&lt;</span><span class="n">LogicalOperator</span><span class="p">,</span> <span class="n">LogicalDummyScan</span><span class="o">&gt;</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">group_index</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 处理 having 语句, 使用 LogicalFilter 过滤算子. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">having</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">PlanSubqueries</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">having</span><span class="p">,</span> <span class="n">root</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">having</span> <span class="o">=</span> <span class="n">make_uniq</span><span class="o">&lt;</span><span class="n">LogicalFilter</span><span class="o">&gt;</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">having</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="n">having</span><span class="o">-&gt;</span><span class="n">AddChild</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">root</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		<span class="n">root</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">having</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 处理窗口函数语句. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">statement</span><span class="p">.</span><span class="n">windows</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">win</span> <span class="o">=</span> <span class="n">make_uniq</span><span class="o">&lt;</span><span class="n">LogicalWindow</span><span class="o">&gt;</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">window_index</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">win</span><span class="o">-&gt;</span><span class="n">expressions</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">windows</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// visit the window expressions
</span></span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="nl">expr</span> <span class="p">:</span> <span class="n">win</span><span class="o">-&gt;</span><span class="n">expressions</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">PlanSubqueries</span><span class="p">(</span><span class="n">expr</span><span class="p">,</span> <span class="n">root</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="n">D_ASSERT</span><span class="p">(</span><span class="o">!</span><span class="n">win</span><span class="o">-&gt;</span><span class="n">expressions</span><span class="p">.</span><span class="n">empty</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">		<span class="n">win</span><span class="o">-&gt;</span><span class="n">AddChild</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">root</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		<span class="n">root</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">win</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 处理 QUALIFY 语句, QUALIFY 是针对窗口函数结果的过滤. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">qualify</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">PlanSubqueries</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">qualify</span><span class="p">,</span> <span class="n">root</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">qualify</span> <span class="o">=</span> <span class="n">make_uniq</span><span class="o">&lt;</span><span class="n">LogicalFilter</span><span class="o">&gt;</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">qualify</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="n">qualify</span><span class="o">-&gt;</span><span class="n">AddChild</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">root</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		<span class="n">root</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">qualify</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* Unnesting 算子. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="n">statement</span><span class="p">.</span><span class="n">unnests</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span><span class="o">--</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">unnest_level</span> <span class="o">=</span> <span class="n">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">entry</span> <span class="o">=</span> <span class="n">statement</span><span class="p">.</span><span class="n">unnests</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">unnest_level</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">entry</span> <span class="o">==</span> <span class="n">statement</span><span class="p">.</span><span class="n">unnests</span><span class="p">.</span><span class="n">end</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">throw</span> <span class="nf">InternalException</span><span class="p">(</span><span class="s">&#34;unnests specified at level %d but none were found&#34;</span><span class="p">,</span> <span class="n">unnest_level</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="o">&amp;</span><span class="n">unnest_node</span> <span class="o">=</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">second</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">unnest</span> <span class="o">=</span> <span class="n">make_uniq</span><span class="o">&lt;</span><span class="n">LogicalUnnest</span><span class="o">&gt;</span><span class="p">(</span><span class="n">unnest_node</span><span class="p">.</span><span class="n">index</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">unnest</span><span class="o">-&gt;</span><span class="n">expressions</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">unnest_node</span><span class="p">.</span><span class="n">expressions</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="c1">// visit the unnest expressions
</span></span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="nl">expr</span> <span class="p">:</span> <span class="n">unnest</span><span class="o">-&gt;</span><span class="n">expressions</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">PlanSubqueries</span><span class="p">(</span><span class="n">expr</span><span class="p">,</span> <span class="n">root</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="n">D_ASSERT</span><span class="p">(</span><span class="o">!</span><span class="n">unnest</span><span class="o">-&gt;</span><span class="n">expressions</span><span class="p">.</span><span class="n">empty</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">		<span class="n">unnest</span><span class="o">-&gt;</span><span class="n">AddChild</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">root</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		<span class="n">root</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">unnest</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 处理投影列. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="nl">expr</span> <span class="p">:</span> <span class="n">statement</span><span class="p">.</span><span class="n">select_list</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* 如果有子查询. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">PlanSubqueries</span><span class="p">(</span><span class="n">expr</span><span class="p">,</span> <span class="n">root</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 逻辑计划最上层为 LogicalProjection 投影算子. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">auto</span> <span class="n">proj</span> <span class="o">=</span> <span class="n">make_uniq</span><span class="o">&lt;</span><span class="n">LogicalProjection</span><span class="o">&gt;</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">projection_index</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">select_list</span><span class="p">));</span> <span class="k">auto</span> <span class="o">&amp;</span><span class="n">projection</span> <span class="o">=</span> <span class="o">*</span><span class="n">proj</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">proj</span><span class="o">-&gt;</span><span class="n">AddChild</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">root</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">	<span class="n">root</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">proj</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 处理 LIMIT, ORDER BY, DISTINCT 算子. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">root</span> <span class="o">=</span> <span class="n">VisitQueryNode</span><span class="p">(</span><span class="n">statement</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">root</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 处理需要剪枝的情况. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">need_prune</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">root</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">vector</span><span class="o">&lt;</span><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">Expression</span><span class="o">&gt;&gt;</span> <span class="n">prune_expressions</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="k">for</span> <span class="p">(</span><span class="n">idx_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">statement</span><span class="p">.</span><span class="n">column_count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">prune_expressions</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">make_uniq</span><span class="o">&lt;</span><span class="n">BoundColumnRefExpression</span><span class="o">&gt;</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">			    <span class="n">projection</span><span class="p">.</span><span class="n">expressions</span><span class="p">[</span><span class="n">i</span><span class="p">]</span><span class="o">-&gt;</span><span class="n">return_type</span><span class="p">,</span> <span class="n">ColumnBinding</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">projection_index</span><span class="p">,</span> <span class="n">i</span><span class="p">)));</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="k">auto</span> <span class="n">prune</span> <span class="o">=</span> <span class="n">make_uniq</span><span class="o">&lt;</span><span class="n">LogicalProjection</span><span class="o">&gt;</span><span class="p">(</span><span class="n">statement</span><span class="p">.</span><span class="n">prune_index</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">prune_expressions</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		<span class="n">prune</span><span class="o">-&gt;</span><span class="n">AddChild</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">root</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">		<span class="n">root</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">prune</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="n">root</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>通过上述流程可以看到，逻辑计划的生成并不是单纯的&quot;语法树到逻辑树&quot;的转换，而是伴随着大量的语义分析和子查询规划，确保每个算子都具备足够的上下文信息供后续优化与执行阶段使用。</p>
<h2 id="logical-plan-optimizer-逻辑计划优化">Logical Plan Optimizer 逻辑计划优化</h2>
<p>逻辑计划完成生成后, 我们需要对逻辑计划展开优化:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* src/main/client_context.cpp */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">shared_ptr</span><span class="o">&lt;</span><span class="n">PreparedStatementData</span><span class="o">&gt;</span>
</span></span><span class="line"><span class="cl"><span class="n">ClientContext</span><span class="o">::</span><span class="n">CreatePreparedStatementInternal</span><span class="p">(</span><span class="n">ClientContextLock</span> <span class="o">&amp;</span><span class="n">lock</span><span class="p">,</span> <span class="k">const</span> <span class="n">string</span> <span class="o">&amp;</span><span class="n">query</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                               <span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">SQLStatement</span><span class="o">&gt;</span> <span class="n">statement</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                               <span class="n">optional_ptr</span><span class="o">&lt;</span><span class="n">case_insensitive_map_t</span><span class="o">&lt;</span><span class="n">BoundParameterData</span><span class="o">&gt;&gt;</span> <span class="n">values</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">config</span><span class="p">.</span><span class="n">enable_optimizer</span> <span class="o">&amp;&amp;</span> <span class="n">logical_plan</span><span class="o">-&gt;</span><span class="n">RequireOptimizer</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">profiler</span><span class="p">.</span><span class="n">StartPhase</span><span class="p">(</span><span class="n">MetricsType</span><span class="o">::</span><span class="n">ALL_OPTIMIZERS</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 进行逻辑计划的优化. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">Optimizer</span> <span class="nf">optimizer</span><span class="p">(</span><span class="o">*</span><span class="n">logical_planner</span><span class="p">.</span><span class="n">binder</span><span class="p">,</span> <span class="o">*</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">logical_plan</span> <span class="o">=</span> <span class="n">optimizer</span><span class="p">.</span><span class="n">Optimize</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">logical_plan</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="n">D_ASSERT</span><span class="p">(</span><span class="n">logical_plan</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">profiler</span><span class="p">.</span><span class="n">EndPhase</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef DEBUG
</span></span></span><span class="line"><span class="cl">		<span class="n">logical_plan</span><span class="o">-&gt;</span><span class="n">Verify</span><span class="p">(</span><span class="o">*</span><span class="k">this</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>Optimizer::Optimize</code> 的整体框架如下：</p>
<p>DuckDB 的逻辑优化器本质上是一个多阶段的规则引擎：首先应用表达式重写（常量折叠、谓词归并等），随后执行逻辑层的算子改写（谓词下推、JOIN 重排、列裁剪），最后再把控制权交给扩展点，让用户注入自定义的优化规则。每个阶段都通过 <code>RunOptimizer</code> 计时，配合 <code>EXPLAIN ANALYZE</code> 中的 Profile 信息可以精确定位耗时的规则。</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">LogicalOperator</span><span class="o">&gt;</span> <span class="n">Optimizer</span><span class="o">::</span><span class="n">Optimize</span><span class="p">(</span><span class="n">unique_ptr</span><span class="o">&lt;</span><span class="n">LogicalOperator</span><span class="o">&gt;</span> <span class="n">plan_p</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">Verify</span><span class="p">(</span><span class="o">*</span><span class="n">plan_p</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">this</span><span class="o">-&gt;</span><span class="n">plan</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">plan_p</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 用户自定义的优化规则. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="nl">pre_optimizer_extension</span> <span class="p">:</span> <span class="n">DBConfig</span><span class="o">::</span><span class="n">GetConfig</span><span class="p">(</span><span class="n">context</span><span class="p">).</span><span class="n">optimizer_extensions</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">RunOptimizer</span><span class="p">(</span><span class="n">OptimizerType</span><span class="o">::</span><span class="n">EXTENSION</span><span class="p">,</span> <span class="p">[</span><span class="o">&amp;</span><span class="p">]()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">OptimizerExtensionInput</span> <span class="n">input</span> <span class="p">{</span><span class="n">GetContext</span><span class="p">(),</span> <span class="o">*</span><span class="k">this</span><span class="p">,</span> <span class="n">pre_optimizer_extension</span><span class="p">.</span><span class="n">optimizer_info</span><span class="p">.</span><span class="n">get</span><span class="p">()};</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="n">pre_optimizer_extension</span><span class="p">.</span><span class="n">pre_optimize_function</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="n">pre_optimizer_extension</span><span class="p">.</span><span class="n">pre_optimize_function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">plan</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">});</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* Built-in 优化规则. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">RunBuiltInOptimizers</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 用户自定义的优化规则. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="p">(</span><span class="k">auto</span> <span class="o">&amp;</span><span class="nl">optimizer_extension</span> <span class="p">:</span> <span class="n">DBConfig</span><span class="o">::</span><span class="n">GetConfig</span><span class="p">(</span><span class="n">context</span><span class="p">).</span><span class="n">optimizer_extensions</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">RunOptimizer</span><span class="p">(</span><span class="n">OptimizerType</span><span class="o">::</span><span class="n">EXTENSION</span><span class="p">,</span> <span class="p">[</span><span class="o">&amp;</span><span class="p">]()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="n">OptimizerExtensionInput</span> <span class="n">input</span> <span class="p">{</span><span class="n">GetContext</span><span class="p">(),</span> <span class="o">*</span><span class="k">this</span><span class="p">,</span> <span class="n">optimizer_extension</span><span class="p">.</span><span class="n">optimizer_info</span><span class="p">.</span><span class="n">get</span><span class="p">()};</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="n">optimizer_extension</span><span class="p">.</span><span class="n">optimize_function</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">				<span class="n">optimizer_extension</span><span class="p">.</span><span class="n">optimize_function</span><span class="p">(</span><span class="n">input</span><span class="p">,</span> <span class="n">plan</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">			<span class="p">}</span>
</span></span><span class="line"><span class="cl">		<span class="p">});</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">Planner</span><span class="o">::</span><span class="n">VerifyPlan</span><span class="p">(</span><span class="n">context</span><span class="p">,</span> <span class="n">plan</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">plan</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>DuckDB 内置了很多启发式规则, 其中内置的优化规则包括:</p>
<ul>
<li>EXPRESSION_REWRITER: 表达式重写.</li>
</ul>
<blockquote>
<p>表达式重写是 <code>Optimizer</code> 初始化时构造函数里就填好的规则, 包括常量折叠, 分配律优化, CASE 语句简化等等.</p>
</blockquote>
<ul>
<li>FILTER_PULLUP: 谓词上拉.</li>
</ul>
<blockquote>
<p>我们以下面这个 SQL 为例, FILTER_PULLUP 优化会将 <code>vals1.i=5</code> 提至 <code>FILTER</code> 算子下方, 方便后续的谓词下推:</p>
</blockquote>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">D</span><span class="w"> </span><span class="k">explain</span><span class="w"> </span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="p">(</span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">vals1</span><span class="p">,</span><span class="w"> </span><span class="n">vals2</span><span class="w"> </span><span class="k">WHERE</span><span class="w"> </span><span class="n">vals1</span><span class="p">.</span><span class="n">i</span><span class="o">=</span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">tbl1</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">(</span><span class="k">SELECT</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">FROM</span><span class="w"> </span><span class="n">vals1</span><span class="p">,</span><span class="w"> </span><span class="n">vals2</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="n">tbl2</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">WHERE</span><span class="w"> </span><span class="n">tbl1</span><span class="p">.</span><span class="n">i</span><span class="o">=</span><span class="n">tbl2</span><span class="p">.</span><span class="n">i</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">┌─────────────────────────────┐</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│┌───────────────────────────┐│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">││</span><span class="w"> </span><span class="n">Unoptimized</span><span class="w"> </span><span class="n">Logical</span><span class="w"> </span><span class="n">Plan</span><span class="w">  </span><span class="err">││</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│└───────────────────────────┘│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">└─────────────────────────────┘</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">┌───────────────────────────┐</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">         </span><span class="n">PROJECTION</span><span class="w">        </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">        </span><span class="n">Expressions</span><span class="p">:</span><span class="w">       </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">             </span><span class="n">i</span><span class="w">             </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">            </span><span class="n">i_1</span><span class="w">            </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">             </span><span class="n">i</span><span class="w">             </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">            </span><span class="n">i_1</span><span class="w">            </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">└─────────────┬─────────────┘</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">┌─────────────┴─────────────┐</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">           </span><span class="n">FILTER</span><span class="w">          </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">    </span><span class="n">Expressions</span><span class="p">:</span><span class="w"> </span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">i</span><span class="p">)</span><span class="w">   </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">└─────────────┬─────────────┘</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">┌─────────────┴─────────────┐</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">       </span><span class="n">CROSS_PRODUCT</span><span class="w">       </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">├───────────────────────────────────────────┐</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">└─────────────┬─────────────┘</span><span class="w">                                           </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">┌─────────────┴─────────────┐</span><span class="w">                             </span><span class="err">┌─────────────┴─────────────┐</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">         </span><span class="n">PROJECTION</span><span class="w">        </span><span class="err">│</span><span class="w">                             </span><span class="err">│</span><span class="w">         </span><span class="n">PROJECTION</span><span class="w">        </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">│</span><span class="w">                             </span><span class="err">│</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">        </span><span class="n">Expressions</span><span class="p">:</span><span class="w">       </span><span class="err">│</span><span class="w">                             </span><span class="err">│</span><span class="w">        </span><span class="n">Expressions</span><span class="p">:</span><span class="w">       </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">             </span><span class="n">i</span><span class="w">             </span><span class="err">│</span><span class="w">                             </span><span class="err">│</span><span class="w">             </span><span class="n">i</span><span class="w">             </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">             </span><span class="n">i</span><span class="w">             </span><span class="err">│</span><span class="w">                             </span><span class="err">│</span><span class="w">             </span><span class="n">i</span><span class="w">             </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">└─────────────┬─────────────┘</span><span class="w">                             </span><span class="err">└─────────────┬─────────────┘</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">┌─────────────┴─────────────┐</span><span class="w">                             </span><span class="err">┌─────────────┴─────────────┐</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">           </span><span class="n">FILTER</span><span class="w">          </span><span class="err">│</span><span class="w">                             </span><span class="err">│</span><span class="w">       </span><span class="n">CROSS_PRODUCT</span><span class="w">       </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">│</span><span class="w">                             </span><span class="err">│</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">        </span><span class="n">Expressions</span><span class="p">:</span><span class="w">       </span><span class="err">│</span><span class="w">                             </span><span class="err">│</span><span class="w">                           </span><span class="err">├──────────────┐</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">  </span><span class="p">(</span><span class="n">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">CAST</span><span class="p">(</span><span class="mi">5</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="nb">INTEGER</span><span class="p">))</span><span class="w"> </span><span class="err">│</span><span class="w">                             </span><span class="err">│</span><span class="w">                           </span><span class="err">│</span><span class="w">              </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">└─────────────┬─────────────┘</span><span class="w">                             </span><span class="err">└─────────────┬─────────────┘</span><span class="w">              </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">┌─────────────┴─────────────┐</span><span class="w">                             </span><span class="err">┌─────────────┴─────────────┐┌─────────────┴─────────────┐</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">       </span><span class="n">CROSS_PRODUCT</span><span class="w">       </span><span class="err">│</span><span class="w">                             </span><span class="err">│</span><span class="w">          </span><span class="n">SEQ_SCAN</span><span class="w">         </span><span class="err">││</span><span class="w">          </span><span class="n">SEQ_SCAN</span><span class="w">         </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">│</span><span class="w">                             </span><span class="err">│</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">││</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">                           </span><span class="err">├──────────────┐</span><span class="w">              </span><span class="err">│</span><span class="w">        </span><span class="k">Table</span><span class="p">:</span><span class="w"> </span><span class="n">vals1</span><span class="w">       </span><span class="err">││</span><span class="w">        </span><span class="k">Table</span><span class="p">:</span><span class="w"> </span><span class="n">vals2</span><span class="w">       </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">                           </span><span class="err">│</span><span class="w">              </span><span class="err">│</span><span class="w">              </span><span class="err">│</span><span class="w">   </span><span class="k">Type</span><span class="p">:</span><span class="w"> </span><span class="n">Sequential</span><span class="w"> </span><span class="n">Scan</span><span class="w">   </span><span class="err">││</span><span class="w">   </span><span class="k">Type</span><span class="p">:</span><span class="w"> </span><span class="n">Sequential</span><span class="w"> </span><span class="n">Scan</span><span class="w">   </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">└─────────────┬─────────────┘</span><span class="w">              </span><span class="err">│</span><span class="w">              </span><span class="err">└───────────────────────────┘└───────────────────────────┘</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">┌─────────────┴─────────────┐┌─────────────┴─────────────┐</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">          </span><span class="n">SEQ_SCAN</span><span class="w">         </span><span class="err">││</span><span class="w">          </span><span class="n">SEQ_SCAN</span><span class="w">         </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">││</span><span class="w">    </span><span class="err">────────────────────</span><span class="w">   </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">        </span><span class="k">Table</span><span class="p">:</span><span class="w"> </span><span class="n">vals1</span><span class="w">       </span><span class="err">││</span><span class="w">        </span><span class="k">Table</span><span class="p">:</span><span class="w"> </span><span class="n">vals2</span><span class="w">       </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">│</span><span class="w">   </span><span class="k">Type</span><span class="p">:</span><span class="w"> </span><span class="n">Sequential</span><span class="w"> </span><span class="n">Scan</span><span class="w">   </span><span class="err">││</span><span class="w">   </span><span class="k">Type</span><span class="p">:</span><span class="w"> </span><span class="n">Sequential</span><span class="w"> </span><span class="n">Scan</span><span class="w">   </span><span class="err">│</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="err">└───────────────────────────┘└───────────────────────────┘</span></span></span></code></pre></div><p>FILTER_PULLUP 优化后的逻辑计划:</p>
<pre tabindex="0"><code>┌─────────────────────────────┐
│┌───────────────────────────┐│
││ Unoptimized Logical Plan  ││
│└───────────────────────────┘│
└─────────────────────────────┘
┌───────────────────────────┐
│         PROJECTION        │
│    ────────────────────   │
│        Expressions:       │
│             i             │
│            i_1            │
│             i             │
│            i_1            │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│           FILTER          │
│    ────────────────────   │
│    Expressions: (i = i)   │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│           FILTER          │
│    ────────────────────   │
│    Expressions: (i = 5)   │  /* 将谓词条件 vals1.i=5 提升至这里. */
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│       CROSS_PRODUCT       │
│    ────────────────────   ├───────────────────────────────────────────┐
└─────────────┬─────────────┘                                           │
┌─────────────┴─────────────┐                             ┌─────────────┴─────────────┐
│         PROJECTION        │                             │         PROJECTION        │
│    ────────────────────   │                             │    ────────────────────   │
│        Expressions:       │                             │        Expressions:       │
│             i             │                             │             i             │
│             i             │                             │             i             │
└─────────────┬─────────────┘                             └─────────────┬─────────────┘
              │                                           ┌─────────────┴─────────────┐
              │                                           │       CROSS_PRODUCT       │
              │                                           │    ────────────────────   │
              │                                           │                           ├──────────────┐
              │                                           │                           │              │
              │                                           └─────────────┬─────────────┘              │
┌─────────────┴─────────────┐                             ┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│       CROSS_PRODUCT       │                             │          SEQ_SCAN         ││          SEQ_SCAN         │
│    ────────────────────   │                             │    ────────────────────   ││    ────────────────────   │
│                           ├──────────────┐              │        Table: vals1       ││        Table: vals2       │
│                           │              │              │   Type: Sequential Scan   ││   Type: Sequential Scan   │
└─────────────┬─────────────┘              │              └───────────────────────────┘└───────────────────────────┘
┌─────────────┴─────────────┐┌─────────────┴─────────────┐
│          SEQ_SCAN         ││          SEQ_SCAN         │
│    ────────────────────   ││    ────────────────────   │
│        Table: vals1       ││        Table: vals2       │
│   Type: Sequential Scan   ││   Type: Sequential Scan   │
└───────────────────────────┘└───────────────────────────┘</code></pre><ul>
<li>FILTER_PUSHDOWN: 谓词下推</li>
</ul>
<blockquote>
<p>FILTER_PULLUP 将过滤条件提至了上端, FILTER_PUSHDOWN 搜集这些过滤条件下推到底层投影列, 这样在 SQL 执行的过程中可以提早的过滤数据, 减少 CPU 和 IO 的开销.</p>
</blockquote>
<ul>
<li>
<p>EMPTY_RESULT_PULLUP,</p>
</li>
<li>
<p>CTE_FILTER_PUSHER,</p>
</li>
<li>
<p>REGEX_RANGE,</p>
</li>
<li>
<p>IN_CLAUSE,</p>
</li>
<li>
<p>JOIN_ORDER:</p>
</li>
</ul>
<blockquote>
<p>JOIN_ORDER 优化方法主要目标是重新排列多表 JOIN 的顺序，以获得最优的查询执行性能.</p>
</blockquote>
<ul>
<li>
<p>DELIMINATOR,</p>
</li>
<li>
<p>UNNEST_REWRITER,</p>
</li>
<li>
<p>UNUSED_COLUMNS,</p>
</li>
<li>
<p>STATISTICS_PROPAGATION,</p>
</li>
<li>
<p>COMMON_SUBEXPRESSIONS,</p>
</li>
<li>
<p>COMMON_AGGREGATE,</p>
</li>
<li>
<p>COLUMN_LIFETIME,</p>
</li>
<li>
<p>BUILD_SIDE_PROBE_SIDE,</p>
</li>
<li>
<p>LIMIT_PUSHDOWN:</p>
</li>
</ul>
<blockquote>
<p>LIMIT_PUSHDOWN 优化是将 LIMIT 操作符下推到 PROJECTION 操作符之下，以减少不必要的行处理和提高查询性能.</p>
</blockquote>
<ul>
<li>TOP_N:</li>
</ul>
<blockquote>
<p>针对 <code>ORDER BY ... LIMIT</code> 的查询，将排序限制在 Top-N 元素范围内执行，避免对全部数据排序。</p>
</blockquote>
<ul>
<li>COMPRESSED_MATERIALIZATION:</li>
</ul>
<blockquote>
<p>在物化中保留压缩格式，仅在必要时解压以减少内存带宽和缓存占用。</p>
</blockquote>
<ul>
<li>DUPLICATE_GROUPS:</li>
</ul>
<blockquote>
<p>在聚合之前检测并消除重复的分组键组合，降低 <code>GROUP BY</code> 需要处理的行数。</p>
</blockquote>
<ul>
<li>REORDER_FILTER:</li>
</ul>
<blockquote>
<p>调整同一节点下多个过滤条件的顺序，优先执行选择性更高的条件以尽早裁剪数据。</p>
</blockquote>
<ul>
<li>SAMPLING_PUSHDOWN:</li>
</ul>
<blockquote>
<p>将采样操作下推到扫描算子，从数据源直接获取样本行，缩短上层管线等待时间。</p>
</blockquote>
<ul>
<li>JOIN_FILTER_PUSHDOWN:</li>
</ul>
<blockquote>
<p>将连接产生的过滤条件继续向下推送到参与连接的输入上，减少参与 JOIN 的候选行。</p>
</blockquote>
<ul>
<li>EXTENSION:</li>
</ul>
<blockquote>
<p>为用户自定义或外部扩展预留的规则集合，可在此阶段注入定制优化。</p>
</blockquote>
<ul>
<li>MATERIALIZED_CTE:</li>
</ul>
<blockquote>
<p>判断 CTE 是否需要物化，若可复用则缓存一次结果供多处引用，若仅使用一次则改为内联避免额外开销。</p>
</blockquote>
<ul>
<li>SUM_REWRITER: SUM() 聚合表达式的重写.</li>
</ul>
<blockquote>
<p>官方注释里标明了具体的优化规则即: SUM(x + C) -&gt; SUM(x) + C * COUNT(x), x 为某列字段, C 为常量:
SUM(x + C) 需要对每列的数据先多一次加法, 每列数据额外引入了一条加法指令.
SUM(x) 对一大列连续内存做加法, 可以使用 SIMD，比如 AVX 指令一轮处理8或16个元素, 然后使用一条乘法指令 C * count(X) 即可.</p>
</blockquote>
<ul>
<li>LATE_MATERIALIZATION:</li>
</ul>
<blockquote>
<p>在列式执行中尽量延迟宽列或大对象的加载，等到真正需要输出阶段再取回具体字段。</p>
</blockquote>
<p>这些规则以流水线的方式运行：例如在 <code>FILTER_PULLUP</code> 将谓词统一收集到上层后，<code>FILTER_PUSHDOWN</code> 会立刻尝试把同一组谓词重新分发到 <code>LogicalGet</code>、<code>LogicalJoin</code> 等算子上，从而达到“上提 → 分发 → 裁剪”的闭环。</p>
<h2 id="总结">总结</h2>
<p>本文梳理了 DuckDB 在逻辑层面的执行流程：从 PostgreSQL Parser 得到 AST，到 Binder 绑定语义，再到 <code>LogicalOperator</code> 树的逐步构建，最后交由启发式优化器对计划进行重写。逻辑计划优化器仍然是 DuckDB 中最值得深挖的部分，例如谓词上下推、JOIN 重排、统计信息传播等策略都蕴含着大量实现细节。后续文章将针对其中几类优化算法展开更深入的源码分析。</p>
]]></content>
  </entry><entry>
    <title>InnoDB 读写锁逻辑分析-8.0.25</title>
    <link href="https://leviathan.vip/2024/11/02/innodb-rw-lock/" />
    <id>https://leviathan.vip/2024/11/02/innodb-rw-lock/</id>
    <updated>2024-11-02T13:36:38Z</updated>
    <summary type="html"><![CDATA[<h2 id="版本">版本</h2>
<ul>
<li>MySQL 8.0.25</li>
</ul>
<h2 id="背景">背景</h2>
<p>除了 Mutex 的使用, 在 InnoDB 内核中, 为了增加 InnoDB 的并发读写, 引入了读写锁. Mutex 严格的限制只有一个 thread 可以进入临界区, 但是实际的存储引擎中, 例如针对数据 Page 可以区分读和写, 多个读操作同时访问一个数据 Page 是合法的. InnoDB 实现了一套读写锁的逻辑, 除了读锁 S, 写锁 X, 还引入了 SX 锁, SX 锁和 SX 和 X 锁之间互斥, 但是兼容 S 锁. 实际的使用场景例如数据 Page 在刷脏的时候加的是 SX 锁, 但是允许读 S 锁, 这也符合数据库的使用特征.</p>
<h2 id="rw_lock_t-数据结构">rw_lock_t 数据结构</h2>
<p>struct <code>rw_lock_t</code> 是 InnoDB 读写锁的数据结构, 关键的成员变量有 <code>lock_word</code>, <code>waiters</code>, <code>recursive</code>, <code>writer_thread</code>, 在我们 GDB 调试 InnoDB 代码时, print rw_lock_t 时这几个成员变量可以帮助我们理解该读写锁的持有情况.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="版本">版本</h2>
<ul>
<li>MySQL 8.0.25</li>
</ul>
<h2 id="背景">背景</h2>
<p>除了 Mutex 的使用, 在 InnoDB 内核中, 为了增加 InnoDB 的并发读写, 引入了读写锁. Mutex 严格的限制只有一个 thread 可以进入临界区, 但是实际的存储引擎中, 例如针对数据 Page 可以区分读和写, 多个读操作同时访问一个数据 Page 是合法的. InnoDB 实现了一套读写锁的逻辑, 除了读锁 S, 写锁 X, 还引入了 SX 锁, SX 锁和 SX 和 X 锁之间互斥, 但是兼容 S 锁. 实际的使用场景例如数据 Page 在刷脏的时候加的是 SX 锁, 但是允许读 S 锁, 这也符合数据库的使用特征.</p>
<h2 id="rw_lock_t-数据结构">rw_lock_t 数据结构</h2>
<p>struct <code>rw_lock_t</code> 是 InnoDB 读写锁的数据结构, 关键的成员变量有 <code>lock_word</code>, <code>waiters</code>, <code>recursive</code>, <code>writer_thread</code>, 在我们 GDB 调试 InnoDB 代码时, print rw_lock_t 时这几个成员变量可以帮助我们理解该读写锁的持有情况.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 锁的兼容矩阵:
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 1. X 与 X 和 SX 和 S 全部互斥.
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 2. SX 和 SX 和 X 互斥, 但是兼容 S 锁. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">LOCK</span> <span class="n">COMPATIBILITY</span> <span class="n">MATRIX</span>
</span></span><span class="line"><span class="cl">    <span class="n">S</span> <span class="n">SX</span>  <span class="n">X</span>
</span></span><span class="line"><span class="cl"> <span class="n">S</span>  <span class="o">+</span>  <span class="o">+</span>  <span class="o">-</span>
</span></span><span class="line"><span class="cl"> <span class="n">SX</span> <span class="o">+</span>  <span class="o">-</span>  <span class="o">-</span>
</span></span><span class="line"><span class="cl"> <span class="n">X</span>  <span class="o">-</span>  <span class="o">-</span>  <span class="o">-</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">rw_lock_t</span>
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef UNIV_DEBUG
</span></span></span><span class="line"><span class="cl">    <span class="o">:</span> <span class="k">public</span> <span class="n">latch_t</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif </span><span class="cm">/* UNIV_DEBUG */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 锁状态. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o">&lt;</span><span class="kt">int32_t</span><span class="o">&gt;</span> <span class="n">lock_word</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 是否有线程等待在这个读写锁. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="n">waiters</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 这个锁是否处于递归加锁的状态, 一个线程允许对一个 rw_lock 加了 SX 锁再次申请 X 锁. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o">&lt;</span><span class="kt">bool</span><span class="o">&gt;</span> <span class="n">recursive</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 持有锁的线程 id. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">std</span><span class="o">::</span><span class="n">atomic</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="kr">thread</span><span class="o">::</span><span class="n">id</span><span class="o">&gt;</span> <span class="n">writer_thread</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><h2 id="lock_word">lock_word</h2>
<p><code>lock_word</code> 代表当前读写锁的锁状态, 所以我们可以从 <code>lock_word</code> 来判断这个锁的持有状态, 比如可以得知该锁目前是 S 锁还是 X 锁, 还是 SX 锁. 如果是 S 锁, 有多少 S 锁, 或者当前这个锁是否已经被一个线程预定了 X 锁等等.</p>
<p><code>lock_word</code> 初始值为 <code>X_LOCK_DECR</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cp">#define X_LOCK_DECR 0x20000000 (10 进制 536870912)
</span></span></span><span class="line"><span class="cl"><span class="cp">#define X_LOCK_HALF_DECR 0x10000000 (10 进制 268435456)</span></span></span></code></pre></div><h2 id="申请-x-锁">申请 X 锁</h2>
<p>通过函数 <code>rw_lock_x_lock_low()</code> 来申请获取 X 锁:</p>
<ol>
<li>
<p>判断当前的 <code>lock_word</code> 是否大于 X_LOCK_HALF_DECR.</p>
</li>
<li>
<p>如果条件 1 满足就使用 CAS 减去 X_LOCK_DECR, 然后等待 <code>lock_word</code> 的值为 0.</p>
</li>
<li>
<p>如果条件 1 不满足, 则判断是否可以递归加锁, 即在申请 X 锁之前是否已经持有了 SX 或者 X 锁.</p>
</li>
<li>
<p>如果该线程之前已经持有了 SX 锁, 则将 <code>lock_word</code> 的值减去 X_LOCK_DECR, 然后等待 <code>lock_word</code> 的值为 -X_LOCK_HALF_DECR, 因为 SX 锁和 S 锁兼容, 所以由 SX 锁升级为 X 锁后, 需要等待其他线程持有的 S 锁释放.</p>
</li>
<li>
<p>如果该线程之前已经持有了 X 锁, 则修改 <code>lock_word</code> 后即可.</p>
</li>
<li>
<p>上述条件都不满足, 即其他线程可能持有了 SX 锁或者 X 锁, 则进入 spin 的等待状态.</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">UNIV_INLINE</span>
</span></span><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">rw_lock_x_lock_low</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">rw_lock_t</span> <span class="o">*</span><span class="n">lock</span><span class="p">,</span>       <span class="cm">/*!&lt; in: pointer to rw-lock */</span>
</span></span><span class="line"><span class="cl">    <span class="n">ulint</span> <span class="n">pass</span><span class="p">,</span>            <span class="cm">/*!&lt; in: pass value; != 0, if the lock will
</span></span></span><span class="line"><span class="cl"><span class="cm">                           be passed to another thread to unlock */</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">file_name</span><span class="p">,</span> <span class="cm">/*!&lt; in: file name where lock requested */</span>
</span></span><span class="line"><span class="cl">    <span class="n">ulint</span> <span class="n">line</span><span class="p">)</span>            <span class="cm">/*!&lt; in: line where requested */</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">rw_lock_lock_word_decr</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">X_LOCK_DECR</span><span class="p">,</span> <span class="n">X_LOCK_HALF_DECR</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 1. 如果当前不存在 S 锁或者 SX 锁或者其他 X 锁, 步骤 1 是满足的,
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 步骤 2 也无需等待直接返回加锁成功. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">rw_lock_set_writer_id_and_recursion_flag</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="o">!</span><span class="n">pass</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	
</span></span><span class="line"><span class="cl">    <span class="cm">/* 2. 如果这个锁已经被其他线程加了数量小于 X_LOCK_HALF_DECR 个 S 锁,
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 步骤 1 也是满足的, 但是步骤 2 需要进入等待逻辑直到所有的 S 锁释放,
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 即使当前读写锁上已经有其他的 S 锁, 但是为了防止 X 锁饥饿状态,
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 所以将 X 预先分配给申请线程. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">rw_lock_x_lock_wait</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">pass</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">file_name</span><span class="p">,</span> <span class="n">line</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pass</span> <span class="o">&amp;&amp;</span> <span class="n">lock</span><span class="o">-&gt;</span><span class="n">recursive</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_acquire</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="n">lock</span><span class="o">-&gt;</span><span class="n">writer_thread</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_relaxed</span><span class="p">)</span> <span class="o">==</span>
</span></span><span class="line"><span class="cl">            <span class="n">std</span><span class="o">::</span><span class="n">this_thread</span><span class="o">::</span><span class="n">get_id</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* The existing X or SX lock is from this thread */</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="n">rw_lock_lock_word_decr</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">X_LOCK_DECR</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="cm">/* 之前已经持有了 SX 锁, 由 SX 锁升级为 X 锁. */</span>
</span></span><span class="line"><span class="cl">        <span class="n">rw_lock_x_lock_wait</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">pass</span><span class="p">,</span> <span class="o">-</span><span class="n">X_LOCK_HALF_DECR</span><span class="p">,</span> <span class="n">file_name</span><span class="p">,</span> <span class="n">line</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="cm">/* 之前已经持有了 X 锁. */</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">lock_word</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">lock</span><span class="o">-&gt;</span><span class="n">lock_word</span> <span class="o">==</span> <span class="o">-</span><span class="n">X_LOCK_HALF_DECR</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="cm">/* 1. 上一次直接申请了 X 锁, 所以 lock_word 为 0.
</span></span></span><span class="line"><span class="cl"><span class="cm">         * 2. 上一次是 SX 锁升级为 X 锁, 所以 lock_word 为-X_LOCK_HALF_DECR.
</span></span></span><span class="line"><span class="cl"><span class="cm">         * 3. 所以 X 锁的 lock_word 减去 X_LOCK_DECR. */</span>
</span></span><span class="line"><span class="cl">          <span class="n">lock</span><span class="o">-&gt;</span><span class="n">lock_word</span> <span class="o">-=</span> <span class="n">X_LOCK_DECR</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">ut_ad</span><span class="p">(</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">lock_word</span> <span class="o">&lt;=</span> <span class="o">-</span><span class="n">X_LOCK_DECR</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">          <span class="cm">/* 之前已经持有了两个 X 锁, 所以当前的 lock_word 递减 1 即可. */</span>
</span></span><span class="line"><span class="cl">          <span class="o">--</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">lock_word</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 申请失败, 返回进入等待. */</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut_d</span><span class="p">(</span><span class="n">rw_lock_add_debug_info</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">pass</span><span class="p">,</span> <span class="n">RW_LOCK_X</span><span class="p">,</span> <span class="n">file_name</span><span class="p">,</span> <span class="n">line</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">lock</span><span class="o">-&gt;</span><span class="n">last_x_file_name</span> <span class="o">=</span> <span class="n">file_name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">line</span> <span class="o">&lt;=</span> <span class="n">std</span><span class="o">::</span><span class="n">numeric_limits</span><span class="o">&lt;</span><span class="k">decltype</span><span class="p">(</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">last_x_line</span><span class="p">)</span><span class="o">&gt;::</span><span class="n">max</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">  <span class="n">lock</span><span class="o">-&gt;</span><span class="n">last_x_line</span> <span class="o">=</span> <span class="n">line</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 申请 X 锁成功. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="申请-sx-锁">申请 SX 锁</h2>
<p>通过函数 <code>rw_lock_sx_lock_low()</code> 来申请获取 SX 锁:</p>
<ol>
<li>
<p>判断当前的 <code>lock_word</code> 是否大于 X_LOCK_HALF_DECR.</p>
</li>
<li>
<p>如果条件 1 满足就使用 CAS 减去 X_LOCK_DECR, 因为 SX 锁和 S 锁兼容, 所以无需等待 lock_word 的值为 0.</p>
</li>
<li>
<p>如果条件 1 不满足, 则判断是否可以递归加锁, 即在申请 X 锁之前是否已经持有了 SX 或者 X 锁.</p>
</li>
<li>
<p>如果该线程之前已经成功申请了一个 X 锁, 所以 lock_word -= X_LOCK_HALF_DECR.</p>
</li>
<li>
<p>上述条件都不满足, 即 SX 或者 X 锁已经被其他线程持有, 则进入 spin 的等待状态.</p>
</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">rw_lock_sx_lock_low</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">rw_lock_t</span> <span class="o">*</span><span class="n">lock</span><span class="p">,</span>       <span class="cm">/*!&lt; in: pointer to rw-lock */</span>
</span></span><span class="line"><span class="cl">    <span class="n">ulint</span> <span class="n">pass</span><span class="p">,</span>            <span class="cm">/*!&lt; in: pass value; != 0, if the lock will
</span></span></span><span class="line"><span class="cl"><span class="cm">                           be passed to another thread to unlock */</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">file_name</span><span class="p">,</span> <span class="cm">/*!&lt; in: file name where lock requested */</span>
</span></span><span class="line"><span class="cl">    <span class="n">ulint</span> <span class="n">line</span><span class="p">)</span>            <span class="cm">/*!&lt; in: line where requested */</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">rw_lock_lock_word_decr</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">X_LOCK_HALF_DECR</span><span class="p">,</span> <span class="n">X_LOCK_HALF_DECR</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 1. 判断当前的 lock_word 是否大于 X_LOCK_HALF_DECR.
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 2. 如果条件 1 满足就使用 CAS 减去 X_LOCK_DECR, 因为 SX 锁和 S 锁兼容,
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 所以无需等待 lock_word 的值为 0. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* lock-&gt;recursive == true implies that the lock-&gt;writer_thread is the
</span></span></span><span class="line"><span class="cl"><span class="cm">    current writer. As we are going to write our own thread id in that field it
</span></span></span><span class="line"><span class="cl"><span class="cm">    must be the case that the current writer_thread value is not the current
</span></span></span><span class="line"><span class="cl"><span class="cm">    writer anymore, thus recursive flag must be false.  */</span>
</span></span><span class="line"><span class="cl">    <span class="n">ut_a</span><span class="p">(</span><span class="o">!</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">recursive</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_relaxed</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* Decrement occurred: we are the SX lock owner. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">rw_lock_set_writer_id_and_recursion_flag</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="o">!</span><span class="n">pass</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 设置 sx_recursive 的标记为 1. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">lock</span><span class="o">-&gt;</span><span class="n">sx_recursive</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pass</span> <span class="o">&amp;&amp;</span> <span class="n">lock</span><span class="o">-&gt;</span><span class="n">recursive</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_acquire</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="n">lock</span><span class="o">-&gt;</span><span class="n">writer_thread</span><span class="p">.</span><span class="n">load</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">memory_order_relaxed</span><span class="p">)</span> <span class="o">==</span>
</span></span><span class="line"><span class="cl">            <span class="n">std</span><span class="o">::</span><span class="n">this_thread</span><span class="o">::</span><span class="n">get_id</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* This thread owns an X or SX lock */</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">sx_recursive</span><span class="o">++</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">ut_ad</span><span class="p">((</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">lock_word</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">              <span class="p">((</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">lock_word</span> <span class="o">&lt;=</span> <span class="o">-</span><span class="n">X_LOCK_DECR</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">               <span class="p">(</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">lock_word</span> <span class="o">&gt;</span> <span class="o">-</span><span class="p">(</span><span class="n">X_LOCK_DECR</span> <span class="o">+</span> <span class="n">X_LOCK_HALF_DECR</span><span class="p">))));</span>
</span></span><span class="line"><span class="cl">        <span class="cm">/* 之前已经成功申请了一个 X 锁, 所以 lock_word -= X_LOCK_HALF_DECR. */</span>
</span></span><span class="line"><span class="cl">        <span class="n">lock</span><span class="o">-&gt;</span><span class="n">lock_word</span> <span class="o">-=</span> <span class="n">X_LOCK_HALF_DECR</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* Another thread locked before us */</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut_d</span><span class="p">(</span><span class="n">rw_lock_add_debug_info</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">pass</span><span class="p">,</span> <span class="n">RW_LOCK_SX</span><span class="p">,</span> <span class="n">file_name</span><span class="p">,</span> <span class="n">line</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">lock</span><span class="o">-&gt;</span><span class="n">last_x_file_name</span> <span class="o">=</span> <span class="n">file_name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">line</span> <span class="o">&lt;=</span> <span class="n">std</span><span class="o">::</span><span class="n">numeric_limits</span><span class="o">&lt;</span><span class="k">decltype</span><span class="p">(</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">last_x_line</span><span class="p">)</span><span class="o">&gt;::</span><span class="n">max</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">  <span class="n">lock</span><span class="o">-&gt;</span><span class="n">last_x_line</span> <span class="o">=</span> <span class="n">line</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="申请-s-锁">申请 S 锁</h2>
<p>通过函数 <code>rw_lock_s_lock_low()</code> 来申请获取 S 锁, 判断的逻辑是当前的 <code>lock_word</code> 是否大于 0, 如果大于 0 则使用 CAS 操作减去 1.</p>
<h2 id="如何通过-lock_word-判断锁信息">如何通过 lock_word 判断锁信息</h2>
<ol>
<li>
<p>lock_word = X_LOCK_DECR: 没有任何锁持有.</p>
</li>
<li>
<p>X_LOCK_HALF_DECR &lt; lock_word &lt; X_LOCK_DECR: 存在 S 锁, S 锁的数量是(X_LOCK_DECR - lock_word), 并且没有任何 SX/X 锁的申请等待.</p>
</li>
<li>
<p>lock_word == X_LOCK_HALF_DECR: 存在 SX 锁, 并且没有任何 SX/X 锁的申请在等待.</p>
</li>
<li>
<p>0 &lt; lock_word &lt; X_LOCK_HALF_DECR: 存在 SX 锁和 S 锁, 并且没有任何 SX/X 锁的申请在等待.</p>
</li>
<li>
<p>lock_word == 0: 存在 X 锁, 并且没有任何 SX/X 锁的申请在等待.</p>
</li>
<li>
<p>-X_LOCK_HALF_DECR &lt; lock_word &lt; 0: 存在 S 锁, 并且有一个 X 锁申请在等待.</p>
</li>
<li>
<p>lock_word == -X_LOCK_HALF_DECR: 存在一个 SX 锁和 X 锁, 是递归持有, 并且没有 SX/X 锁的申请在等待.</p>
</li>
<li>
<p>-X_LOCK_DECR &lt; lock_word &lt; -X_LOCK_HALF_DECR: 存在 S 锁, 并且有一个持有 SX 锁的线程在等待升级为 X 锁.</p>
</li>
<li>
<p>lock_word == -X_LOCK_DECR: 存在两个递归持有的 X 锁.</p>
</li>
<li>
<p>-(X_LOCK_DECR + X_LOCK_HALF_DECR) &lt; lock_word &lt; -X_LOCK_DECR: 存在 X 锁, 数量为 2 - (lock_word + X_LOCK_DECR).</p>
</li>
<li>
<p>lock_word == -(X_LOCK_DECR + X_LOCK_HALF_DECR): 存在递归持有的 1 个 SX 锁和两个 X 锁.</p>
</li>
<li>
<p>lock_word &lt; -(X_LOCK_DECR + X_LOCK_HALF_DECR): 存在递归持有的 X 锁和 SX 锁, X 锁的数量是 2 - (lock_word + X_LOCK_DECR + X_LOCK_HALF_DECR).</p>
</li>
</ol>
<h2 id="总结">总结</h2>
<p>InnoDB 自行实现了一套读写锁, 并且允许 X 锁的预定从而避免饿死, 不过存在一个可能的情况是如果同时有多个 X 锁来申请, 仍然需要争抢, 并不保证申请顺序.</p>
<h2 id="参考">参考</h2>
<p><a href="https://dev.mysql.com/worklog/task/?id=6363">WL#6363: InnoDB: implement SX-lock for rw_lock</a></p>
]]></content>
  </entry><entry>
    <title>InnoDB 关于 B+ tree 的整理</title>
    <link href="https://leviathan.vip/2024/07/18/innodb-b-plus-tree-curd/" />
    <id>https://leviathan.vip/2024/07/18/innodb-b-plus-tree-curd/</id>
    <updated>2024-07-18T21:06:27Z</updated>
    <summary type="html"><![CDATA[<h2 id="版本">版本</h2>
<ul>
<li>MySQL 8.0.25</li>
</ul>
<h2 id="背景">背景</h2>
<p>InnoDB 使用 B+ 树作为它的索引数据结构, B+ 树作为一种经典的数据结构具备高效的读写查询, 本文主要分析 InnoDB 中 B+ 树对于 Record 的增删改如何实现, 理解 Record 在 InnoDB 中的 B+ 树如何增删改，可以更直观的帮助我们理解 InnoDB 的索引组织方式.</p>
<h2 id="b-tree-的插入操作">B+ tree 的插入操作</h2>
<p>在MySQL中, 一条 Insert 语句就是一个 Record 的插入操作, 我们以插入一条聚簇索引(非压缩)为例, 略过连接建立过程和 SQL parse 阶段, 经过 InnoDB 的 <code>handler::ha_write_row()</code> 调用:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"> <span class="o">------------</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span>    <span class="p">...</span>     <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">------------</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>    <span class="cm">/* Cluster Index 聚簇索引的插入 */</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>    <span class="o">---------------------------------</span>
</span></span><span class="line"><span class="cl">   <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">row_ins_clust_index_entry_low</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">        <span class="o">---------------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="cm">/* 针对 delete-marked 的记录, 并且该记录的唯一字段和待插入的 Record 一致，则 Inplace Modify. */</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>    <span class="o">---------------------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">row_ins_clust_index_entry_by_modify</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>    <span class="o">---------------------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>           <span class="cm">/* 乐观插入 */</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>    <span class="o">-----------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">btr_cur_optimistic_insert</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>    <span class="o">-----------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>       <span class="cm">/* 加锁并记录 Undo Log */</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>    <span class="o">-----------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">btr_cur_ins_lock_and_undo</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>    <span class="o">-----------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>        <span class="cm">/* 插入b+树 */</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>    <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">page_cur_tuple_insert</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>           <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>            <span class="cm">/* 悲观插入 */</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>    <span class="o">------------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">btr_cur_pessimistic_insert</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">               <span class="o">------------------------------</span></span></span></code></pre></div><p>上述的调用过程说明了插入一条 Record 的过程, 具体的分析如下:</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="版本">版本</h2>
<ul>
<li>MySQL 8.0.25</li>
</ul>
<h2 id="背景">背景</h2>
<p>InnoDB 使用 B+ 树作为它的索引数据结构, B+ 树作为一种经典的数据结构具备高效的读写查询, 本文主要分析 InnoDB 中 B+ 树对于 Record 的增删改如何实现, 理解 Record 在 InnoDB 中的 B+ 树如何增删改，可以更直观的帮助我们理解 InnoDB 的索引组织方式.</p>
<h2 id="b-tree-的插入操作">B+ tree 的插入操作</h2>
<p>在MySQL中, 一条 Insert 语句就是一个 Record 的插入操作, 我们以插入一条聚簇索引(非压缩)为例, 略过连接建立过程和 SQL parse 阶段, 经过 InnoDB 的 <code>handler::ha_write_row()</code> 调用:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"> <span class="o">------------</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span>    <span class="p">...</span>     <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">------------</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>    <span class="cm">/* Cluster Index 聚簇索引的插入 */</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>    <span class="o">---------------------------------</span>
</span></span><span class="line"><span class="cl">   <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">row_ins_clust_index_entry_low</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">        <span class="o">---------------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="cm">/* 针对 delete-marked 的记录, 并且该记录的唯一字段和待插入的 Record 一致，则 Inplace Modify. */</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>    <span class="o">---------------------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">row_ins_clust_index_entry_by_modify</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>    <span class="o">---------------------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>           <span class="cm">/* 乐观插入 */</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>    <span class="o">-----------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">btr_cur_optimistic_insert</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>    <span class="o">-----------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>       <span class="cm">/* 加锁并记录 Undo Log */</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>    <span class="o">-----------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">btr_cur_ins_lock_and_undo</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>    <span class="o">-----------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>        <span class="cm">/* 插入b+树 */</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">|</span>    <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>      <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">page_cur_tuple_insert</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>           <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>            <span class="cm">/* 悲观插入 */</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>    <span class="o">------------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">btr_cur_pessimistic_insert</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">               <span class="o">------------------------------</span></span></span></code></pre></div><p>上述的调用过程说明了插入一条 Record 的过程, 具体的分析如下:</p>
<ul>
<li>
<p><code>row_ins_clust_index_entry_low()</code> 函数的参数包括我们需要插入的 Record 和当前的 <code>dict_index_t</code> 索引. 首先我们需要通过 <code>pcur</code> 游标来定位我们需要插入的位置:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* 调用 btr_pcur_open() 定位待插入的位置.
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 参数 entry 是待插入的 Record, search mode 是 PAGE_CUR_LE, 即定位到一个小于等于待插入记录的 Record.
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 举例假如当前存在 Record 是[1, 2, 3], 我们插入5, 即 cursor 会定位到3. */</span>
</span></span><span class="line"><span class="cl"><span class="n">btr_pcur_open</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">entry</span><span class="p">,</span> <span class="n">PAGE_CUR_LE</span><span class="p">,</span> <span class="n">mode</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pcur</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mtr</span><span class="p">);</span></span></span></code></pre></div></li>
<li>
<p>针对 cursor 返回的 Record 检查主键重复的问题.</p>
</li>
<li>
<p>调用 <code>btr_cur_optimistic_insert()</code> 乐观插入:</p>
<ul>
<li>
<p>通过 cursor 定位 leaf page, 计算 Record 的物理长度.</p>
</li>
<li>
<p>假如 Record 的大小超过了 Page 的剩余空间, 则乐观插入失败，需要调用悲观插入.</p>
<ul>
<li>
<p>对于乐观插入成功的情况下, 调用 <code>btr_cur_ins_lock_and_undo()</code> 记录 Undo Log.</p>
</li>
<li>
<p>调用 <code>page_cur_insert_rec_low()</code> 完成 Page 的插入并记录类型为 MLOG_REC_INSERT 的 Redo Log.</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="b-tree-的悲观插入">B+ tree 的悲观插入</h3>
<p>对于需要分裂的 Page 需要调用 <code>btr_cur_pessimistic_insert()</code>, 悲观插入会对 B+tree 进行分裂操作:</p>
<ol>
<li>如果插入点在 Page 的最后, 则尝试分裂 <code>btr_insert_into_right_sibling()</code>.</li>
<li>如果插入点为顺序插入, 即待插入的位置位于 PAGE_LAST_INSERT 之后, 则直接在当前插入点向后(FSP_UP)分裂 <code>btr_page_get_split_rec_to_right()</code>.</li>
<li>如果插入点为递减插入, 即待插入的位置位于 PAGE_LAST_INSERT 之前,  则直接在当前插入点向前(FSP_DOWN)分裂 <code>btr_page_get_split_rec_to_left()</code>.</li>
<li>如果插入位置都不位于 PAGE_LAST_INSERT 左右, 则直接进行中间点分裂 <code>page_get_middle_rec()</code>, 以一个中间 record 进行分裂.</li>
</ol>
<h3 id="b-tree-的分裂">B+ tree 的分裂</h3>
<h3 id="b-tree-的加锁流程">B+ tree 的加锁流程</h3>
<ul>
<li>乐观插入</li>
</ul>
<p>乐观插入使用的 mode 为 BTR_MODIFY_LEAF, 加锁顺序:</p>
<ol>
<li>先对 dict_index_t 加 S 锁.</li>
<li>查找过程中针对所有的 non-leaf page 加 S 锁, 因为需要目标 leaf page 进行修改，所以对 leaf page 加 X 锁.</li>
<li>到达 leaf level 层后(height == 0), 开始释放 lock: 释放 dict_index_t 的 s 锁; 释放 page 的 s 锁, 释放顺序是从上至下.</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">row_ins_clust_index_entry_low</span><span class="p">(</span><span class="n">flags</span><span class="p">,</span> <span class="n">BTR_MODIFY_LEAF</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">n_uniq</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                              <span class="n">entry</span><span class="p">,</span> <span class="n">thr</span><span class="p">,</span> <span class="n">dup_chk_only</span><span class="p">);</span></span></span></code></pre></div><ul>
<li>悲观插入</li>
</ul>
<p>悲观插入使用的 mode 为 BTR_MODIFY_TREE, 加锁顺序:</p>
<ol>
<li>先对 dict_index_t 加 SX 锁.</li>
<li>在 search 过程中不会针对 page 加任何锁(RW_NO_LATCH), 但会保留整个 branch 涉及的 page.</li>
<li>对于路径上不会发生 SMO 的 page 全部都释放.</li>
<li>最后针对路径可能涉及 SMO 的所有 page 加 X 锁.</li>
<li>SMO 过程中保留dict_index_t 的 sx 锁和可能涉及 SMO 的 page 的 x 锁.</li>
</ol>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">row_ins_clust_index_entry_low</span><span class="p">(</span><span class="n">flags</span><span class="p">,</span> <span class="n">BTR_MODIFY_TREE</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">n_uniq</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                              <span class="n">entry</span><span class="p">,</span> <span class="n">thr</span><span class="p">,</span> <span class="n">dup_chk_only</span><span class="p">);</span></span></span></code></pre></div><p>BTR_MODIFY_TREE 涉及 B+ tree 的 SMO 操作, 因为 dict_index_t 的 SX 锁的互斥关系, 所以在一个索引 B+tree 上, 同时只能有一个 SMO 操作.</p>
<h2 id="record-的删除操作">Record 的删除操作</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl">  <span class="cm">/* Record 删除操作. */</span>
</span></span><span class="line"><span class="cl"> <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="n">ha_innobase</span><span class="o">::</span><span class="n">delete_row</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>    <span class="cm">/* Record 的更新删除操作都经过 row_update_for_mysql() 入口. */</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>    <span class="o">------------------------</span>
</span></span><span class="line"><span class="cl">   <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">row_update_for_mysql</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">        <span class="o">------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">|</span>    <span class="o">----------------------------------------</span>
</span></span><span class="line"><span class="cl">          <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">row_update_for_mysql_using_upd_graph</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">               <span class="o">----------------------------------------</span>
</span></span><span class="line"><span class="cl">                 <span class="o">|</span>
</span></span><span class="line"><span class="cl">                 <span class="o">|</span>
</span></span><span class="line"><span class="cl">                 <span class="o">|</span>
</span></span><span class="line"><span class="cl">                 <span class="o">|</span>    <span class="o">-----------</span>
</span></span><span class="line"><span class="cl">                 <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">row_upd</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                 <span class="o">|</span>   <span class="o">------------</span>
</span></span><span class="line"><span class="cl">                       <span class="o">|</span>
</span></span><span class="line"><span class="cl">                       <span class="o">|</span>    <span class="o">----------------</span>
</span></span><span class="line"><span class="cl">                       <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">row_upd_step</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                            <span class="o">----------------</span>
</span></span><span class="line"><span class="cl">                             <span class="o">|</span>
</span></span><span class="line"><span class="cl">                             <span class="o">|</span>
</span></span><span class="line"><span class="cl">                             <span class="o">|</span>       <span class="cm">/* 加锁并记录 Undo Log */</span>
</span></span><span class="line"><span class="cl">                             <span class="o">|</span>    <span class="o">------------------------------</span>
</span></span><span class="line"><span class="cl">                             <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">row_upd_del_mark_clust_rec</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                  <span class="o">------------------------------</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">|</span>    <span class="cm">/* 保存 Record 至 node-&gt; row 用来删除二级索引. */</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">|</span>    <span class="o">---------------------</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">row_upd_store_row</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">|</span>    <span class="o">---------------------</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">|</span>     <span class="cm">/* 上锁, 记录 Undo Log, 设置 Record 的标志位为 REC_INFO_DELETED_FLAG. */</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">|</span>    <span class="o">----------------------------------</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">btr_cur_del_mark_set_clust_rec</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                         <span class="o">----------------------------------</span></span></span></code></pre></div><p>通过源码分析我们可以发现 Record 的删除操作对于聚簇索引并不是真的物理删除，仅仅是标记为 REC_INFO_DELETED_FLAG. 而对于其他的二级索引, 依然采用设置标记的方法 (<code>btr_cur_del_mark_set_sec_rec()</code>).</p>
<h2 id="b-tree-的悲观删除">B+ tree 的悲观删除</h2>
<p>InnoDB 中 B+ tree record 的删除操作由 purge 线程来处理, 用户的删除标记为 delete mark 以后, 由 purge 线程来后台清理 record 所占用的空间, purge 的流程为先使用乐观删除, 即待清理 record 的空间回收以后不会发生 SMO 操作, 但是以下条件会判断是否需要进行悲观删除:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">ibool</span> <span class="nf">btr_cur_can_delete_without_compress</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">btr_cur_t</span> <span class="o">*</span><span class="n">cursor</span><span class="p">,</span> <span class="cm">/*!&lt; in: btr cursor */</span>
</span></span><span class="line"><span class="cl">    <span class="n">ulint</span> <span class="n">rec_size</span><span class="p">,</span>    <span class="cm">/*!&lt; in: rec_get_size(btr_cur_get_rec(cursor)</span><span class="p">)</span><span class="err">*/</span>
</span></span><span class="line"><span class="cl">    <span class="n">mtr_t</span> <span class="o">*</span><span class="n">mtr</span><span class="p">)</span>        <span class="cm">/*!&lt; in: mtr */</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">page_t</span> <span class="o">*</span><span class="n">page</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">mtr_memo_contains</span><span class="p">(</span><span class="n">mtr</span><span class="p">,</span> <span class="n">btr_cur_get_block</span><span class="p">(</span><span class="n">cursor</span><span class="p">),</span> <span class="n">MTR_MEMO_PAGE_X_FIX</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">page</span> <span class="o">=</span> <span class="n">btr_cur_get_page</span><span class="p">(</span><span class="n">cursor</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 是否需要 SMO(merge) 操作
</span></span></span><span class="line"><span class="cl"><span class="cm">   * 1. 删除这条 record 后该 Page 的剩余空间小于 50%(DICT_INDEX_MERGE_THRESHOLD_DEFAULT).
</span></span></span><span class="line"><span class="cl"><span class="cm">   * 2. 或者 Page 的前后节点均为 FIL_NULL. (代表这一层只有一个 Page, 没有保留的必要了)
</span></span></span><span class="line"><span class="cl"><span class="cm">   * 3. 或者 Page 的 user records 数量小于 2. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">((</span><span class="n">page_get_data_size</span><span class="p">(</span><span class="n">page</span><span class="p">)</span> <span class="o">-</span> <span class="n">rec_size</span> <span class="o">&lt;</span>
</span></span><span class="line"><span class="cl">       <span class="n">BTR_CUR_PAGE_COMPRESS_LIMIT</span><span class="p">(</span><span class="n">cursor</span><span class="o">-&gt;</span><span class="n">index</span><span class="p">))</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">      <span class="p">((</span><span class="n">btr_page_get_next</span><span class="p">(</span><span class="n">page</span><span class="p">,</span> <span class="n">mtr</span><span class="p">)</span> <span class="o">==</span> <span class="n">FIL_NULL</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">       <span class="p">(</span><span class="n">btr_page_get_prev</span><span class="p">(</span><span class="n">page</span><span class="p">,</span> <span class="n">mtr</span><span class="p">)</span> <span class="o">==</span> <span class="n">FIL_NULL</span><span class="p">))</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="n">page_get_n_recs</span><span class="p">(</span><span class="n">page</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 判断是否为 root page. */</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">dict_index_get_page</span><span class="p">(</span><span class="n">cursor</span><span class="o">-&gt;</span><span class="n">index</span><span class="p">)</span> <span class="o">==</span> <span class="n">page_get_page_no</span><span class="p">(</span><span class="n">page</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="n">TRUE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>悲观删除 <code>btr_cur_pessimistic_delete()</code>:</p>
<ol>
<li>
<p>对于只剩下一个 Record 且不是 Root Page 的情况, 需要直接删除 Page(btr_discard_page()).</p>
</li>
<li>
<p>在悲观删除中如果删除的 Record 是 Page 上的第一个, 需要更新父节点的 node ptr.</p>
<ul>
<li>
<p>在父节点层更新 node ptr, 也通过悲观删除(btr_cur_pessimistic_delete()) 递归删除.</p>
</li>
<li>
<p>使用 next_rec 重新构建一个 node ptr 插入父节点.</p>
</li>
</ul>
</li>
<li>
<p>其余的情况直接调用 btr_cur_compress_if_useful() 尝试合并 Page.</p>
<ul>
<li>
<p>针对不存在左右节点的 Page, 即代表该层只有这一个 Page, 将 record 合并到父节点.</p>
</li>
<li>
<p>存在左右 Page 的情况下, 首先尝试与左节点 merge, 如果无法与左 Page 合并, 则尝试与右 Page 合并.</p>
</li>
<li>
<p>合并的流程均为拷贝需要释放的 Page 上的 record 至 merge Page 上, 更新左右 Page 指针, 删除 Page 所指向的 node pointer, 更新删除的 Page 上原有的 lock (table lock, record lock) 至 merge Page 上.</p>
</li>
<li>
<p>调用 btr_page_free() 将 Page 放回 segment 的 free list.</p>
</li>
<li>
<p>被删除的 Page 的 node pointer 在删除时可能会导致父节点也发生 merge 操作, 所以可能出现递归 merge 的情况, 在获取 node pointer 时使用的 BTR MODE 是 BTR_CONT_MODIFY_TREE.</p>
</li>
</ul>
</li>
<li>
<p>merge 的过程中可能造成 non-leaf level 的 node ptr 被删除, 如果向左 merge, 则保留左边 page 的 node ptr; 如果向右 merge, 则保留当前 page 的 node ptr, 删除右边 page 的 node ptr.</p>
</li>
<li>
<p>所以 InnoDB B+ tree 中的 node ptr 都是小于等于下层 page 的最小的 user record (小于的情况是因为可能存在下层的最小的 user record 被删除的情况, 在标记为 DELETE MARK 的场景里并不会直接更新 node ptr).</p>
</li>
</ol>
<h2 id="record-的修改操作">Record 的修改操作</h2>
<p>对于 Record 的修改操作, 使用了和删除操作一样的接口 <code>row_upd_clust_step() </code>. 对于修改存在多种不同的处理方法:</p>
<ol>
<li>对于只修改聚簇索引，而无需修改二级索引的 Update 操作, 调用 <code>row_upd_clust_rec()</code>, 对于仅修改聚簇也存在两种情况: 是否存在 Record 长度的变化.</li>
</ol>
<ul>
<li>
<p>对于 Update 后长度不变的 Record, 调用 <code>btr_cur_update_in_place()</code> 原地修改.</p>
</li>
<li>
<p>对于 Update 后引起 Record 长度变化的操作, 依然会根据当前 Page 的剩余空间调用乐观更新(<code>btr_cur_optimistic_update()</code>)和悲观更新(<code>btr_cur_pessimistic_update()</code>). 引起 Record 长度变化的 Update 操作都是 append 写入方式, 对于旧的 Record 需要更新其标志位, 插入 Page 的 <code>PAGE_FREE</code> 链表.</p>
</li>
</ul>
<ol start="2">
<li>
<p>对于会影响排序的字段, 调用 <code>row_upd_clust_rec_by_insert()</code> 更新.</p>
</li>
<li>
<p>对于需要同时修改聚簇索引和二级索引的 Update 操作, 依然调用 <code>row_upd_clust_rec()</code> 完成. 与 一样会在使用 <code>row_upd_store_row()</code> 记录旧的 Record 至 row-&gt;node, 以供二级索引更新使用.</p>
</li>
<li>
<p>对于二级索引的修改操作，全部采用标记删除后重新插入的方式.</p>
</li>
</ol>
<h2 id="总结">总结</h2>
<p>我们通过源码分析了 InnoDB 中关于索引部分的增删改步骤, 需要注意的是 B+ tree 中的增删改流程全部处于同一个 trx 的保护中，因此对于聚簇索引和二级索引的修改都保证了原子性, 这里也涉及 InnoDB 的 Undo Log 模块和事务锁系统模块.</p>
]]></content>
  </entry><entry>
    <title>InnoDB Record 细节整理</title>
    <link href="https://leviathan.vip/2022/06/08/innodb-record/" />
    <id>https://leviathan.vip/2022/06/08/innodb-record/</id>
    <updated>2022-06-08T22:03:08Z</updated>
    <summary type="html"><![CDATA[<h2 id="背景">背景</h2>
<p>InnoDB 作为目前 MySQL 的主要存储引擎，其中 record 细节繁琐，这里仅做整理以便查阅. 版本基于 MySQL-8.0.25.</p>
<h2 id="数据结构">数据结构</h2>
<h3 id="innodb-record-的逻辑格式-dtuple_t">InnoDB record 的逻辑格式: dtuple_t</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/** Structure for an SQL data tuple of fields (logical record) */</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">dtuple_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** Number of fields in dtuple */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">n_fields</span><span class="p">;</span> <span class="cm">/* 当前 dtuple 记录的字段数量. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** number of fields which should be used in comparison services of rem0cmp.*;
</span></span></span><span class="line"><span class="cl"><span class="cm">  the index search is performed by comparing only these fields, others are
</span></span></span><span class="line"><span class="cl"><span class="cm">  ignored; the default value in dtuple creation is the same value as n_fields */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">n_fields_cmp</span><span class="p">;</span> <span class="cm">/* 当前 dtuple 中可以用来比较的字段数量, 可以通过
</span></span></span><span class="line"><span class="cl"><span class="cm">                       * dtuple_set_n_fields_cmp() 设置. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** Fields. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">dfield_t</span> <span class="o">*</span><span class="n">fields</span><span class="p">;</span> <span class="cm">/* 当前 dtuple 的字段内容. */</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/** Structure for an SQL data field */</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="nc">dfield_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">;</span>       <span class="cm">/*!&lt; pointer to data */</span>
</span></span><span class="line"><span class="cl">      <span class="kt">unsigned</span> <span class="nl">ext</span> <span class="p">:</span> <span class="mi">1</span><span class="p">;</span> <span class="cm">/*!&lt; TRUE=externally stored, FALSE=local */</span>
</span></span><span class="line"><span class="cl">      <span class="kt">unsigned</span> <span class="nl">spatial_status</span> <span class="p">:</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/*!&lt; spatial status of externally stored field
</span></span></span><span class="line"><span class="cl"><span class="cm">        in undo log for purge */</span>
</span></span><span class="line"><span class="cl">      <span class="kt">unsigned</span> <span class="n">len</span><span class="p">;</span> <span class="cm">/*!&lt; data length; UNIV_SQL_NULL if SQL null 数据长度 */</span>
</span></span><span class="line"><span class="cl">      <span class="n">dtype_t</span> <span class="n">type</span><span class="p">;</span> <span class="cm">/*!&lt; type of data  数据类型*/</span>
</span></span><span class="line"><span class="cl">      
</span></span><span class="line"><span class="cl">      <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="err">*/</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** Compare a data tuple to a physical record.
</span></span></span><span class="line"><span class="cl"><span class="cm">    * dtuple_t 与 rec_t 的比较函数. */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">int</span> <span class="n">compare</span><span class="p">(</span><span class="k">const</span> <span class="n">rec_t</span> <span class="o">*</span><span class="n">rec</span><span class="p">,</span> <span class="k">const</span> <span class="n">dict_index_t</span> <span class="o">*</span><span class="n">index</span><span class="p">,</span> <span class="k">const</span> <span class="n">ulint</span> <span class="o">*</span><span class="n">offsets</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">ulint</span> <span class="o">*</span><span class="n">matched_fields</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>MySQL SQL 层的 record 可以通过 <code>row_sel_convert_mysql_key_to_innobase()</code> 转换为 InnoDB 可识别的 <code>dtuple_t</code> 结构.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="背景">背景</h2>
<p>InnoDB 作为目前 MySQL 的主要存储引擎，其中 record 细节繁琐，这里仅做整理以便查阅. 版本基于 MySQL-8.0.25.</p>
<h2 id="数据结构">数据结构</h2>
<h3 id="innodb-record-的逻辑格式-dtuple_t">InnoDB record 的逻辑格式: dtuple_t</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/** Structure for an SQL data tuple of fields (logical record) */</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">dtuple_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** Number of fields in dtuple */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">n_fields</span><span class="p">;</span> <span class="cm">/* 当前 dtuple 记录的字段数量. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** number of fields which should be used in comparison services of rem0cmp.*;
</span></span></span><span class="line"><span class="cl"><span class="cm">  the index search is performed by comparing only these fields, others are
</span></span></span><span class="line"><span class="cl"><span class="cm">  ignored; the default value in dtuple creation is the same value as n_fields */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">n_fields_cmp</span><span class="p">;</span> <span class="cm">/* 当前 dtuple 中可以用来比较的字段数量, 可以通过
</span></span></span><span class="line"><span class="cl"><span class="cm">                       * dtuple_set_n_fields_cmp() 设置. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** Fields. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">dfield_t</span> <span class="o">*</span><span class="n">fields</span><span class="p">;</span> <span class="cm">/* 当前 dtuple 的字段内容. */</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/** Structure for an SQL data field */</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="nc">dfield_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">;</span>       <span class="cm">/*!&lt; pointer to data */</span>
</span></span><span class="line"><span class="cl">      <span class="kt">unsigned</span> <span class="nl">ext</span> <span class="p">:</span> <span class="mi">1</span><span class="p">;</span> <span class="cm">/*!&lt; TRUE=externally stored, FALSE=local */</span>
</span></span><span class="line"><span class="cl">      <span class="kt">unsigned</span> <span class="nl">spatial_status</span> <span class="p">:</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/*!&lt; spatial status of externally stored field
</span></span></span><span class="line"><span class="cl"><span class="cm">        in undo log for purge */</span>
</span></span><span class="line"><span class="cl">      <span class="kt">unsigned</span> <span class="n">len</span><span class="p">;</span> <span class="cm">/*!&lt; data length; UNIV_SQL_NULL if SQL null 数据长度 */</span>
</span></span><span class="line"><span class="cl">      <span class="n">dtype_t</span> <span class="n">type</span><span class="p">;</span> <span class="cm">/*!&lt; type of data  数据类型*/</span>
</span></span><span class="line"><span class="cl">      
</span></span><span class="line"><span class="cl">      <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="err">*/</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** Compare a data tuple to a physical record.
</span></span></span><span class="line"><span class="cl"><span class="cm">    * dtuple_t 与 rec_t 的比较函数. */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">int</span> <span class="n">compare</span><span class="p">(</span><span class="k">const</span> <span class="n">rec_t</span> <span class="o">*</span><span class="n">rec</span><span class="p">,</span> <span class="k">const</span> <span class="n">dict_index_t</span> <span class="o">*</span><span class="n">index</span><span class="p">,</span> <span class="k">const</span> <span class="n">ulint</span> <span class="o">*</span><span class="n">offsets</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="n">ulint</span> <span class="o">*</span><span class="n">matched_fields</span><span class="p">)</span> <span class="k">const</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>MySQL SQL 层的 record 可以通过 <code>row_sel_convert_mysql_key_to_innobase()</code> 转换为 InnoDB 可识别的 <code>dtuple_t</code> 结构.</p>
<h3 id="索引内存结构-dict_index_t">索引内存结构: dict_index_t</h3>
<ul>
<li>index-&gt;table-&gt;n_cols: table 的列数，包含用户定义的列 + 3 列系统列(DB_ROW_ID, DB_TRX_ID, DB_ROLL_PTR).</li>
<li>index-&gt;table-&gt;cols: 存上面 n_cols 个列的数组, 系统列在倒数后3个.</li>
<li>index-&gt;n_fields: 当前索引包含的列数，小于等于上面的 index-&gt;table-&gt;n_cols.</li>
<li>index-&gt;fields: 记录当前索引 column 的描述信息, 列名，长度, 顺序 or 倒序</li>
</ul>
<p>Record Node:</p>
<ul>
<li>
<p>对于主键索引 leaf node:</p>
<ul>
<li>
<ol>
<li>如果定义了主键, 那么系统列就没有 DB_ROW_ID，那么此时 n_fields 比 n_cols 小 1.</li>
</ol>
</li>
<li>
<ol start="2">
<li>如果没有定义主键, 那么系统列就包含 DB_ROW_ID，那么此时 n_fields 和 n_cols 值一样.</li>
</ol>
</li>
</ul>
</li>
<li>
<p>对于主键索引 non-leaf node:</p>
<ul>
<li>
<ol>
<li>n_fields 包含所有唯一字段 + Page Number, 数量为 index-&gt;n_uniq + 1.</li>
</ol>
</li>
</ul>
</li>
<li>
<p>对于二级索引 leaf node:</p>
<ul>
<li>
<ol>
<li>n_fields 就是包含二级索引定义的列数 + 主键列数.</li>
</ol>
</li>
</ul>
</li>
<li>
<p>对于二级索引 non-leaf node:</p>
<ul>
<li>
<ol>
<li>n_fields 就是包含二级索引定义的列数 + 主键列数 + Page Number, 数量为 index-&gt;n_fields + 1.</li>
</ol>
</li>
</ul>
</li>
</ul>
<p>使用 <code>dict_index_build_node_ptr()</code> 构建 non-leaf node:</p>
<h3 id="innodb-物理-record-rec_t">InnoDB 物理 record: rec_t</h3>
<p>offsets 数组由 <code>rec_get_offsets()</code>, 数组大小由 n_fields + 1 + REC_OFFS_HEADER_SIZE 决定.</p>
<ul>
<li>offsets[0] = n_alloc    /* n_alloc 是数组元素个数. */</li>
<li>offsets[1] = n_fields   /* n_fields 是 record 列数. */</li>
<li>offsets[2] = extra size</li>
<li>offsets[3.. 3 + n_fields] /* 记录每个 field 的结束偏移. */</li>
</ul>
<p><code>rec_t</code> 可以直接通过 <code>cmp_dtuple_rec_with_match_low()</code> 与 <code>dtuple_t</code> 比较:</p>
<p><code>rec_t</code> 可以通过 offsets 数组分别获取对应的 filed 字段, 再与 <code>((dfield_t *)tuple-&gt;fields + n)</code> 直接进行比较.</p>
<h3 id="b-tree-游标-btr_pcur_t">B-tree 游标: btr_pcur_t</h3>
<p><code>btr_pcur_t</code> 是在 search 或者 modify 过程中用来定位的游标, 其中记录定位信息, 可以直接通过 <code>store_position()</code> 来保存，通过 <code>restore_position()</code> 可以恢复至上一次保存 record 位置信息.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">btr_pcur_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 保存 pcur 记录的信息. */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">void</span> <span class="nf">store_position</span><span class="p">(</span><span class="n">mtr_t</span> <span class="o">*</span><span class="n">mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 恢复出来上一次 pcur 保存的位置. */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">bool</span> <span class="nf">restore_position</span><span class="p">(</span><span class="n">ulint</span> <span class="n">latch_mode</span><span class="p">,</span> <span class="n">mtr_t</span> <span class="o">*</span><span class="n">mtr</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">ulint</span> <span class="n">line</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** pcur 定位的元信息: index, block, n_fileds ... */</span>
</span></span><span class="line"><span class="cl">  <span class="n">btr_cur_t</span> <span class="n">m_btr_cur</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** true if old_rec is stored */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">bool</span> <span class="n">m_old_stored</span><span class="p">{</span><span class="nb">false</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 保存当前 pcur 指向的 record. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">rec_t</span> <span class="o">*</span><span class="n">m_old_rec</span><span class="p">{</span><span class="k">nullptr</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 记录 m_old_rec 的 filed 数量. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">m_old_n_fields</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 记录数据 page 的 modify clock. */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">uint64_t</span> <span class="n">m_modify_clock</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>store_position()</code> 保存位置信息:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">btr_pcur_t</span><span class="o">::</span><span class="n">store_position</span><span class="p">(</span><span class="n">mtr_t</span> <span class="o">*</span><span class="n">mtr</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">m_pos_state</span> <span class="o">==</span> <span class="n">BTR_PCUR_IS_POSITIONED</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">m_latch_mode</span> <span class="o">!=</span> <span class="n">BTR_NO_LATCHES</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">auto</span> <span class="n">block</span> <span class="o">=</span> <span class="n">get_block</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="k">auto</span> <span class="n">index</span> <span class="o">=</span> <span class="n">btr_cur_get_index</span><span class="p">(</span><span class="n">get_btr_cur</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">auto</span> <span class="n">page_cursor</span> <span class="o">=</span> <span class="n">get_page_cur</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* pcur 指向的 record. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">auto</span> <span class="n">rec</span> <span class="o">=</span> <span class="n">page_cur_get_rec</span><span class="p">(</span><span class="n">page_cursor</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* record 所在的 page. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">auto</span> <span class="n">page</span> <span class="o">=</span> <span class="n">page_align</span><span class="p">(</span><span class="n">rec</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* record 在 page 上的 offset. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">auto</span> <span class="n">offs</span> <span class="o">=</span> <span class="n">page_offset</span><span class="p">(</span><span class="n">rec</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">page_rec_is_supremum_low</span><span class="p">(</span><span class="n">offs</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* pcur 指向的是一个 supremum record, 则保存前一个 record. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">rec</span> <span class="o">=</span> <span class="n">page_rec_get_prev</span><span class="p">(</span><span class="n">rec</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">m_rel_pos</span> <span class="o">=</span> <span class="n">BTR_PCUR_AFTER</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">page_rec_is_infimum_low</span><span class="p">(</span><span class="n">offs</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* pcur 指向的是一个 infimum record, 则保存后一个 record. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">rec</span> <span class="o">=</span> <span class="n">page_rec_get_next</span><span class="p">(</span><span class="n">rec</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">m_rel_pos</span> <span class="o">=</span> <span class="n">BTR_PCUR_BEFORE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* pcur 指向的是一个 user record, 直接保存这个 record. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">m_rel_pos</span> <span class="o">=</span> <span class="n">BTR_PCUR_ON</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">m_old_stored</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 保存当前指向的 record 至 m_old_rec. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">m_old_rec</span> <span class="o">=</span> <span class="n">dict_index_copy_rec_order_prefix</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">rec</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">m_old_n_fields</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                               <span class="o">&amp;</span><span class="n">m_old_rec_buf</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">m_buf_size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">m_block_when_stored</span><span class="p">.</span><span class="n">store</span><span class="p">(</span><span class="n">block</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* Function try to check if block is S/X latch. */</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* 记录 modify clock. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">m_modify_clock</span> <span class="o">=</span> <span class="n">buf_block_get_modify_clock</span><span class="p">(</span><span class="n">block</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><ul>
<li>
<p>如果 pcur 指向一个 supremum record, 保存 supremum record 前的一个 record, m_rel_pos 为 BTR_PCUR_AFTER.</p>
</li>
<li>
<p>如果 pcur 指向一个 infimum record, 保存 infimum record 后的一个 record, m_rel_pos 为 BTR_PCUR_BEFORE.</p>
</li>
<li>
<p>如果 pcur 指向一个 user record, 保存 user record, m_rel_pos 为 BTR_PCUR_ON.</p>
</li>
</ul>
<p><code>restore_position()</code> 先尝试乐观加锁，即直接判断 <code>m_modify_clock</code> 是否变化，假如 b+ tree 发生了 SMO, 需要进行悲观加锁的方式，即通过 <code>btr_cur_search_to_nth_level()</code> 重新 search 加锁:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">btr_pcur_t</span><span class="o">::</span><span class="n">restore_position</span><span class="p">(</span><span class="n">ulint</span> <span class="n">latch_mode</span><span class="p">,</span> <span class="n">mtr_t</span> <span class="o">*</span><span class="n">mtr</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                  <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> <span class="n">ulint</span> <span class="n">line</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">dtuple_t</span> <span class="o">*</span><span class="n">tuple</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">page_cur_mode_t</span> <span class="n">mode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">mtr</span><span class="o">-&gt;</span><span class="n">is_active</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">m_old_stored</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">is_positioned</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">auto</span> <span class="n">index</span> <span class="o">=</span> <span class="n">btr_cur_get_index</span><span class="p">(</span><span class="n">get_btr_cur</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut_a</span><span class="p">(</span><span class="n">m_old_rec</span> <span class="o">!=</span> <span class="k">nullptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_a</span><span class="p">(</span><span class="n">m_old_n_fields</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* Optimistic latching involves S/X latch not required for
</span></span></span><span class="line"><span class="cl"><span class="cm">  intrinsic table instead we would prefer to search fresh. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">((</span><span class="n">latch_mode</span> <span class="o">==</span> <span class="n">BTR_SEARCH_LEAF</span> <span class="o">||</span> <span class="n">latch_mode</span> <span class="o">==</span> <span class="n">BTR_MODIFY_LEAF</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">       <span class="n">latch_mode</span> <span class="o">==</span> <span class="n">BTR_SEARCH_PREV</span> <span class="o">||</span> <span class="n">latch_mode</span> <span class="o">==</span> <span class="n">BTR_MODIFY_PREV</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">      <span class="o">!</span><span class="n">m_btr_cur</span><span class="p">.</span><span class="n">index</span><span class="o">-&gt;</span><span class="n">table</span><span class="o">-&gt;</span><span class="n">is_intrinsic</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* Try optimistic restoration. */</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 乐观恢复. */</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">m_block_when_stored</span><span class="p">.</span><span class="n">run_with_hint</span><span class="p">([</span><span class="o">&amp;</span><span class="p">](</span><span class="n">buf_block_t</span> <span class="o">*</span><span class="n">hint</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="k">return</span> <span class="n">hint</span> <span class="o">!=</span> <span class="k">nullptr</span> <span class="o">&amp;&amp;</span> <span class="n">btr_cur_optimistic_latch_leaves</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                                        <span class="n">hint</span><span class="p">,</span> <span class="n">m_modify_clock</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">latch_mode</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                        <span class="o">&amp;</span><span class="n">m_btr_cur</span><span class="p">,</span> <span class="n">file</span><span class="p">,</span> <span class="n">line</span><span class="p">,</span> <span class="n">mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">m_pos_state</span> <span class="o">=</span> <span class="n">BTR_PCUR_IS_POSITIONED</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">m_latch_mode</span> <span class="o">=</span> <span class="n">latch_mode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">buf_block_dbg_add_level</span><span class="p">(</span><span class="n">get_block</span><span class="p">(),</span> <span class="n">dict_index_is_ibuf</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                                               <span class="o">?</span> <span class="nl">SYNC_IBUF_TREE_NODE</span>
</span></span><span class="line"><span class="cl">                                               <span class="p">:</span> <span class="n">SYNC_TREE_NODE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="n">m_rel_pos</span> <span class="o">==</span> <span class="n">BTR_PCUR_ON</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef UNIV_DEBUG
</span></span></span><span class="line"><span class="cl">        <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif </span><span class="cm">/* UNIV_DEBUG */</span><span class="cp">
</span></span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* This is the same record as stored,
</span></span></span><span class="line"><span class="cl"><span class="cm">      may need to be adjusted for BTR_PCUR_BEFORE/AFTER,
</span></span></span><span class="line"><span class="cl"><span class="cm">      depending on search mode and direction. */</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="n">is_on_user_rec</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">m_pos_state</span> <span class="o">=</span> <span class="n">BTR_PCUR_IS_POSITIONED_OPTIMISTIC</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="p">(</span><span class="nb">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* If optimistic restoration did not succeed, open the cursor anew */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">auto</span> <span class="n">heap</span> <span class="o">=</span> <span class="n">mem_heap_create</span><span class="p">(</span><span class="mi">256</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">tuple</span> <span class="o">=</span> <span class="n">dict_index_build_data_tuple</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">m_old_rec</span><span class="p">,</span> <span class="n">m_old_n_fields</span><span class="p">,</span> <span class="n">heap</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* Save the old search mode of the cursor */</span>
</span></span><span class="line"><span class="cl">  <span class="k">auto</span> <span class="n">old_mode</span> <span class="o">=</span> <span class="n">m_search_mode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/*  根据 store_position() 时记录的 m_rel_pos 采用不同的 search mode. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">switch</span> <span class="p">(</span><span class="n">m_rel_pos</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nl">BTR_PCUR_ON</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="n">mode</span> <span class="o">=</span> <span class="n">PAGE_CUR_LE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nl">BTR_PCUR_AFTER</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="n">mode</span> <span class="o">=</span> <span class="n">PAGE_CUR_G</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">case</span> <span class="nl">BTR_PCUR_BEFORE</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">      <span class="n">mode</span> <span class="o">=</span> <span class="n">PAGE_CUR_L</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">      <span class="n">ut_error</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 乐观恢复 pcur 失败，就要通过 btr_cur_search_to_nth_level 来重新定位 pcur. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">open_no_init</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">tuple</span><span class="p">,</span> <span class="n">mode</span><span class="p">,</span> <span class="n">latch_mode</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">mtr</span><span class="p">,</span> <span class="n">file</span><span class="p">,</span> <span class="n">line</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* Restore the old search mode */</span>
</span></span><span class="line"><span class="cl">  <span class="n">m_search_mode</span> <span class="o">=</span> <span class="n">old_mode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">m_rel_pos</span> <span class="o">==</span> <span class="n">BTR_PCUR_ON</span> <span class="o">||</span> <span class="n">m_rel_pos</span> <span class="o">==</span> <span class="n">BTR_PCUR_BEFORE</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">        <span class="n">m_rel_pos</span> <span class="o">==</span> <span class="n">BTR_PCUR_AFTER</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">m_rel_pos</span> <span class="o">==</span> <span class="n">BTR_PCUR_ON</span> <span class="o">&amp;&amp;</span> <span class="n">is_on_user_rec</span><span class="p">()</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">      <span class="o">!</span><span class="n">cmp_dtuple_rec</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">tuple</span><span class="p">,</span> <span class="n">get_rec</span><span class="p">(),</span> <span class="n">index</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="n">rec_get_offsets</span><span class="p">(</span><span class="n">get_rec</span><span class="p">(),</span> <span class="n">index</span><span class="p">,</span> <span class="k">nullptr</span><span class="p">,</span> <span class="n">ULINT_UNDEFINED</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">heap</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* We have to store the NEW value for the modify clock,
</span></span></span><span class="line"><span class="cl"><span class="cm">    since the cursor can now be on a different page!
</span></span></span><span class="line"><span class="cl"><span class="cm">    But we can retain the value of old_rec */</span>
</span></span><span class="line"><span class="cl">    <span class="k">auto</span> <span class="n">block</span> <span class="o">=</span> <span class="n">get_block</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">m_block_when_stored</span><span class="p">.</span><span class="n">store</span><span class="p">(</span><span class="n">block</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">m_modify_clock</span> <span class="o">=</span> <span class="n">buf_block_get_modify_clock</span><span class="p">(</span><span class="n">block</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">m_old_stored</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">mem_heap_free</span><span class="p">(</span><span class="n">heap</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">mem_heap_free</span><span class="p">(</span><span class="n">heap</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* We have to store new position information, modify_clock etc.,
</span></span></span><span class="line"><span class="cl"><span class="cm">  to the cursor because it can now be on a different page, the record
</span></span></span><span class="line"><span class="cl"><span class="cm">  under it may have been removed, etc. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">store_position</span><span class="p">(</span><span class="n">mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="nb">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><ol>
<li>
<p>对于 BTR_SEARCH_LEAF,BTR_MODIFY_LEAF,BTR_SEARCH_PREV,BTR_MODIFY_PREV 四种 latch mode, 可以尝试乐观 restore_position().</p>
</li>
<li>
<p>针对悲观 restore_position 的情况:</p>
<ul>
<li>如果 <code>store_position()</code> 时记录的 m_rel_pos 为 BTR_PCUR_ON, 则 <code>store_position()</code> 时为一个 user record, 采用 PAGE_CUR_LE 的 search mode, 恢复至最后一个小于等于 user record(old) 的 record.</li>
<li>如果 <code>store_position()</code> 时记录的 m_rel_pos 为 BTR_PCUR_AFTER, 则 <code>store_position()</code> 时为一个 supremum record, 采用 PAGE_CUR_G 的 search mode, <code>store_position()</code> 时 pcur 保存的是 supremum record 前的 record(old), 所以恢复至大于 record(old) 的 record. (所以可能定位在大于 record(old) 的下一个 user record, 也可能是 <code>store_position()</code> 当时定位的 supremum record).</li>
<li>如果 <code>store_position()</code> 时记录的 m_rel_pos 为 BTR_PCUR_BEFORE, 则 <code>store_position()</code> 时为一个 infimum record, 采用 PAGE_CUR_L 的 search mode, <code>store_position()</code> 时 pcur 保存的是 infimum record 后的 record(old), 所以恢复至小于 record(old) 的 record. (所以可能定位在小于 record(old) 的上一个 user record, 也可能是 <code>store_position()</code> 当时定位的 infimum record).</li>
</ul>
</li>
</ol>
<p><code>store_position()</code> 会记录 <code>buf_block_t</code>, 在乐观恢复中直接通过尝试对 <code>buf_block_t</code> 加锁，当前的 Buffer Pool 支持动态 resize, 这部分的内存可能会被释放, 所以 InnoDB 会首先判断这个 <code>buf_block_t</code> 指针是否存在于 Buffer Pool 的 chunk 中:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">Block_hint</span><span class="o">::</span><span class="n">buffer_fix_block_if_still_valid</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">m_block</span> <span class="o">!=</span> <span class="k">nullptr</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="n">buf_pool_t</span> <span class="o">*</span><span class="k">const</span> <span class="n">pool</span> <span class="o">=</span> <span class="n">buf_pool_get</span><span class="p">(</span><span class="n">m_page_id</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">rw_lock_t</span> <span class="o">*</span><span class="n">latch</span> <span class="o">=</span> <span class="n">buf_page_hash_lock_get</span><span class="p">(</span><span class="n">pool</span><span class="p">,</span> <span class="n">m_page_id</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">rw_lock_s_lock</span><span class="p">(</span><span class="n">latch</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* If not own buf_pool_mutex, page_hash can be changed. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">latch</span> <span class="o">=</span> <span class="n">buf_page_hash_lock_s_confirm</span><span class="p">(</span><span class="n">latch</span><span class="p">,</span> <span class="n">pool</span><span class="p">,</span> <span class="n">m_page_id</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">buf_is_block_in_instance</span><span class="p">(</span><span class="n">pool</span><span class="p">,</span> <span class="n">m_block</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="n">m_page_id</span> <span class="o">==</span> <span class="n">m_block</span><span class="o">-&gt;</span><span class="n">page</span><span class="p">.</span><span class="n">id</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="n">buf_block_get_state</span><span class="p">(</span><span class="n">m_block</span><span class="p">)</span> <span class="o">==</span> <span class="n">BUF_BLOCK_FILE_PAGE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">buf_block_buf_fix_inc</span><span class="p">(</span><span class="n">m_block</span><span class="p">,</span> <span class="n">__FILE__</span><span class="p">,</span> <span class="n">__LINE__</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">clear</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">rw_lock_s_unlock</span><span class="p">(</span><span class="n">latch</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>游标 cursor 的 up_match 和 low_match 分别代表在 search 阶段与目标 record 相等的 fileds 的数目.</p>
<h3 id="游标-cursor-的搜索模式">游标 cursor 的搜索模式</h3>
<ul>
<li>
<p>PAGE_CUR_G: &gt; 查询，查询第一个大于 dtuple 的 rec_t.</p>
</li>
<li>
<p>PAGE_CUR_GE: &gt;=，&gt; 查询，查询第一个大于等于 dtuple 的 rec_t.</p>
<ol>
<li>如果搜索一个存在的 user record, 使用 PAGE_CUR_GE 可能定位在这个 user record 的 previous page 的 supremum record.</li>
</ol>
</li>
<li>
<p>PAGE_CUR_L: &lt; 查询，查询最后一个小于 dtuple 的 rec_t.</p>
</li>
<li>
<p>PAGE_CUR_LE: &lt;= 查询，查询最后一个小于等于 dtuple 的 rec_t.</p>
<ol>
<li>如果搜索一个不存在的 user record, 使用 PAGE_CUR_LE 返回最后一个小于 dtuple 的 record.</li>
</ol>
</li>
</ul>
]]></content>
  </entry><entry>
    <title>InnoDB 事务锁调度分析</title>
    <link href="https://leviathan.vip/2021/08/16/innodb-trx-lock-scheduling/" />
    <id>https://leviathan.vip/2021/08/16/innodb-trx-lock-scheduling/</id>
    <updated>2021-08-16T10:44:04Z</updated>
    <summary type="html"><![CDATA[<h2 id="准备">准备</h2>
<p>MySQL 8.0.25</p>
<h2 id="背景">背景</h2>
<p>数据库内核月报<a href="http://mysql.taobao.org/monthly/2016/01/01/"> InnoDB 事务锁系统简介</a>对 InnoDB 的事务锁系统: record lock 和 table lock 做了具体的介绍, 而<a href="https://leviathan.vip/2020/12/22/mysql-understand-trx-lock/"> InnoDB 事务 sharded 锁系统优化</a> 介绍了 MySQL 官方团队针对 InnoDB 事务锁系统进行的拆分优化. InnoDB 采用 2PL + MVCC 的并发控制方式, 以此来提高读写性能. 两阶段加锁(2PL)将事务锁的申请与释放拆为两步: 1.在事务过程中统一加锁, 2. 在事务提交或回滚后统一放锁， 除非事务提交或者回滚, 否则不会在事务的中间状态释放锁. 所以在事务申请 lock 的过程中, 需要判断是否与其他事务持有的 lock 冲突, 对于冲突情况需要进入 waiting 队列, 而在持有 lock 的事务提交或者回滚之后, 都会释放持有的事务锁, 从而选择等待队列里的事务进行 grant lock. 选择合适的等待事务可以有效的提高事务的并发性能, 所以事务锁调度算法的关键是如何选择合适的等待事务. 当存在多个事务请求同一个对象的锁时, 哪个事务, 或者哪些事务应当最先获得锁?</p>
<h2 id="first-come-first-served-fcfs">First Come First Served (FCFS)</h2>
<p>在 8.0.3 之前的 MySQL 版本, 采用的是 FCFS 的调度算法, 原理也相对简单. 在事务执行阶段向对应的 record 进行加锁行为, 通过 lock_sys 记录的 record lock 来判断是否存在冲突, 因为两阶段加锁的限制, 对于冲突的 lock 我们将其放入等待队列, 当持有的事务提交或者回滚时, 逐一释放其持有的 lock时, 会检查相应的等待队列，并按 FCFS 顺序检查是否可以将锁授予等待事务.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="准备">准备</h2>
<p>MySQL 8.0.25</p>
<h2 id="背景">背景</h2>
<p>数据库内核月报<a href="http://mysql.taobao.org/monthly/2016/01/01/"> InnoDB 事务锁系统简介</a>对 InnoDB 的事务锁系统: record lock 和 table lock 做了具体的介绍, 而<a href="https://leviathan.vip/2020/12/22/mysql-understand-trx-lock/"> InnoDB 事务 sharded 锁系统优化</a> 介绍了 MySQL 官方团队针对 InnoDB 事务锁系统进行的拆分优化. InnoDB 采用 2PL + MVCC 的并发控制方式, 以此来提高读写性能. 两阶段加锁(2PL)将事务锁的申请与释放拆为两步: 1.在事务过程中统一加锁, 2. 在事务提交或回滚后统一放锁， 除非事务提交或者回滚, 否则不会在事务的中间状态释放锁. 所以在事务申请 lock 的过程中, 需要判断是否与其他事务持有的 lock 冲突, 对于冲突情况需要进入 waiting 队列, 而在持有 lock 的事务提交或者回滚之后, 都会释放持有的事务锁, 从而选择等待队列里的事务进行 grant lock. 选择合适的等待事务可以有效的提高事务的并发性能, 所以事务锁调度算法的关键是如何选择合适的等待事务. 当存在多个事务请求同一个对象的锁时, 哪个事务, 或者哪些事务应当最先获得锁?</p>
<h2 id="first-come-first-served-fcfs">First Come First Served (FCFS)</h2>
<p>在 8.0.3 之前的 MySQL 版本, 采用的是 FCFS 的调度算法, 原理也相对简单. 在事务执行阶段向对应的 record 进行加锁行为, 通过 lock_sys 记录的 record lock 来判断是否存在冲突, 因为两阶段加锁的限制, 对于冲突的 lock 我们将其放入等待队列, 当持有的事务提交或者回滚时, 逐一释放其持有的 lock时, 会检查相应的等待队列，并按 FCFS 顺序检查是否可以将锁授予等待事务.</p>
<h2 id="contention-aware-transaction-scheduling-cats">Contention-Aware Transaction Scheduling (CATS)</h2>
<p>CATS 的全称是 Contention-Aware Transaction Scheduling (竞争感知), 在 MySQL 8.0.20 开始已经作为默认的事务调度算法, 不仅仅只在低冲突场景才会使用. 事务锁调度最常见的策略就是 FCFS 策略, 先到先得, 这种朴素的调度策略实现也较为简单, 但存在的问题是例如某个等待事务持有较多的 lock 并且阻塞了其他的事务的进行，但因为先到先得的策略无法立即获得 lock, 从而致使整个数据库的 TPS 减慢. 这是 FCFS 策略无法解决的问题, 所以我们最好对事务本身进行感知, 比如所有事务的等待关系等. CATS 相关的论文有两篇: <a href="https://arxiv.org/pdf/1602.01871.pdf">Identifying the Major Sources of Variance in Transaction Latencies: Towards More Predictable Databases</a>, <a href="https://www.vldb.org/pvldb/vol11/p648-tian.pdf">Contention-Aware Lock Scheduling for Transactional Databases</a>.</p>
<p>论文[Contention-Aware Lock Scheduling for Transactional Databases]介绍了几种调度策略, 并逐步引申出 CATS 算法.</p>
<ul>
<li>Number of locks held</li>
</ul>
<p>在 FCFS 策略后, 我们可以讨论以锁持有的数量来判断优先级, 例如下图:</p>
<p><img src="/images/scheduling_1.png" alt="scheduling_1"></p>
<p>事务 t1 和事务 t2 都在等待对象 O1 的锁, t1 事务本身持有的锁数量是 4 个,  而 t2 事务持有的锁数量是 2 个, 假如以&quot;锁持有的数量&quot;为标准, 那事务 t2 应该获得 lock, 但在事务的等待关系中, 有 3 个事务等待在 t2 上，而仅有 1 个事务等待在 t1.</p>
<ul>
<li>Number of locks that block other transactions</li>
</ul>
<p>假定以等待事务阻塞事务数量来判断优先级, 例如下图:</p>
<p><img src="/images/scheduling_2.png" alt="scheduling_2"></p>
<p>事务 t1 和事务 t2 都在等待对象 O1 的锁,  t1 事务持有的锁只有一个阻塞了事务 t3, 而 t2 事务持有的锁却阻塞了两个事务, 假如以等待事务阻塞的事务数量来判断优先级, O1 的锁会被授予 t2, 但需要注意的是 t3 事务却阻塞了 3 个其他事务. 所以假如我们想提高事务的并发度, 最好的选择是将 O1 锁授予 t1.</p>
<ul>
<li>Depth of the dependency subgraph</li>
</ul>
<p>假定以等待事务关系图的深度来判断优先级, 例如下图:</p>
<p><img src="/images/scheduling_3.png" alt="scheduling_3"></p>
<p>虽然 t1 事务有更深的依赖关系, 而 t2 事务同时阻塞两个事务, 但假如将锁授予 t1, 势必影响整个 DB 的事务并发度.</p>
<ul>
<li>Largest-Dependency-Set-First (LDSF)</li>
</ul>
<p><img src="/images/scheduling_4.png" alt="scheduling_4"></p>
<p>真正的事务等待关系应该是有向图, 所以计算权重不应该考虑子树, 而是子图. 所以最后提出了一种 Largest-Dependency-Set-First (LDSF) 的算法, 根据计算等待事务所有的等待关系权重来决定锁的调度优先级.</p>
<p>InnoDB 根据 LDSF 在原有的事务锁基础上实现了基于竞争感知的事务锁调度算法, 主要两个 patch 分别是 <a href="https://dev.mysql.com/worklog/task/?id=10793">WL#10793: InnoDB: Use CATS for scheduling lock release under high load</a>, <a href="https://dev.mysql.com/worklog/task/?id=13468">WL#13468: Improved CATS implementation</a>.</p>
<h3 id="源码分析">源码分析</h3>
<p>MySQL 8.0.18 版本针对死锁检测进行了优化, 将原先的死锁检测机制交由 background thread: lock_wait_timeout_thread() 来处理, 思路是将当前的事务锁 lock 信息打一份快照, 由这份快照判断是否存在回环, 假如存在死锁即唤醒等待事务. 因为这个过程可以感知所有的锁等待关系, 所以 InnoDB 也基于这份快照来计算权重.</p>
<p>lock_wait_timeout_thread 线程除了检查等待超时以外, 也会更新全局等待事务的权重和死锁检测, 具体的函数是 <code>lock_wait_update_schedule_and_check_for_deadlocks()</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">lock_wait_update_schedule_and_check_for_deadlocks</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">waiting_trx_info_t</span><span class="o">&gt;</span> <span class="n">infos</span><span class="p">;</span>  <span class="cm">/* 记录事务的依赖关系. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span> <span class="n">outgoing</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">trx_schedule_weight_t</span><span class="o">&gt;</span> <span class="n">new_weights</span><span class="p">;</span> <span class="cm">/* 记录事务的权重. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 获取事务的等待关系, 仅收集等待事务, 即 [from] 事务阻塞在 [to] 事务上. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">auto</span> <span class="n">table_reservations</span> <span class="o">=</span> <span class="n">lock_wait_snapshot_waiting_threads</span><span class="p">(</span><span class="n">infos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* 构建事务的等待关系图.
</span></span></span><span class="line"><span class="cl"><span class="cm">   * outgoing 数组的下标代表是第 n 个事务, value 代表其等待的事务下标. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">lock_wait_build_wait_for_graph</span><span class="p">(</span><span class="n">infos</span><span class="p">,</span> <span class="n">outgoing</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* We don&#39;t update trx-&gt;lock.schedule_weight for trxs on cycles. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">lock_wait_compute_and_publish_weights_except_cycles</span><span class="p">(</span><span class="n">infos</span><span class="p">,</span> <span class="n">table_reservations</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                                      <span class="n">outgoing</span><span class="p">,</span> <span class="n">new_weights</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">innobase_deadlock_detect</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 假如打开了死锁检测, 处理死锁的情况. */</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* This will also update trx-&gt;lock.schedule_weight for trxs on cycles. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">lock_wait_find_and_handle_deadlocks</span><span class="p">(</span><span class="n">infos</span><span class="p">,</span> <span class="n">outgoing</span><span class="p">,</span> <span class="n">new_weights</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>在获取了所有的等待事务关系图后，需要根据其阻塞的事务数量开始计算权重, 过程如下:</p>
<ul>
<li>lock_wait_compute_initial_weights(): 初始化权重, 初始值为 1. InnoDB 新增了一个全局自增变量 lock_wait_table_reservations, 在每个线程因为锁等待进入等待状态时, 会获取当时的 lock_wait_table_reservations 的值, 所以每个事务自身的 table_reservations 与全局的 lock_wait_table_reservations 的差值代表了等待的时间, 差值越大等待时间越长. 所以在事务锁的调度算法中, 为了防止有事务饿死的情况, 将差值超过等待事务数量的事务权重设为等待事务数量:</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl">  <span class="cm">/* WEIGHT_BOOST  设置成等待事务的数量或者 1e9. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">const</span> <span class="n">trx_schedule_weight_t</span> <span class="n">WEIGHT_BOOST</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">      <span class="n">n</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">?</span> <span class="mi">1</span> <span class="o">:</span> <span class="n">std</span><span class="o">::</span><span class="n">min</span><span class="o">&lt;</span><span class="n">trx_schedule_weight_t</span><span class="o">&gt;</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="mf">1e9</span> <span class="o">/</span> <span class="n">n</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">new_weights</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* 默认权重值为 1. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">new_weights</span><span class="p">.</span><span class="n">resize</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* MAX_FAIR_WAIT 是两倍的等待事务数量. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">const</span> <span class="kt">uint64_t</span> <span class="n">MAX_FAIR_WAIT</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="n">size_t</span> <span class="n">from</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">from</span> <span class="o">&lt;</span> <span class="n">n</span><span class="p">;</span> <span class="o">++</span><span class="n">from</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* reservation_no 是事务进入等待状态时的 lock_wait_table_reservations 的值,
</span></span></span><span class="line"><span class="cl"><span class="cm">     * table_reservations 是开始进行快照时 lock_wait_table_reservations 的值,
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 所以假如 infos[from].reservation_no + MAX_FAIR_WAIT 小于 table_reservations
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 的情况出现就代表事务 &#34;from&#34; 等待的时间较长, 为了防止饿死, 所以将其权重置为
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 两倍的等待事务数量(n). */</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">infos</span><span class="p">[</span><span class="n">from</span><span class="p">].</span><span class="n">reservation_no</span> <span class="o">+</span> <span class="n">MAX_FAIR_WAIT</span> <span class="o">&lt;</span> <span class="n">table_reservations</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">new_weights</span><span class="p">[</span><span class="n">from</span><span class="p">]</span> <span class="o">=</span> <span class="n">WEIGHT_BOOST</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span></span></span></code></pre></div><ul>
<li>
<p>lock_wait_compute_incoming_count(): 更新事务等待关系图中的入度情况, 即一个事务阻塞了多少个事务.</p>
</li>
<li>
<p>lock_wait_accumulate_weights(): 计算每个等待事务的权重, 其策略是累加等待事务阻塞的事务权重, 例如事务 t1 阻塞了事务 t2, t3, t5, 则 t1 事务的权重为:</p>
</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">t1_weight</span> <span class="o">=</span> <span class="n">t1_weight</span> <span class="o">+</span> <span class="n">t2_weight</span> <span class="o">+</span> <span class="n">t3_weight</span> <span class="o">+</span> <span class="n">t5_weight</span><span class="p">;</span></span></span></code></pre></div><ul>
<li>lock_wait_publish_new_weights(): 更新等待事务权重.</li>
</ul>
<p>事务在提交或者回滚之后都会释放其持有的 lock: <code>lock_release()</code>. 将其持有的锁授予哪个事务的顺序是, 第一顺位是高优先级的事务, 其次是事务的权重排序, 权重为 1 或者 0 ( lock.schedule_weight 的默认值)的事务依照 FCFS 的顺序.</p>
<h2 id="总结">总结</h2>
<p>本文介绍了 InnoDB 在锁调度策略的最新优化, 该算法在锁冲突严重的场景效果明显, 计算权重的重要参考指标是等待事务的等待时间 (lock_wait_table_reservations) 和其阻塞的事务权重之和. InnoDB 目前的实现没有区分读/写事务, 例如当多个读事务等待同一个锁, 选择读事务较多的子图, 可以有效的提高事务并发度. 关于 CATS 的策略方面后续可以加入更多的指标, 在计算的复杂度和判断的有效性采用折中的方案, 既不影响权重的计算, 也有效的提高数据库的事务并发度.</p>
]]></content>
  </entry><entry>
    <title>InnoDB 的 LRU 策略分析</title>
    <link href="https://leviathan.vip/2021/05/31/understand-innodb-old-blocks-pct/" />
    <id>https://leviathan.vip/2021/05/31/understand-innodb-old-blocks-pct/</id>
    <updated>2021-05-31T16:08:16Z</updated>
    <summary type="html"><![CDATA[<h2 id="准备">准备</h2>
<ul>
<li>MySQL 8.0.25</li>
</ul>
<h2 id="参数解释">参数解释</h2>
<ul>
<li>
<p>innodb_old_blocks_pct: 在 Buffer Pool 的 LRU list 中 old 部分所占的比例.</p>
</li>
<li>
<p>innodb_old_blocks_time: 当一个 Page 距第一次被访问的时间大于等于 innodb_old_blocks_time 时，再次被访问的时候，会被移动到 LRU list 的头部.</p>
</li>
</ul>
<h2 id="lru-list">LRU list</h2>
<p>InnoDB 的 Buffer Pool 使用 LRU 算法管理数据 Page, 为了防止全表扫描或者范围查询造成对 LRU 链表的污染, InnoDB 将 LRU 分为两个部分: young / old :</p>
<ul>
<li>
<p>young 区域代表经常访问的数据 Page.</p>
</li>
<li>
<p>old 区域代表不常访问的数据 Page.</p>
</li>
</ul>
<p><img src="/images/innodb-buffer-pool-list.png" alt="innodb-buffer-pool-list"></p>
<p>上图显示了 Buffer Pool 的布局.</p>
<p>5/8 的 &ldquo;young&rdquo; 区域和 3/8 的 &ldquo;old&rdquo; 区域划分是参数 innodb_old_blocks_pct 的默认值 37 决定的，这个参数可以动态调整.</p>
<h2 id="源码分析">源码分析</h2>
<h3 id="lru-初始化">LRU 初始化</h3>
<p>InnoDB  在启动时针对 Buffer Pool 进行初始化, 完成 Buffer Pool 的初始化后使用 100 * 3 / 8 = 37 来调整 LRU list 的 young 和 old 的区域.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="准备">准备</h2>
<ul>
<li>MySQL 8.0.25</li>
</ul>
<h2 id="参数解释">参数解释</h2>
<ul>
<li>
<p>innodb_old_blocks_pct: 在 Buffer Pool 的 LRU list 中 old 部分所占的比例.</p>
</li>
<li>
<p>innodb_old_blocks_time: 当一个 Page 距第一次被访问的时间大于等于 innodb_old_blocks_time 时，再次被访问的时候，会被移动到 LRU list 的头部.</p>
</li>
</ul>
<h2 id="lru-list">LRU list</h2>
<p>InnoDB 的 Buffer Pool 使用 LRU 算法管理数据 Page, 为了防止全表扫描或者范围查询造成对 LRU 链表的污染, InnoDB 将 LRU 分为两个部分: young / old :</p>
<ul>
<li>
<p>young 区域代表经常访问的数据 Page.</p>
</li>
<li>
<p>old 区域代表不常访问的数据 Page.</p>
</li>
</ul>
<p><img src="/images/innodb-buffer-pool-list.png" alt="innodb-buffer-pool-list"></p>
<p>上图显示了 Buffer Pool 的布局.</p>
<p>5/8 的 &ldquo;young&rdquo; 区域和 3/8 的 &ldquo;old&rdquo; 区域划分是参数 innodb_old_blocks_pct 的默认值 37 决定的，这个参数可以动态调整.</p>
<h2 id="源码分析">源码分析</h2>
<h3 id="lru-初始化">LRU 初始化</h3>
<p>InnoDB  在启动时针对 Buffer Pool 进行初始化, 完成 Buffer Pool 的初始化后使用 100 * 3 / 8 = 37 来调整 LRU list 的 young 和 old 的区域.</p>
<h3 id="插入-lru-old">插入 LRU old</h3>
<p>当我们需要从从 Buffer Pool 中读取一个 Page, 并且这个 Page 需要从文件中进行读取时 <code>buf_page_init_for_read()</code>, 我们会从 Buffer Pool 中申请一个 Free Page, 之后需要插入 LRU 的 old 的头部区域 <code>buf_LRU_add_block()</code>, 即 old-&gt;head:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl">                              <span class="err">新读取的</span> <span class="n">Page</span> <span class="err">插入位置</span>
</span></span><span class="line"><span class="cl">                                       <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                       <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                       <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                       <span class="n">v</span>
</span></span><span class="line"><span class="cl">  <span class="o">-----------------------------------------------------</span>
</span></span><span class="line"><span class="cl"> <span class="o">|</span>                                    <span class="o">|</span>                <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">|</span>               <span class="n">young</span>                <span class="o">|</span>     <span class="n">old</span>        <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">|</span>                                    <span class="o">|</span>                <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">-----------------------------------------------------</span></span></span></code></pre></div><h3 id="插入-lru-young">插入 LRU young</h3>
<p>LRU 区分了 young 和 old 区域，所以需要适时的将 old 区域的 Page 根据需求移动至 young 区域, 操作过程也比较简单，直接从 LRU 的 old 区域摘除然后插入 young 区域即可 <code>buf_page_make_young()</code>:</p>
<p>以下是插入 LRU young 区域的时机:</p>
<ul>
<li>
<p>btr_search_guess_on_hash():</p>
</li>
<li>
<p>buf_page_optimistic_get():</p>
</li>
<li>
<p>buf_page_get_known_nowait():</p>
</li>
<li>
<p>Buf_fetch<T>::single_page(): 对于通过 <code>buf_page_get_gen()</code> 且 mode 不是 Page_fetch::SCAN 和 Page_fetch::PEEK_IF_IN_POOL 这两种的都会将 Page 插入 LRU list 的 young 区域.</p>
</li>
</ul>
<h3 id="lru-evict">LRU evict</h3>
<p>Buffer Pool 的容量是有限的，为了用户的写入读取能获取 Free Page, Buffer Pool 要不停的从 LRU list 置换 &ldquo;old&rdquo; Page: 策略是从 Buffer Pool 的 old list 的尾部扫描合适的 Page 换出.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl">                                            <span class="n">LRU</span> <span class="n">evict</span> <span class="err">起始位置</span>
</span></span><span class="line"><span class="cl">                                                      <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                                      <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                                      <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                                      <span class="n">v</span>
</span></span><span class="line"><span class="cl">  <span class="o">-----------------------------------------------------</span>
</span></span><span class="line"><span class="cl"> <span class="o">|</span>                                    <span class="o">|</span>                <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">|</span>               <span class="n">young</span>                <span class="o">|</span>     <span class="n">old</span>        <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">|</span>                                    <span class="o">|</span>                <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">-----------------------------------------------------</span></span></span></code></pre></div><p>以下是 LRU evict 数据 Page 的时机:</p>
<ul>
<li>
<p>buf_page_io_complete(): 当从 LRU list 刷脏完成后，会将 Page 从 LRU list 中移除.</p>
</li>
<li>
<p>buf_flush_LRU_list_batch(): 扫描 LRU list 时，将满足条件的 Page<code>buf_flush_ready_for_replace()</code> 换出.</p>
</li>
<li>
<p>buf_flush_single_page_from_LRU(): 当用户需要获取空闲 Page 而 LRU List 暂时没有 Free Page 时, 会选择一个 Page 直接换出 <code>buf_flush_ready_for_replace()</code> 或者 <code>buf_flush_ready_for_flush()</code> 刷入磁盘.</p>
</li>
</ul>
<h2 id="总结">总结</h2>
<p>当一个 Page 从 disk 读入 Buffer Pool 后, 先插入 old 区域起始位置, 后续的非 scan mode 的读则会调整插入 young 区域. 在 young 区域的 page 假如再次被读到，会通过 <code>buf_page_peek_if_young()</code> 判断是否接近被 evict, 否则在 young 中是不会调整 page 的顺序的.</p>
<h2 id="推荐文档">推荐文档</h2>
<ul>
<li><a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-performance-midpoint_insertion.html">Making the Buffer Pool Scan Resistant</a></li>
</ul>
]]></content>
  </entry><entry>
    <title>InnoDB 的事务故障恢复流程</title>
    <link href="https://leviathan.vip/2021/04/16/innodb-trx-recover/" />
    <id>https://leviathan.vip/2021/04/16/innodb-trx-recover/</id>
    <updated>2021-04-16T16:39:06Z</updated>
    <summary type="html"><![CDATA[<h2 id="背景">背景</h2>
<p><strong>MySQL 版本: 8.0.25</strong></p>
<p>数据库系统中关于事务有 4 个重要特性 ACID, 其中 A 代表的原子性: 一个事务必须被视为一个不可分割的最小工作单元，整个事务中的所有操作要么全部提交成功，要么全部失败回滚，对于一个事务来说，不可能只执行其中的一部分操作，这就是事务的原子性. 对于 InnoDB 来说, 针对意外崩溃情况，也需要保证事务满足原子性，即在崩溃前提交的事务需要保证重启后可读, 尚未提交的事务需要正确的回滚.</p>
<h3 id="redo-log">Redo Log</h3>
<p>关于 Redo Log 在之前的文章 <a href="https://leviathan.vip/2018/12/15/InnoDB%E7%9A%84Redo-Log%E5%88%86%E6%9E%90/">InnoDB 的 Redo Log 分析
</a>已经详细介绍过, InnoDB 利用 Redo Log 来记录所有的数据和其他的文件操作. InnoDB 在对应操作的 Redo Log 落盘后就会给用户返回操作成功, 此时对应的数据 Page 可能还在 Buffer Pool 中尚未落盘, 这里可以加快的写入的速度, 但也需要在意外崩溃后能使数据库的数据 Page 恢复到一个正确的状态.</p>
<h3 id="undo-log">Undo Log</h3>
<p>InnoDB 使用 MVCC + Undo Log 来实现不同的事务隔离级别, 在数据库正常的运行时，用户可以通过 Undo Log 来在不同的隔离级别下读取相应正确的数据, 其中在意外崩溃后，InnoDB 需要使用 Undo Log 来回滚尚未提交的事务.</p>
<h3 id="启动流程">启动流程</h3>
<pre tabindex="0"><code>mysqld_main() -&gt; init_server_components() -&gt;
                                              dd::init() -&gt; bootstrap::DDSE_dict_init() -&gt; ddse_dict_init() [srv_start()]
                                                                                        -&gt; initialize_dictionary() [校验tablespace, srv_dict_recover_on_restart(), 回滚 DD 事务]
                                          -&gt;
                                              ha_post_recover() [post_ddl()]</code></pre><h3 id="checkpoint">Checkpoint</h3>
<p>在 MySQL 8.0 新建了一个独立的线程 <code>log_checkpointer</code> 来执行 Checkpoint 任务, 当 InnoDB 执行一次 Checkpoint 时, 会将指定 lsn 位置的数据 Page 刷入磁盘, 这就保证了在此 lsn 之前的数据均以持久化. <code>log_checkpointer</code> 在执行 Checkpoint 后会写入 Checkpoint 信息至 <code>ib_logfile0</code>,  InnoDB 设计在 offset 512 bytes 和 1536 bytes 轮流写 Checkpoint 信息，防止某次写入 Checkpoint 失败导致故障恢复无法找到上次的位点.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="背景">背景</h2>
<p><strong>MySQL 版本: 8.0.25</strong></p>
<p>数据库系统中关于事务有 4 个重要特性 ACID, 其中 A 代表的原子性: 一个事务必须被视为一个不可分割的最小工作单元，整个事务中的所有操作要么全部提交成功，要么全部失败回滚，对于一个事务来说，不可能只执行其中的一部分操作，这就是事务的原子性. 对于 InnoDB 来说, 针对意外崩溃情况，也需要保证事务满足原子性，即在崩溃前提交的事务需要保证重启后可读, 尚未提交的事务需要正确的回滚.</p>
<h3 id="redo-log">Redo Log</h3>
<p>关于 Redo Log 在之前的文章 <a href="https://leviathan.vip/2018/12/15/InnoDB%E7%9A%84Redo-Log%E5%88%86%E6%9E%90/">InnoDB 的 Redo Log 分析
</a>已经详细介绍过, InnoDB 利用 Redo Log 来记录所有的数据和其他的文件操作. InnoDB 在对应操作的 Redo Log 落盘后就会给用户返回操作成功, 此时对应的数据 Page 可能还在 Buffer Pool 中尚未落盘, 这里可以加快的写入的速度, 但也需要在意外崩溃后能使数据库的数据 Page 恢复到一个正确的状态.</p>
<h3 id="undo-log">Undo Log</h3>
<p>InnoDB 使用 MVCC + Undo Log 来实现不同的事务隔离级别, 在数据库正常的运行时，用户可以通过 Undo Log 来在不同的隔离级别下读取相应正确的数据, 其中在意外崩溃后，InnoDB 需要使用 Undo Log 来回滚尚未提交的事务.</p>
<h3 id="启动流程">启动流程</h3>
<pre tabindex="0"><code>mysqld_main() -&gt; init_server_components() -&gt;
                                              dd::init() -&gt; bootstrap::DDSE_dict_init() -&gt; ddse_dict_init() [srv_start()]
                                                                                        -&gt; initialize_dictionary() [校验tablespace, srv_dict_recover_on_restart(), 回滚 DD 事务]
                                          -&gt;
                                              ha_post_recover() [post_ddl()]</code></pre><h3 id="checkpoint">Checkpoint</h3>
<p>在 MySQL 8.0 新建了一个独立的线程 <code>log_checkpointer</code> 来执行 Checkpoint 任务, 当 InnoDB 执行一次 Checkpoint 时, 会将指定 lsn 位置的数据 Page 刷入磁盘, 这就保证了在此 lsn 之前的数据均以持久化. <code>log_checkpointer</code> 在执行 Checkpoint 后会写入 Checkpoint 信息至 <code>ib_logfile0</code>,  InnoDB 设计在 offset 512 bytes 和 1536 bytes 轮流写 Checkpoint 信息，防止某次写入 Checkpoint 失败导致故障恢复无法找到上次的位点.</p>
<h2 id="回滚流程">回滚流程</h2>
<p>当 MySQL 启动后，无论之前是否发生 crash 都会尝试进行 recover (<code>recv_recovery_from_checkpoint_start()</code>):</p>
<ul>
<li>
<p>读取 Checkpoint 信息，找到记录的最新的 Checkpoint (<code>recv_find_max_checkpoint()</code>).</p>
</li>
<li>
<p>将 Checkpoint 之后的 Redo Log 重新进行 apply, 保证数据 Page 的正确性 (<code>recv_apply_hashed_log_recs()</code>).</p>
</li>
<li>
<p>针对不完整的 mtr 的 redo log 情况下:</p>
<ol>
<li>会 apply 到完整的 mtr redo log, 然后丢弃不完整的 mtr redo log.</li>
<li>将最后一个完整的 mtr redo log 的最后一个 block 内容拷贝至 log_sys-&gt;buf, 目的是进行 log_start().</li>
</ol>
</li>
<li>
<p>InnoDB 针对 Undo Tablespace 的回滚段进行事务的重建(<code>trx_sys_init_at_db_start() --&gt; trx_rsegs_init()</code>).</p>
</li>
<li>
<p>重建回滚段后恢复当前事务列表(<code>trx_lists_init_at_db_start()</code>). (事务信息记录在回滚段中的 undo log segment, InnoDB 可以借此恢复事务信息).</p>
</li>
<li>
<p>恢复 table id, 用以在数据字段恢复时重新加锁(<code>srv_dict_recover_on_restart()</code>).</p>
</li>
</ul>
<h3 id="事务恢复的回滚">事务恢复的回滚</h3>
<ul>
<li>
<p>针对事务中存在 DDL 的操作, 采用同步回滚的方式 <code>innobase_dict_recover() --&gt; srv_dict_recover_on_restart()</code>.</p>
</li>
<li>
<p>针对不涉及数据字典操作的普通事务, InnoDB 采用异步事务回滚的方式, 通过新启一个线程 <code>trx_recovery_rollback_thread</code> 来回滚恢复出来的事务.</p>
</li>
<li>
<p>Crash Recovery 阶段 Undo Log 回滚路径会调用 <code>row_convert_impl_to_expl_if_needed()</code> 加事务锁.</p>
</li>
</ul>
<h2 id="总结">总结</h2>
<p>事务的故障恢复重要的一个关键点是如何恢复意外 crash 前的事务状态信息, InnoDB 使用的 Undo Log 结构里为每个事务都会分配的 Undo Log Segment 持久化记录了事务的状态信息, 即使 Undo Page 尚未刷盘，也可以通过 Redo Log 也可以恢复了 Undo Page,  Redo Log + Undo Log 保证了 InnoDB 关于事务实现的可靠性.</p>
]]></content>
  </entry><entry>
    <title>理解 InnoDB 的 Change Buffer</title>
    <link href="https://leviathan.vip/2021/04/06/innodb-change-buffer/" />
    <id>https://leviathan.vip/2021/04/06/innodb-change-buffer/</id>
    <updated>2021-04-06T17:19:31Z</updated>
    <summary type="html"><![CDATA[<h2 id="背景">背景</h2>
<p><strong>MySQL 版本: 8.0.23</strong></p>
<p>Change Buffer 是 InnoDB 系统表空间(space id = 0) 的一个 B+ tree 索引, 它的作用是为满足指定条件下而数据 Page 不在 Buffer Pool 的二级索引操作进行缓存, 包括一开始的 INSERT 和后来加入的 UPDATE, DELETE. 聚簇索引的顺序插入，可能体现在二级索引中字段并不是顺序的, 所以存在大量的随机读取和写入, 将二级索引的数据操作顺序写入 Change Buffer 的 B+ tree, 以此达到与聚簇索引一致的顺序写入. 当我们需要读取时，会将对应的数据 Page 从磁盘读取至 Buffer Pool 并与 Change Buffer 中对应的 records 进行 merge 操作.</p>
<h2 id="change-buffer-使用">Change buffer 使用</h2>
<h3 id="参数">参数</h3>
<ul>
<li>
<p>innodb_change_buffering: 设置缓存的操作类型, 包括 <code>none</code>, <code>all</code>, <code>inserts</code>, <code>deletes</code>, <code>changes</code>, <code>purges</code>.</p>
</li>
<li>
<p>innodb_change_buffer_max_size: 设置 Change Buffer 所占 Buffer Pool 的大小, 默认 25%, 最大50%. (假如超过了阈值，会阻止进行 Change Buffer 写入，转而使用通常的写入方式，然后进行主动 merge, 即将数据 Page 读至 Buffer Pool, 并将 Change Buffer 的 records 与数据 Page 进行合并)</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="背景">背景</h2>
<p><strong>MySQL 版本: 8.0.23</strong></p>
<p>Change Buffer 是 InnoDB 系统表空间(space id = 0) 的一个 B+ tree 索引, 它的作用是为满足指定条件下而数据 Page 不在 Buffer Pool 的二级索引操作进行缓存, 包括一开始的 INSERT 和后来加入的 UPDATE, DELETE. 聚簇索引的顺序插入，可能体现在二级索引中字段并不是顺序的, 所以存在大量的随机读取和写入, 将二级索引的数据操作顺序写入 Change Buffer 的 B+ tree, 以此达到与聚簇索引一致的顺序写入. 当我们需要读取时，会将对应的数据 Page 从磁盘读取至 Buffer Pool 并与 Change Buffer 中对应的 records 进行 merge 操作.</p>
<h2 id="change-buffer-使用">Change buffer 使用</h2>
<h3 id="参数">参数</h3>
<ul>
<li>
<p>innodb_change_buffering: 设置缓存的操作类型, 包括 <code>none</code>, <code>all</code>, <code>inserts</code>, <code>deletes</code>, <code>changes</code>, <code>purges</code>.</p>
</li>
<li>
<p>innodb_change_buffer_max_size: 设置 Change Buffer 所占 Buffer Pool 的大小, 默认 25%, 最大50%. (假如超过了阈值，会阻止进行 Change Buffer 写入，转而使用通常的写入方式，然后进行主动 merge, 即将数据 Page 读至 Buffer Pool, 并将 Change Buffer 的 records 与数据 Page 进行合并)</p>
</li>
</ul>
<h3 id="触发条件">触发条件</h3>
<p>除了设置上述的参数打开 Change Buffer 以外，真正使用 Change Buffer 还需要经过一些条件判断:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">ibool</span> <span class="nf">ibuf_should_try</span><span class="p">(</span><span class="n">dict_index_t</span> <span class="o">*</span><span class="n">index</span><span class="p">,</span>     <span class="cm">/*!&lt; in: index where to insert */</span>
</span></span><span class="line"><span class="cl">                      <span class="n">ulint</span> <span class="n">ignore_sec_unique</span><span class="p">)</span> <span class="cm">/*!&lt; in: if != 0, we should
</span></span></span><span class="line"><span class="cl"><span class="cm">                                               ignore UNIQUE constraint on
</span></span></span><span class="line"><span class="cl"><span class="cm">                                               a secondary index when we
</span></span></span><span class="line"><span class="cl"><span class="cm">                                               decide */</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="n">innodb_change_buffering</span> <span class="o">!=</span> <span class="n">IBUF_USE_NONE</span> <span class="o">&amp;&amp;</span> <span class="n">ibuf</span><span class="o">-&gt;</span><span class="n">max_size</span> <span class="o">!=</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">          <span class="n">index</span><span class="o">-&gt;</span><span class="n">space</span> <span class="o">!=</span> <span class="n">dict_sys_t</span><span class="o">::</span><span class="n">s_space_id</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">index</span><span class="o">-&gt;</span><span class="n">is_clustered</span><span class="p">()</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">          <span class="o">!</span><span class="n">dict_index_is_spatial</span><span class="p">(</span><span class="n">index</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">dict_index_has_desc</span><span class="p">(</span><span class="n">index</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">          <span class="n">index</span><span class="o">-&gt;</span><span class="n">table</span><span class="o">-&gt;</span><span class="n">quiesce</span> <span class="o">==</span> <span class="n">QUIESCE_NONE</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">          <span class="p">(</span><span class="n">ignore_sec_unique</span> <span class="o">||</span> <span class="o">!</span><span class="n">dict_index_is_unique</span><span class="p">(</span><span class="n">index</span><span class="p">))</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">          <span class="n">srv_force_recovery</span> <span class="o">&lt;</span> <span class="n">SRV_FORCE_NO_IBUF_MERGE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><ol>
<li>设置 <code>innodb_change_buffering</code> 不为 <code>IBUF_USE_NONE</code>.</li>
<li>设置 <code>innodb_change_buffer_max_size</code> 不为0.</li>
<li>待缓存的索引不为数据字典表.</li>
<li>待缓存的索引不是聚簇索引.</li>
<li>待缓存的索引不是 Spatial Index.</li>
<li>待缓存的索引包含递减列.</li>
<li>待缓存的表上没有 flush 操作.</li>
<li>待缓存的索引包含唯一列(唯一列需要全局判断, 可以缓存删除操作, 但无法缓存插入操作).</li>
<li>设置 srv_force_recovery 不允许 ibuf merge 操作.</li>
</ol>
<p>所以我们在打开 Change Buffer 的同时也需要判断以上的条件是否符合. 对于<strong>唯一索引</strong>和写入立即需要读取的数据并不适合打开 Change Buffer.</p>
<h2 id="change-buffer-原理">Change Buffer 原理</h2>
<p>Change Buffer 的调用逻辑是当我们需要进行支持的 DML 操作时，尝试从 Buffer Pool 读取 Page 时，假如 Page 不在 Buffer Pool 中并符合上述的触发条件, 会通过 <code>ibuf_insert()</code> 来针对不同类型的操作进行 Change Buffer 的缓存.</p>
<p>Change Buffer 最初的功能只有缓存 INSERT 操作，所以也作 ibuf, 代码中均使用 ibuf 代替 Change Buffer.</p>
<p>Change Buffer 中有几个重要的概念:</p>
<h3 id="change-buffer-record">Change Buffer Record</h3>
<p>Change Buffer 的 Page 缓存对应二级索引的 DML 操作, 使用 <code>&lt;space_id, page_no, counter&gt;</code> 作为 key, 当需要查找的时, 使用 <code>&lt;space_id, page_no&gt;</code> 就可以定位到具体的 record, 而 counter 作为一个递增的值，记录着 DML 的操作顺序.</p>
<h3 id="change-buffer-bitmap-page">Change Buffer Bitmap Page</h3>
<p>在每个 Tablespace 中 Extent 的第二个 Page 会作为 Change Buffer 的元信息 Page, 即为 ibuf bitmap page, bitmap page 会使用 4 bits 来记录 Tablespace 中关于数据 Page 的 Change Buffer 信息, 以下方法可以计算 bitmap page no:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* #define FSP_IBUF_BITMAP_OFFSET 1 */</span>
</span></span><span class="line"><span class="cl"><span class="n">ulint</span> <span class="n">bitmap_page_no</span> <span class="o">=</span> <span class="n">FSP_IBUF_BITMAP_OFFSET</span> <span class="o">+</span> <span class="p">((</span><span class="n">page_no</span> <span class="o">/</span> <span class="n">page_size</span><span class="p">)</span> <span class="o">*</span> <span class="n">page_size</span><span class="p">)</span></span></span></code></pre></div><p>其中包括以下几个信息:</p>
<ul>
<li>
<p>IBUF_BITMAP_FREE: 长度 2 bit, 记录该 Page 空闲空间, 使用 2个 bit 来描述空闲空间大小，以 16KB 的 page size 为例，能表示的空闲空间范围为0 (0 bytes)、1 (512 bytes)、2 (1024 bytes)、3 (2048 bytes). 注意此处2048 bytes 意为用户的累计插入 records 长度不能超过 2048 bytes, 并不单单指一次插入, 假如累积缓存的 record 长度超过了 2048bytes, 就会触发 ibuf merge 操作.</p>
</li>
<li>
<p>IBUF_BITMAP_BUFFERED: 长度 1 bit, 代表该 Page 上存在被缓存了的 DML 操作.</p>
</li>
<li>
<p>IBUF_BITMAP_IBUF: 长度 1 bit, 代表该 Page 属于 ibuf 类型, 供 AIO 线程判断.</p>
</li>
</ul>
<p>使用函数 <code>ibuf_index_page_calc_free_from_bits()</code> 可以计算 Page 的空闲空间:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">ibuf_code</span> <span class="o">==</span> <span class="mi">3</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">ibuf_code</span> <span class="o">=</span> <span class="mi">4</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">free_space</span> <span class="o">=</span> <span class="n">ibuf_code</span> <span class="o">*</span> <span class="p">(</span><span class="n">page_size</span> <span class="o">/</span> <span class="n">IBUF_PAGE_SIZE_PER_FREE_SPACE</span><span class="p">);</span></span></span></code></pre></div><p>在正常的 DML 操作成功后会更新对应数据 Page 的 <code>IBUF_BITMAP_BUFFERED</code>, <code>IBUF_BITMAP_BUFFERED</code> 并不是准确的记录数据 Page 的空闲空间, 最大只能记录 2kb, 所以用户在写入 record 导致 Page 的剩余空闲空间小于 2kb 之后才会更新. 而 Change Buffer 的缓存操作也通过 <code>IBUF_BITMAP_BUFFERED</code> 最大缓存 2kb 的 records.</p>
<h3 id="change-buffer-写入">Change Buffer 写入</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">static</span> <span class="nf">MY_ATTRIBUTE</span><span class="p">((</span><span class="n">warn_unused_result</span><span class="p">))</span> <span class="n">dberr_t</span>
</span></span><span class="line"><span class="cl">    <span class="n">ibuf_insert_low</span><span class="p">(</span><span class="n">ulint</span> <span class="n">mode</span><span class="p">,</span> <span class="n">ibuf_op_t</span> <span class="n">op</span><span class="p">,</span> <span class="n">ibool</span> <span class="n">no_counter</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="k">const</span> <span class="n">dtuple_t</span> <span class="o">*</span><span class="n">entry</span><span class="p">,</span> <span class="n">ulint</span> <span class="n">entry_size</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">dict_index_t</span> <span class="o">*</span><span class="n">index</span><span class="p">,</span> <span class="k">const</span> <span class="n">page_id_t</span> <span class="o">&amp;</span><span class="n">page_id</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="k">const</span> <span class="n">page_size_t</span> <span class="o">&amp;</span><span class="n">page_size</span><span class="p">,</span> <span class="n">que_thr_t</span> <span class="o">*</span><span class="n">thr</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 假如当前的 ibuf 大小超过了设置的阈值, 调用 ibuf_contract() 进行
</span></span></span><span class="line"><span class="cl"><span class="cm">   * 部分 ibuf merge 操作以缓解 ibuf 的空间问题. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">ibuf</span><span class="o">-&gt;</span><span class="n">size</span> <span class="o">&gt;=</span> <span class="n">ibuf</span><span class="o">-&gt;</span><span class="n">max_size</span> <span class="o">+</span> <span class="n">IBUF_CONTRACT_DO_NOT_INSERT</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* Insert buffer is now too big, contract it but do not try
</span></span></span><span class="line"><span class="cl"><span class="cm">    to insert */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef UNIV_IBUF_DEBUG
</span></span></span><span class="line"><span class="cl">    <span class="n">fputs</span><span class="p">(</span><span class="s">&#34;Ibuf too big</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">stderr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl">    <span class="n">ibuf_contract</span><span class="p">(</span><span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">DB_STRONG_FAIL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">heap</span> <span class="o">=</span> <span class="n">mem_heap_create</span><span class="p">(</span><span class="mi">1024</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 构建插入 ibuf 的 entry:
</span></span></span><span class="line"><span class="cl"><span class="cm">   * entry 是以 &lt;space_id, page_no, counter&gt; 为 Key,
</span></span></span><span class="line"><span class="cl"><span class="cm">   * counter 的作用是来保证 DML 操作的顺序, 每次 DML 自增 1.
</span></span></span><span class="line"><span class="cl"><span class="cm">   * space_id, pae_no 是已知, counter 先默认设为 0xFFFF. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ibuf_entry</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">      <span class="n">ibuf_entry_build</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">entry</span><span class="p">,</span> <span class="n">page_id</span><span class="p">.</span><span class="n">space</span><span class="p">(),</span> <span class="n">page_id</span><span class="p">.</span><span class="n">page_no</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                       <span class="n">no_counter</span> <span class="o">?</span> <span class="nl">ULINT_UNDEFINED</span> <span class="p">:</span> <span class="mh">0xFFFF</span><span class="p">,</span> <span class="n">heap</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 针对 DML 操作判断 ibuf 的数据 Page 是否充裕，否则要进行分配直到满足写入. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">BTR_LATCH_MODE_WITHOUT_INTENTION</span><span class="p">(</span><span class="n">mode</span><span class="p">)</span> <span class="o">==</span> <span class="n">BTR_MODIFY_TREE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">mutex_enter</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ibuf_pessimistic_insert_mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="n">mutex_enter</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ibuf_mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="n">UNIV_LIKELY</span><span class="p">(</span><span class="n">ibuf_data_enough_free_for_insert</span><span class="p">()))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">mutex_exit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ibuf_mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="n">mutex_exit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ibuf_pessimistic_insert_mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ibuf_add_free_page</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">mem_heap_free</span><span class="p">(</span><span class="n">heap</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="n">DB_STRONG_FAIL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ibuf_mtr_start</span><span class="p">(</span><span class="o">&amp;</span><span class="n">mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 使用先前创建的 entry 对 ibuf-&gt;index 进行 search, 注意使用的是 PAGE_CUR_LE, 即 pcur 是落在一个小于等于的 record 上. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">btr_pcur_open</span><span class="p">(</span><span class="n">ibuf</span><span class="o">-&gt;</span><span class="n">index</span><span class="p">,</span> <span class="n">ibuf_entry</span><span class="p">,</span> <span class="n">PAGE_CUR_LE</span><span class="p">,</span> <span class="n">mode</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pcur</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">page_validate</span><span class="p">(</span><span class="n">btr_pcur_get_page</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pcur</span><span class="p">),</span> <span class="n">ibuf</span><span class="o">-&gt;</span><span class="n">index</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">min_n_recs</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">buffered</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">      <span class="n">ibuf_get_volume_buffered</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pcur</span><span class="p">,</span> <span class="n">page_id</span><span class="p">.</span><span class="n">space</span><span class="p">(),</span> <span class="n">page_id</span><span class="p">.</span><span class="n">page_no</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">                               <span class="n">op</span> <span class="o">==</span> <span class="n">IBUF_OP_DELETE</span> <span class="o">?</span> <span class="o">&amp;</span><span class="nl">min_n_recs</span> <span class="p">:</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 假如是 IBUF_OP_DELETE 操作并且待缓存的 Page 上的 record 数量小于 2 则不能进行 ibuf 缓存操作, 因为会导致 SMO. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">op</span> <span class="o">==</span> <span class="n">IBUF_OP_DELETE</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">      <span class="p">(</span><span class="n">min_n_recs</span> <span class="o">&lt;</span> <span class="mi">2</span> <span class="o">||</span> <span class="n">buf_pool_watch_occurred</span><span class="p">(</span><span class="n">page_id</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">  <span class="nl">fail_exit</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">BTR_LATCH_MODE_WITHOUT_INTENTION</span><span class="p">(</span><span class="n">mode</span><span class="p">)</span> <span class="o">==</span> <span class="n">BTR_MODIFY_TREE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">mutex_exit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ibuf_mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="n">mutex_exit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ibuf_pessimistic_insert_mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">err</span> <span class="o">=</span> <span class="n">DB_STRONG_FAIL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">goto</span> <span class="n">func_exit</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ibuf_mtr_start</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bitmap_mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 获取 bitmap page. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">bitmap_page</span> <span class="o">=</span> <span class="n">ibuf_bitmap_get_map_page</span><span class="p">(</span><span class="n">page_id</span><span class="p">,</span> <span class="n">page_size</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bitmap_mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 1. 检查对应的数据 Page 是否被 load 进 Buffer Pool.
</span></span></span><span class="line"><span class="cl"><span class="cm">   * 2. 检查对应的数据 Page 上是否存在隐式的插入锁. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">buf_page_peek</span><span class="p">(</span><span class="n">page_id</span><span class="p">)</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">      <span class="n">lock_rec_expl_exist_on_page</span><span class="p">(</span><span class="n">page_id</span><span class="p">.</span><span class="n">space</span><span class="p">(),</span> <span class="n">page_id</span><span class="p">.</span><span class="n">page_no</span><span class="p">()))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ibuf_mtr_commit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bitmap_mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">goto</span> <span class="n">fail_exit</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 对于没有指定 counter 的 record, 我们需要通过 ibuf_get_entry_counter() 获取前一个 reocrd 的 counter, 并自增 1, 以此作为当前的 record 的 counter 值.
</span></span></span><span class="line"><span class="cl"><span class="cm">   * 对于当前 Page 的第一个 record, 则从 0 开始.*/</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">no_counter</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ulint</span> <span class="n">counter</span> <span class="o">=</span> <span class="n">ibuf_get_entry_counter</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">page_id</span><span class="p">.</span><span class="n">space</span><span class="p">(),</span> <span class="n">page_id</span><span class="p">.</span><span class="n">page_no</span><span class="p">(),</span> <span class="n">btr_pcur_get_rec</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pcur</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">mtr</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">btr_pcur_get_btr_cur</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pcur</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">low_match</span> <span class="o">&lt;</span> <span class="n">IBUF_REC_FIELD_METADATA</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">dfield_t</span> <span class="o">*</span><span class="n">field</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">counter</span> <span class="o">==</span> <span class="n">ULINT_UNDEFINED</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">ibuf_mtr_commit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bitmap_mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="k">goto</span> <span class="n">fail_exit</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">field</span> <span class="o">=</span> <span class="n">dtuple_get_nth_field</span><span class="p">(</span><span class="n">ibuf_entry</span><span class="p">,</span> <span class="n">IBUF_REC_FIELD_METADATA</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 写入 counter 值. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">mach_write_to_2</span><span class="p">((</span><span class="n">byte</span> <span class="o">*</span><span class="p">)</span><span class="n">dfield_get_data</span><span class="p">(</span><span class="n">field</span><span class="p">)</span> <span class="o">+</span> <span class="n">IBUF_REC_OFFSET_COUNTER</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                    <span class="n">counter</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* Set the bitmap bit denoting that the insert buffer contains
</span></span></span><span class="line"><span class="cl"><span class="cm">  buffered entries for this index page, if the bit is not set yet */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 设置 bitmap page 的 IBUF_BITMAP_BUFFERED 位, 意为当前 Page 存在 Change Buffer 操作. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">old_bit_value</span> <span class="o">=</span> <span class="n">ibuf_bitmap_page_get_bits</span><span class="p">(</span><span class="n">bitmap_page</span><span class="p">,</span> <span class="n">page_id</span><span class="p">,</span> <span class="n">page_size</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                            <span class="n">IBUF_BITMAP_BUFFERED</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bitmap_mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">old_bit_value</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ibuf_bitmap_page_set_bits</span><span class="p">(</span><span class="n">bitmap_page</span><span class="p">,</span> <span class="n">page_id</span><span class="p">,</span> <span class="n">page_size</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                              <span class="n">IBUF_BITMAP_BUFFERED</span><span class="p">,</span> <span class="n">TRUE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bitmap_mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ibuf_mtr_commit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bitmap_mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">cursor</span> <span class="o">=</span> <span class="n">btr_pcur_get_btr_cur</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pcur</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">mode</span> <span class="o">==</span> <span class="n">BTR_MODIFY_PREV</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">err</span> <span class="o">=</span> <span class="n">btr_cur_optimistic_insert</span><span class="p">(</span><span class="n">BTR_NO_LOCKING_FLAG</span><span class="p">,</span> <span class="n">cursor</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">offsets</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">&amp;</span><span class="n">offsets_heap</span><span class="p">,</span> <span class="n">ibuf_entry</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ins_rec</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">&amp;</span><span class="n">dummy_big_rec</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">thr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ut_ad</span><span class="p">(</span><span class="n">BTR_LATCH_MODE_WITHOUT_INTENTION</span><span class="p">(</span><span class="n">mode</span><span class="p">)</span> <span class="o">==</span> <span class="n">BTR_MODIFY_TREE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 进行乐观插入. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">err</span> <span class="o">=</span> <span class="n">btr_cur_optimistic_insert</span><span class="p">(</span><span class="n">BTR_NO_LOCKING_FLAG</span> <span class="o">|</span> <span class="n">BTR_NO_UNDO_LOG_FLAG</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="n">cursor</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">offsets</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">offsets_heap</span><span class="p">,</span> <span class="n">ibuf_entry</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                    <span class="o">&amp;</span><span class="n">ins_rec</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dummy_big_rec</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">thr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">err</span> <span class="o">==</span> <span class="n">DB_FAIL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 乐观插入失败则进行悲观插入. */</span>
</span></span><span class="line"><span class="cl">      <span class="n">err</span> <span class="o">=</span> <span class="n">btr_cur_pessimistic_insert</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">          <span class="n">BTR_NO_LOCKING_FLAG</span> <span class="o">|</span> <span class="n">BTR_NO_UNDO_LOG_FLAG</span><span class="p">,</span> <span class="n">cursor</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">offsets</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="o">&amp;</span><span class="n">offsets_heap</span><span class="p">,</span> <span class="n">ibuf_entry</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ins_rec</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dummy_big_rec</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">thr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">mutex_exit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ibuf_pessimistic_insert_mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">ibuf_size_update</span><span class="p">(</span><span class="n">root</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutex_exit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ibuf_mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">ibuf</span><span class="o">-&gt;</span><span class="n">empty</span> <span class="o">=</span> <span class="n">page_is_empty</span><span class="p">(</span><span class="n">root</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">block</span> <span class="o">=</span> <span class="n">btr_cur_get_block</span><span class="p">(</span><span class="n">cursor</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">ut_ad</span><span class="p">(</span><span class="n">block</span><span class="o">-&gt;</span><span class="n">page</span><span class="p">.</span><span class="n">id</span><span class="p">.</span><span class="n">space</span><span class="p">()</span> <span class="o">==</span> <span class="n">IBUF_SPACE_ID</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">err</span> <span class="o">==</span> <span class="n">DB_SUCCESS</span> <span class="o">&amp;&amp;</span> <span class="n">op</span> <span class="o">!=</span> <span class="n">IBUF_OP_DELETE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* Update the page max trx id field */</span>
</span></span><span class="line"><span class="cl">    <span class="n">page_update_max_trx_id</span><span class="p">(</span><span class="n">block</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">thr_get_trx</span><span class="p">(</span><span class="n">thr</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">id</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">func_exit</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ibuf_mtr_commit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">mtr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">btr_pcur_close</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pcur</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">mem_heap_free</span><span class="p">(</span><span class="n">heap</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">err</span> <span class="o">==</span> <span class="n">DB_SUCCESS</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">      <span class="n">BTR_LATCH_MODE_WITHOUT_INTENTION</span><span class="p">(</span><span class="n">mode</span><span class="p">)</span> <span class="o">==</span> <span class="n">BTR_MODIFY_TREE</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 插入后判断是否需要对数据 Page 进行 merge 操作. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">ibuf_contract_after_insert</span><span class="p">(</span><span class="n">entry_size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">do_merge</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef UNIV_IBUF_DEBUG
</span></span></span><span class="line"><span class="cl">    <span class="n">ut_a</span><span class="p">(</span><span class="n">n_stored</span> <span class="o">&lt;=</span> <span class="n">IBUF_MAX_N_PAGES_MERGED</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl">    <span class="cm">/* 对于读取的 Page 进行 merge 操作. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf_read_ibuf_merge_pages</span><span class="p">(</span><span class="nb">false</span><span class="p">,</span> <span class="n">space_ids</span><span class="p">,</span> <span class="n">page_nos</span><span class="p">,</span> <span class="n">n_stored</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="n">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="change-buffer-合并ibuf-merge">Change Buffer 合并(ibuf merge)</h2>
<p>有以下几个场景会触发 Change Buffer 的 merge 操作, 即将 ibuf Page 的 records 和原数据 Page 进行合并操作 (<code>ibuf_merge_or_delete_for_page()</code>):</p>
<ul>
<li>
<p><code>ibuf_insert_low()</code> 中存在部分判断逻辑会导致无法使用 Change Buffer 写入，从而触发 ibuf merge.</p>
</li>
<li>
<p>当二级索引数据 Page 从磁盘读入至 Buffer Pool 之后，会触发 merge 操作(buf_page_get_gen()).</p>
</li>
<li>
<p><code>ibuf_merge_in_background()</code> 会在后台触发 ibuf Page 进行 merge.</p>
</li>
<li>
<p>在 Recover 阶段会对 ibuf Page 的 Records 和数据 Page 进行 merge.</p>
</li>
<li>
<p>当执行 slow shutdown 时，会强制做一次全部的ibuf merge.</p>
</li>
</ul>
<p>ibuf 的 merge 操作原理比较简单，就是根据操作类型将 records 从 ibuf Page 合并至数据 Page (<code>ibuf_insert_to_index_page()/ibuf_set_del_mark()/ibuf_delete()</code>)</p>
<h2 id="faq">FAQ</h2>
<ol>
<li>我们讲到对于普通索引来说，Change Buffer 可以避免 Update/Delete/Insert 等修改操作的时候访问磁盘. 后续查询的时候再从磁盘中读出并 merge，对于 Delete 操作，删除一行不存在的数据，这时候 Change Buffer 如何处理?</li>
</ol>
<blockquote>
<p>InnoDB 的删除逻辑是先删聚簇索引, 再删除二级索引(标记删除), 所以当主键索引发现 <code>DB_RECORD_NOT_FOUND</code> 就会返回, 所以不会触发缓存不存在的索引数据.</p>
</blockquote>
<h2 id="总结">总结</h2>
<p>InnoDB 实现了 Change buffer 来优化用户在二级索引上的随机写入问题, 用户可以根据自己的需求结合 Change buffer 的一些条件来判断是否启用 Change buffer, 但需要注意的是 Change buffer 的阈值只有 2kb，假如在一个二级索引的数据 Page 写入的 record 长度超过 2kb, 就会触发 ibuf merge, 从而使后续的 ibuf 缓存条件失效， 但这也符合 IO-bound 的场景需求. 本文也介绍了 Change buffer 如何使用 Bitmap Page 跟踪数据 Page 的空闲空间.</p>
]]></content>
  </entry><entry>
    <title>InnoDB 死锁 Bug 排查</title>
    <link href="https://leviathan.vip/2021/02/25/mysql-deadlock-bugfix-23755664/" />
    <id>https://leviathan.vip/2021/02/25/mysql-deadlock-bugfix-23755664/</id>
    <updated>2021-02-25T11:32:25Z</updated>
    <summary type="html"><![CDATA[<h2 id="背景">背景</h2>
<p>某天收到一封读者的邮件，询问我一个关于 InnoDB 死锁的问题, 他在 MySQL 5.7 可以复现这个问题, MySQL 8.0.22 却无法复现, 他询问其死锁的原因. 经过一系列的排查，我后来发现是 InnoDB 内部实现的一个 Bug，目前这个 Bug 已经在 8.0.18 版本进行了修复, 所以也可以通过 8.0.17 vs 8.0.18 来验证这个问题.</p>
<p>整个 SQL 流程如下:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-SQL" data-lang="SQL"><span class="line"><span class="cl"><span class="cm">/* 1. 表结构 */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">t</span><span class="w">  </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">id</span><span class="w"> </span><span class="nb">BIGINT</span><span class="w"> </span><span class="n">UNSIGNED</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="w"> </span><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;id, 无实际意义&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">account_id</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="w"> </span><span class="p">(</span><span class="mi">64</span><span class="p">)</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;用户id，不同app下的account_id可能重复&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">type</span><span class="w"> </span><span class="n">TINYINT</span><span class="w"> </span><span class="n">UNSIGNED</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;余额类型 1:可用余额&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">balance</span><span class="w"> </span><span class="nb">BIGINT</span><span class="w"> </span><span class="n">UNSIGNED</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;余额&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">state</span><span class="w"> </span><span class="nb">INT</span><span class="w"> </span><span class="n">UNSIGNED</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;账户状态 1:NORMAL; 2:FROZE&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">UNIQUE</span><span class="w"> </span><span class="k">KEY</span><span class="w"> </span><span class="n">uk_account</span><span class="w"> </span><span class="p">(</span><span class="n">account_id</span><span class="p">,</span><span class="w"> </span><span class="k">type</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="n">ENGINE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">INNODB</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="n">CHARSET</span><span class="w"> </span><span class="n">utf8mb4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;测试&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 2. 其中 UNIQUE INDEX 为 uk_account(account_id, type) */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 3. 插入数据 */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">values</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="s1">&#39;1&#39;</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">values</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="s1">&#39;2&#39;</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">values</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="s1">&#39;3&#39;</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">values</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="s1">&#39;4&#39;</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">values</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span><span class="s1">&#39;5&#39;</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 4. 查询所有数据. */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">t</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 5. 执行以下 SQL, 注意事务隔离级别为 (RR) */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">t1</span><span class="o">-</span><span class="mi">1</span><span class="err">：</span><span class="p">(</span><span class="n">session1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">begin</span><span class="p">;</span><span class="w"> </span><span class="cm">/* 显式开启事务, 排除 autocommit 的影响. */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">account_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;1&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="k">type</span><span class="w"> </span><span class="o">=</span><span class="mi">1</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="k">update</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">t2</span><span class="p">:</span><span class="w"> </span><span class="p">(</span><span class="n">session2</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">begin</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">account_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;1&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="k">type</span><span class="w"> </span><span class="o">=</span><span class="mi">1</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="k">update</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">t1</span><span class="o">-</span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="p">(</span><span class="n">session1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">update</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="k">state</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">account_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;1&#39;</span><span class="p">;</span></span></span></code></pre></div><p>我们按照顺序执行分别在 MySQL 8.0.17 和 MySQL 8.0.18 执行，可以看到在 8.0.17 版本事务 t2 因为死锁检测而被视为 <code>victim_trx</code> 进行了回滚，而 8.0.18 却不会回滚事务 t2.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="背景">背景</h2>
<p>某天收到一封读者的邮件，询问我一个关于 InnoDB 死锁的问题, 他在 MySQL 5.7 可以复现这个问题, MySQL 8.0.22 却无法复现, 他询问其死锁的原因. 经过一系列的排查，我后来发现是 InnoDB 内部实现的一个 Bug，目前这个 Bug 已经在 8.0.18 版本进行了修复, 所以也可以通过 8.0.17 vs 8.0.18 来验证这个问题.</p>
<p>整个 SQL 流程如下:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-SQL" data-lang="SQL"><span class="line"><span class="cl"><span class="cm">/* 1. 表结构 */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">CREATE</span><span class="w"> </span><span class="k">TABLE</span><span class="w"> </span><span class="n">t</span><span class="w">  </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">id</span><span class="w"> </span><span class="nb">BIGINT</span><span class="w"> </span><span class="n">UNSIGNED</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">KEY</span><span class="w"> </span><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;id, 无实际意义&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">account_id</span><span class="w"> </span><span class="nb">VARCHAR</span><span class="w"> </span><span class="p">(</span><span class="mi">64</span><span class="p">)</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;用户id，不同app下的account_id可能重复&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">type</span><span class="w"> </span><span class="n">TINYINT</span><span class="w"> </span><span class="n">UNSIGNED</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;余额类型 1:可用余额&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">balance</span><span class="w"> </span><span class="nb">BIGINT</span><span class="w"> </span><span class="n">UNSIGNED</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;余额&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">state</span><span class="w"> </span><span class="nb">INT</span><span class="w"> </span><span class="n">UNSIGNED</span><span class="w"> </span><span class="k">NOT</span><span class="w"> </span><span class="k">NULL</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;账户状态 1:NORMAL; 2:FROZE&#39;</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">UNIQUE</span><span class="w"> </span><span class="k">KEY</span><span class="w"> </span><span class="n">uk_account</span><span class="w"> </span><span class="p">(</span><span class="n">account_id</span><span class="p">,</span><span class="w"> </span><span class="k">type</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="n">ENGINE</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">INNODB</span><span class="w"> </span><span class="k">DEFAULT</span><span class="w"> </span><span class="n">CHARSET</span><span class="w"> </span><span class="n">utf8mb4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">COMMENT</span><span class="w"> </span><span class="s1">&#39;测试&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 2. 其中 UNIQUE INDEX 为 uk_account(account_id, type) */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 3. 插入数据 */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">values</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="s1">&#39;1&#39;</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">values</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="s1">&#39;2&#39;</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">values</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span><span class="s1">&#39;3&#39;</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">values</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="s1">&#39;4&#39;</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">insert</span><span class="w"> </span><span class="k">into</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">values</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span><span class="s1">&#39;5&#39;</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">100</span><span class="p">,</span><span class="mi">1</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 4. 查询所有数据. */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">t</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 5. 执行以下 SQL, 注意事务隔离级别为 (RR) */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">t1</span><span class="o">-</span><span class="mi">1</span><span class="err">：</span><span class="p">(</span><span class="n">session1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">begin</span><span class="p">;</span><span class="w"> </span><span class="cm">/* 显式开启事务, 排除 autocommit 的影响. */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">account_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;1&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="k">type</span><span class="w"> </span><span class="o">=</span><span class="mi">1</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="k">update</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">t2</span><span class="p">:</span><span class="w"> </span><span class="p">(</span><span class="n">session2</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">begin</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">account_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;1&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="k">type</span><span class="w"> </span><span class="o">=</span><span class="mi">1</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="k">update</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">t1</span><span class="o">-</span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="p">(</span><span class="n">session1</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">update</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="k">state</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">account_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;1&#39;</span><span class="p">;</span></span></span></code></pre></div><p>我们按照顺序执行分别在 MySQL 8.0.17 和 MySQL 8.0.18 执行，可以看到在 8.0.17 版本事务 t2 因为死锁检测而被视为 <code>victim_trx</code> 进行了回滚，而 8.0.18 却不会回滚事务 t2.</p>
<p>基于 MySQL 8.0.17</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">MySQL</span><span class="w"> </span><span class="p">[</span><span class="n">sbtest</span><span class="p">]</span><span class="o">&gt;</span><span class="w"> </span><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">account_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;1&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="k">type</span><span class="w"> </span><span class="o">=</span><span class="mi">1</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="k">update</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">ERROR</span><span class="w"> </span><span class="mi">1213</span><span class="w"> </span><span class="p">(</span><span class="mi">40001</span><span class="p">):</span><span class="w"> </span><span class="n">Deadlock</span><span class="w"> </span><span class="k">found</span><span class="w"> </span><span class="k">when</span><span class="w"> </span><span class="n">trying</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="k">get</span><span class="w"> </span><span class="k">lock</span><span class="p">;</span><span class="w"> </span><span class="n">try</span><span class="w"> </span><span class="n">restarting</span><span class="w"> </span><span class="k">transaction</span></span></span></code></pre></div><h2 id="分析流程">分析流程</h2>
<p>我们基于问题版本 8.0.17 来分析 Bug 的真正原因.</p>
<h3 id="sql-分析">SQL 分析</h3>
<p>通过表结构我们可以看到整个表有两个索引, PRIMARY INDEX 和 UNIQUE INDEX uk_account. 因为是死锁问题, 所以我们要逐条分析 SQL 语句加的 record lock 分别是什么:</p>
<ul>
<li>
<p>t1-1
t1-1 是一条 SELECT FROM UPDATE 的语句, 而 <code>account_id</code> 和 <code>type</code> 是一组唯一索引字段, 所以只需要加一个主键索引的 X record lock 和唯一索引 uk_account 的 X record lock.</p>
</li>
<li>
<p>t2
t2 语句与 t1-1 相同, 加锁一致，也是一个主键索引的 X record lock 和 唯一索引 uk_account 的 X record lock.</p>
</li>
<li>
<p>t1-2
t1-2 注意 t1-2 的查询条件只有where account_id = &lsquo;1&rsquo;, 这与 t1-1 的查询条件是不同的, 所以在 RR 隔离级别下，为了避免出现可能的幻读, 这需要加一个 Next-key lock, 另外需要对 record (2,&lsquo;2&rsquo;,1,100,1) 加一个 GAP lock, 防止在此之前的插入造成幻读.</p>
</li>
</ul>
<h3 id="锁信息验证">锁信息验证</h3>
<p>为了验证我们对于 SQL 的分析, 我们可以通过 <code>set global innodb_status_output_locks = on;</code> 打开锁状态输出, 然后 <code>show engine innodb status\G</code>, 来查看锁信息, 这里我们为了验证分析正确，跳过执行 t2 语句, 因为 t2 的加锁类型一定是与 t1-1 一致的:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-SQL" data-lang="SQL"><span class="line"><span class="cl"><span class="o">&gt;</span><span class="w"> </span><span class="k">begin</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">&gt;</span><span class="w"> </span><span class="k">select</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">account_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;1&#39;</span><span class="w"> </span><span class="k">and</span><span class="w"> </span><span class="k">type</span><span class="w"> </span><span class="o">=</span><span class="mi">1</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="k">update</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">&gt;</span><span class="w"> </span><span class="k">update</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="k">state</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">account_id</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;1&#39;</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">&gt;</span><span class="w"> </span><span class="k">set</span><span class="w"> </span><span class="k">global</span><span class="w"> </span><span class="n">innodb_status_output_locks</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">on</span><span class="p">;</span><span class="w"> </span><span class="cm">/* 暂不提交，以查看事务锁的持有信息. */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="o">&gt;</span><span class="w"> </span><span class="k">show</span><span class="w"> </span><span class="n">engine</span><span class="w"> </span><span class="n">innodb</span><span class="w"> </span><span class="n">status</span><span class="err">\</span><span class="k">G</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 事务 t1 持有 5 个 lock, 4 个 row lock, 1 个 table lock (暂时忽略). */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="c1">---TRANSACTION 2068, ACTIVE 16 sec
</span></span></span><span class="line"><span class="cl"><span class="mi">5</span><span class="w"> </span><span class="k">lock</span><span class="w"> </span><span class="n">struct</span><span class="p">(</span><span class="n">s</span><span class="p">),</span><span class="w"> </span><span class="n">heap</span><span class="w"> </span><span class="k">size</span><span class="w"> </span><span class="mi">1200</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="k">row</span><span class="w"> </span><span class="k">lock</span><span class="p">(</span><span class="n">s</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">MySQL</span><span class="w"> </span><span class="n">thread</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="mi">9</span><span class="p">,</span><span class="w"> </span><span class="n">OS</span><span class="w"> </span><span class="n">thread</span><span class="w"> </span><span class="n">handle</span><span class="w"> </span><span class="mi">140737025267456</span><span class="p">,</span><span class="w"> </span><span class="n">query</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="mi">74</span><span class="w"> </span><span class="mi">127</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="w"> </span><span class="n">myadmin</span><span class="w"> </span><span class="n">starting</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">show</span><span class="w"> </span><span class="n">engine</span><span class="w"> </span><span class="n">innodb</span><span class="w"> </span><span class="n">status</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 下列为 table lock (笔者注). */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">TABLE</span><span class="w"> </span><span class="k">LOCK</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="o">`</span><span class="n">sbtest</span><span class="o">`</span><span class="p">.</span><span class="o">`</span><span class="n">t</span><span class="o">`</span><span class="w"> </span><span class="n">trx</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="mi">2068</span><span class="w"> </span><span class="k">lock</span><span class="w"> </span><span class="k">mode</span><span class="w"> </span><span class="n">IX</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 下列为 Record: 1,&#39;1&#39;,1,100,1 的 X record lock 基于索引 uk_account (笔者注). */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">RECORD</span><span class="w"> </span><span class="n">LOCKS</span><span class="w"> </span><span class="k">space</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">page</span><span class="w"> </span><span class="k">no</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="n">bits</span><span class="w"> </span><span class="mi">72</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="n">uk_account</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="o">`</span><span class="n">sbtest</span><span class="o">`</span><span class="p">.</span><span class="o">`</span><span class="n">t</span><span class="o">`</span><span class="w"> </span><span class="n">trx</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="mi">2068</span><span class="w"> </span><span class="n">lock_mode</span><span class="w"> </span><span class="n">X</span><span class="w"> </span><span class="n">locks</span><span class="w"> </span><span class="n">rec</span><span class="w"> </span><span class="n">but</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="n">gap</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">Record</span><span class="w"> </span><span class="k">lock</span><span class="p">,</span><span class="w"> </span><span class="n">heap</span><span class="w"> </span><span class="k">no</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">PHYSICAL</span><span class="w"> </span><span class="n">RECORD</span><span class="p">:</span><span class="w"> </span><span class="n">n_fields</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="n">compact</span><span class="w"> </span><span class="n">format</span><span class="p">;</span><span class="w"> </span><span class="n">info</span><span class="w"> </span><span class="n">bits</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">31</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w"> </span><span class="mi">1</span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">1</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">01</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">  </span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">8</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">0000000000000001</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">         </span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 下列为 Record: 1,&#39;1&#39;,1,100,1 的 X record lock 基于主键索引 (笔者注). */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">RECORD</span><span class="w"> </span><span class="n">LOCKS</span><span class="w"> </span><span class="k">space</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">page</span><span class="w"> </span><span class="k">no</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="n">bits</span><span class="w"> </span><span class="mi">72</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="k">PRIMARY</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="o">`</span><span class="n">sbtest</span><span class="o">`</span><span class="p">.</span><span class="o">`</span><span class="n">t</span><span class="o">`</span><span class="w"> </span><span class="n">trx</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="mi">2068</span><span class="w"> </span><span class="n">lock_mode</span><span class="w"> </span><span class="n">X</span><span class="w"> </span><span class="n">locks</span><span class="w"> </span><span class="n">rec</span><span class="w"> </span><span class="n">but</span><span class="w"> </span><span class="k">not</span><span class="w"> </span><span class="n">gap</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">Record</span><span class="w"> </span><span class="k">lock</span><span class="p">,</span><span class="w"> </span><span class="n">heap</span><span class="w"> </span><span class="k">no</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">PHYSICAL</span><span class="w"> </span><span class="n">RECORD</span><span class="p">:</span><span class="w"> </span><span class="n">n_fields</span><span class="w"> </span><span class="mi">7</span><span class="p">;</span><span class="w"> </span><span class="n">compact</span><span class="w"> </span><span class="n">format</span><span class="p">;</span><span class="w"> </span><span class="n">info</span><span class="w"> </span><span class="n">bits</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">8</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">0000000000000001</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">         </span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">1</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">6</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">000000000809</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">       </span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">7</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">01000001160151</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">       </span><span class="n">Q</span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">3</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">31</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w"> </span><span class="mi">1</span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">4</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">01</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">  </span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">5</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">8</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">0000000000000064</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">        </span><span class="n">d</span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">6</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">4</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">00000002</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">     </span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 下列为 Record: 1,&#39;1&#39;,1,100,1 的 Next-key record lock 基于索引 uk_account. (笔者注). */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">RECORD</span><span class="w"> </span><span class="n">LOCKS</span><span class="w"> </span><span class="k">space</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">page</span><span class="w"> </span><span class="k">no</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="n">bits</span><span class="w"> </span><span class="mi">72</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="n">uk_account</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="o">`</span><span class="n">sbtest</span><span class="o">`</span><span class="p">.</span><span class="o">`</span><span class="n">t</span><span class="o">`</span><span class="w"> </span><span class="n">trx</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="mi">2068</span><span class="w"> </span><span class="n">lock_mode</span><span class="w"> </span><span class="n">X</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">Record</span><span class="w"> </span><span class="k">lock</span><span class="p">,</span><span class="w"> </span><span class="n">heap</span><span class="w"> </span><span class="k">no</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">PHYSICAL</span><span class="w"> </span><span class="n">RECORD</span><span class="p">:</span><span class="w"> </span><span class="n">n_fields</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="n">compact</span><span class="w"> </span><span class="n">format</span><span class="p">;</span><span class="w"> </span><span class="n">info</span><span class="w"> </span><span class="n">bits</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">31</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w"> </span><span class="mi">1</span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">1</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">01</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">  </span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">8</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">0000000000000001</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">         </span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="cm">/* 下列为 Record: 2,&#39;2&#39;,1,100,1 的 GAP lock 基于索引 uk_account. (笔者注). */</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">RECORD</span><span class="w"> </span><span class="n">LOCKS</span><span class="w"> </span><span class="k">space</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">page</span><span class="w"> </span><span class="k">no</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="n">bits</span><span class="w"> </span><span class="mi">72</span><span class="w"> </span><span class="k">index</span><span class="w"> </span><span class="n">uk_account</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="k">table</span><span class="w"> </span><span class="o">`</span><span class="n">sbtest</span><span class="o">`</span><span class="p">.</span><span class="o">`</span><span class="n">t</span><span class="o">`</span><span class="w"> </span><span class="n">trx</span><span class="w"> </span><span class="n">id</span><span class="w"> </span><span class="mi">2068</span><span class="w"> </span><span class="n">lock_mode</span><span class="w"> </span><span class="n">X</span><span class="w"> </span><span class="n">locks</span><span class="w"> </span><span class="n">gap</span><span class="w"> </span><span class="k">before</span><span class="w"> </span><span class="n">rec</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="n">Record</span><span class="w"> </span><span class="k">lock</span><span class="p">,</span><span class="w"> </span><span class="n">heap</span><span class="w"> </span><span class="k">no</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">PHYSICAL</span><span class="w"> </span><span class="n">RECORD</span><span class="p">:</span><span class="w"> </span><span class="n">n_fields</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"> </span><span class="n">compact</span><span class="w"> </span><span class="n">format</span><span class="p">;</span><span class="w"> </span><span class="n">info</span><span class="w"> </span><span class="n">bits</span><span class="w"> </span><span class="mi">0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">0</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">32</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w"> </span><span class="mi">2</span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">1</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">01</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">  </span><span class="p">;;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="mi">2</span><span class="p">:</span><span class="w"> </span><span class="n">len</span><span class="w"> </span><span class="mi">8</span><span class="p">;</span><span class="w"> </span><span class="n">hex</span><span class="w"> </span><span class="mi">0000000000000002</span><span class="p">;</span><span class="w"> </span><span class="k">asc</span><span class="w">         </span><span class="p">;;</span></span></span></code></pre></div><p>通过 <code>show engine innodb status\G</code> 我们可以看到当前的事务的锁持有信息, 事务 t1 分别执行 t1-1 和 t1-2 语句后持有的锁分别有:</p>
<ol>
<li>一个主键索引的 X record lock.</li>
<li>一个 UNIQUE INDEX 的 X record lock.</li>
<li>一个 Next-key record lock, InnoDB 为了明确 Next-key lock 和普通的 record lock 的区别，分别用不同的 mode 来区分:</li>
</ol>
<ul>
<li>
<p>普通的 X record lock:</p>
<ul>
<li>lock_mode X locks rec but not gap. [mode: 1027 LOCK_REC_NOT_GAP | LOCK_REC | LOCK_X]</li>
</ul>
</li>
<li>
<p>Next-key lock:</p>
<ul>
<li>lock_mode X [mode: 35 LOCK_X | LOCK_REC | LOCK_ORDINARY]</li>
</ul>
</li>
</ul>
<ol start="4">
<li>一个 GAP record lock</li>
</ol>
<p>另外也可以通过 <code>performance_schema.data_locks</code> 查询锁信息:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">SELECT</span><span class="w"> </span><span class="n">INDEX_NAME</span><span class="p">,</span><span class="w"> </span><span class="n">LOCK_TYPE</span><span class="p">,</span><span class="w"> </span><span class="n">LOCK_MODE</span><span class="p">,</span><span class="w"> </span><span class="n">LOCK_STATUS</span><span class="p">,</span><span class="w"> </span><span class="n">LOCK_DATA</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">performance_schema</span><span class="p">.</span><span class="n">data_locks</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">OBJECT_NAME</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s1">&#39;t&#39;</span><span class="p">;</span></span></span></code></pre></div><h3 id="死锁原因排查">死锁原因排查</h3>
<p>既然 t2 被死锁检测回滚, 我们就需要检查当时是什么锁关系导致了死锁.</p>
<p>官方在 8.0.18 版本对死锁检测进行了优化, 将原先的死锁检测机制<a href="https://leviathan.vip/2020/02/02/mysql-deadlock-check/"> MySQL 死锁检测源码分析</a> 交给了 background thread 来处理, 具体的 Patch 链接: <a href="https://github.com/mysql/mysql-server/commit/3859219875b62154b921e8c6078c751198071b9c">MySQL-8.0.18 死锁检测优化</a>. 具体的思路是将当前事务系统的 lock 信息打一份快照, 由这份快照判断是否存在回环, 假如存在死锁即唤醒等待事务.</p>
<p>而在 8.0.17 版本依然采用旧的死锁检测方法, 具体细节可以参考这篇文章: <a href="https://leviathan.vip/2020/02/02/mysql-deadlock-check/">MySQL 死锁检测源码分析</a>: 每次申请 lock 失败进入 wait 状态后触发一下死锁检测, 所以我们通过 gdb 调试的方法来梳理当时的锁依赖关系, 当我们执行完成 t1-1, 继而执行 t2 后, 事务 t2 进入了 wait 状态，当执行 t1-2 后 t2 回滚，说明触发 t2 回滚的死锁检测是由 t1-2 发起的, 我们 break 在死锁检测的路径上，然后 print 整个锁信息 (代码基于 8.0.17):</p>
<p>我们设置断点在死锁检测的路径上，因为可以明确是 t1-2 的死锁检测触发了 t2 的回滚，所以我们可以明确哪次 break 是我们想要的断点位置.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">b</span> <span class="n">storage</span><span class="o">/</span><span class="n">innobase</span><span class="o">/</span><span class="n">lock</span><span class="o">/</span><span class="n">lock0lock</span><span class="p">.</span><span class="nl">cc</span><span class="p">:</span><span class="mi">7125</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 1. 按顺序执行 t1-1, t2, t1-2 直到 t1-2 触发死锁检测. */</span>
</span></span><span class="line"><span class="cl"><span class="cm">/* 2. DeadlockChecker 会设置一个 m_start 即发起死锁检测的 trx 和 m_wait_lock 即 m_start 等待的 lock. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="n">m_start</span> <span class="cm">/* 事务t1 */</span>
</span></span><span class="line"><span class="cl"><span class="err">$</span><span class="mi">9</span> <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="n">trx_t</span> <span class="o">*</span><span class="p">)</span> <span class="mh">0x7fffe506cc78</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="o">*</span><span class="n">m_wait_lock</span>  <span class="cm">/* 事务 t1 尝试申请的 lock, 目前在等待状态. */</span>
</span></span><span class="line"><span class="cl"><span class="err">$</span><span class="mi">53</span> <span class="o">=</span> <span class="p">{</span><span class="n">trx</span> <span class="o">=</span> <span class="mh">0x7fffe506cc78</span><span class="p">,</span> <span class="n">trx_locks</span> <span class="o">=</span> <span class="p">{</span><span class="n">prev</span> <span class="o">=</span> <span class="mh">0x7fffe00101f8</span><span class="p">,</span> <span class="n">next</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">},</span> <span class="n">index</span> <span class="o">=</span> <span class="mh">0x7ff8740971f8</span><span class="p">,</span> <span class="n">hash</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">,</span> <span class="p">{</span><span class="n">tab_lock</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">table</span> <span class="o">=</span> <span class="mh">0x500000002</span><span class="p">,</span> <span class="n">locks</span> <span class="o">=</span> <span class="p">{</span><span class="n">prev</span> <span class="o">=</span> <span class="mh">0x48</span><span class="p">,</span> <span class="n">next</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">}},</span> <span class="n">rec_lock</span> <span class="o">=</span> <span class="p">{</span><span class="n">space</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">page_no</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span> <span class="n">n_bits</span> <span class="o">=</span> <span class="mi">72</span><span class="p">}},</span>
</span></span><span class="line"><span class="cl">  <span class="n">m_psi_internal_thread_id</span> <span class="o">=</span> <span class="mi">43</span><span class="p">,</span> <span class="n">m_psi_event_id</span> <span class="o">=</span> <span class="mi">66</span><span class="p">,</span> <span class="n">type_mode</span> <span class="o">=</span> <span class="mi">291</span><span class="p">,</span> <span class="n">m_seq</span> <span class="o">=</span> <span class="mi">72</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 注意 m_wait_lock 的 type_mode */</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="o">/</span><span class="n">t</span> <span class="mi">291</span>
</span></span><span class="line"><span class="cl"><span class="err">$</span><span class="mi">13</span> <span class="o">=</span> <span class="mi">100100011</span> <span class="cm">/* 即 LOCK_X | LOCK_REC | LOCK_WAIT
</span></span></span><span class="line"><span class="cl"><span class="cm">
</span></span></span><span class="line"><span class="cl"><span class="cm">(gdb) p m_wait_lock-&gt;index-&gt;name
</span></span></span><span class="line"><span class="cl"><span class="cm">$22 = {m_name = 0x7ff874097538 &#34;uk_account&#34;} /* 可以确认是等待在唯一索引 uk_account 的 record lock. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 3. s 进入函数 DeadlockChecker::search(), 会首先 get_first_lock(&amp;heap_no) 即从 m_wait_lock 对应的 record 上的 heap_no 从 lock_sys-&gt;rec_hash 找到第一个 lock. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="n">lock_t</span> <span class="o">*</span><span class="n">lock</span> <span class="o">=</span> <span class="n">get_first_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">heap_no</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="n">lock</span>
</span></span><span class="line"><span class="cl"><span class="err">$</span><span class="mi">15</span> <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="n">ib_lock_t</span> <span class="o">*</span><span class="p">)</span> <span class="mh">0x7fffe0010098</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="o">*</span><span class="n">lock</span>
</span></span><span class="line"><span class="cl"><span class="err">$</span><span class="mi">14</span> <span class="o">=</span> <span class="p">{</span><span class="n">trx</span> <span class="o">=</span> <span class="mh">0x7fffe506cc78</span><span class="p">,</span> <span class="n">trx_locks</span> <span class="o">=</span> <span class="p">{</span><span class="n">prev</span> <span class="o">=</span> <span class="mh">0x7fffe0010cb8</span><span class="p">,</span> <span class="n">next</span> <span class="o">=</span> <span class="mh">0x7fffe00101f8</span><span class="p">},</span> <span class="n">index</span> <span class="o">=</span> <span class="mh">0x7ff8740971f8</span><span class="p">,</span> <span class="n">hash</span> <span class="o">=</span> <span class="mh">0x7fffe0010358</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">tab_lock</span> <span class="o">=</span> <span class="p">{</span><span class="n">table</span> <span class="o">=</span> <span class="mh">0x500000002</span><span class="p">,</span> <span class="n">locks</span> <span class="o">=</span> <span class="p">{</span><span class="n">prev</span> <span class="o">=</span> <span class="mh">0x48</span><span class="p">,</span> <span class="n">next</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">}},</span> <span class="n">rec_lock</span> <span class="o">=</span> <span class="p">{</span><span class="n">space</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">page_no</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span> <span class="n">n_bits</span> <span class="o">=</span> <span class="mi">72</span><span class="p">}},</span>
</span></span><span class="line"><span class="cl">  <span class="n">m_psi_internal_thread_id</span> <span class="o">=</span> <span class="mi">43</span><span class="p">,</span> <span class="n">m_psi_event_id</span> <span class="o">=</span> <span class="mi">50</span><span class="p">,</span> <span class="n">type_mode</span> <span class="o">=</span> <span class="mi">1059</span><span class="p">,</span> <span class="n">m_seq</span> <span class="o">=</span> <span class="mi">54</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* record 上的第一个 record 锁是 0x7fffe0010098, 它属于我们目前发起的事务 m_start 即 0x7fffe506cc78.
</span></span></span><span class="line"><span class="cl"><span class="cm">   type_mode 是 1059 即 LOCK_X | LOCK_REC | LOCK_REC_NOT_GAP. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 我们通过 lock-&gt;hash 迭代来查看整个 record 上的锁排列信息. */</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="o">*</span><span class="p">(</span><span class="n">ib_lock_t</span><span class="o">*</span><span class="p">)</span><span class="mh">0x7fffe0010098</span>  <span class="cm">/* 第一个 record lock */</span>
</span></span><span class="line"><span class="cl"><span class="err">$</span><span class="mi">48</span> <span class="o">=</span> <span class="p">{</span><span class="n">trx</span> <span class="o">=</span> <span class="mh">0x7fffe506cc78</span><span class="p">,</span> <span class="n">trx_locks</span> <span class="o">=</span> <span class="p">{</span><span class="n">prev</span> <span class="o">=</span> <span class="mh">0x7fffe0010cb8</span><span class="p">,</span> <span class="n">next</span> <span class="o">=</span> <span class="mh">0x7fffe00101f8</span><span class="p">},</span> <span class="n">index</span> <span class="o">=</span> <span class="mh">0x7ff8740971f8</span><span class="p">,</span> <span class="n">hash</span> <span class="o">=</span> <span class="mh">0x7fffe0011918</span><span class="p">,</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">tab_lock</span> <span class="o">=</span> <span class="p">{</span><span class="n">table</span> <span class="o">=</span> <span class="mh">0x500000002</span><span class="p">,</span> <span class="n">locks</span> <span class="o">=</span> <span class="p">{</span><span class="n">prev</span> <span class="o">=</span> <span class="mh">0x48</span><span class="p">,</span> <span class="n">next</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">}},</span> <span class="n">rec_lock</span> <span class="o">=</span> <span class="p">{</span><span class="n">space</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">page_no</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span> <span class="n">n_bits</span> <span class="o">=</span> <span class="mi">72</span><span class="p">}},</span>
</span></span><span class="line"><span class="cl">  <span class="n">m_psi_internal_thread_id</span> <span class="o">=</span> <span class="mi">43</span><span class="p">,</span> <span class="n">m_psi_event_id</span> <span class="o">=</span> <span class="mi">65</span><span class="p">,</span> <span class="n">type_mode</span> <span class="o">=</span> <span class="mi">1059</span><span class="p">,</span> <span class="n">m_seq</span> <span class="o">=</span> <span class="mi">69</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="o">*</span><span class="p">(</span><span class="n">ib_lock_t</span><span class="o">*</span><span class="p">)</span><span class="mh">0x7fffe0011918</span>  <span class="cm">/* 第二个 record lock. */</span>
</span></span><span class="line"><span class="cl"><span class="err">$</span><span class="mi">49</span> <span class="o">=</span> <span class="p">{</span><span class="n">trx</span> <span class="o">=</span> <span class="mh">0x7fffe506d090</span><span class="p">,</span> <span class="n">trx_locks</span> <span class="o">=</span> <span class="p">{</span><span class="n">prev</span> <span class="o">=</span> <span class="mh">0x7fffe00124c8</span><span class="p">,</span> <span class="n">next</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">},</span> <span class="n">index</span> <span class="o">=</span> <span class="mh">0x7ff8740971f8</span><span class="p">,</span> <span class="n">hash</span> <span class="o">=</span> <span class="mh">0x7fffe0010358</span><span class="p">,</span> <span class="p">{</span><span class="n">tab_lock</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">table</span> <span class="o">=</span> <span class="mh">0x500000002</span><span class="p">,</span> <span class="n">locks</span> <span class="o">=</span> <span class="p">{</span><span class="n">prev</span> <span class="o">=</span> <span class="mh">0x48</span><span class="p">,</span> <span class="n">next</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">}},</span> <span class="n">rec_lock</span> <span class="o">=</span> <span class="p">{</span><span class="n">space</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">page_no</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span> <span class="n">n_bits</span> <span class="o">=</span> <span class="mi">72</span><span class="p">}},</span>
</span></span><span class="line"><span class="cl">  <span class="n">m_psi_internal_thread_id</span> <span class="o">=</span> <span class="mi">44</span><span class="p">,</span> <span class="n">m_psi_event_id</span> <span class="o">=</span> <span class="mi">36</span><span class="p">,</span> <span class="n">type_mode</span> <span class="o">=</span> <span class="mi">1315</span><span class="p">,</span> <span class="n">m_seq</span> <span class="o">=</span> <span class="mi">71</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="o">*</span><span class="p">(</span><span class="n">ib_lock_t</span><span class="o">*</span><span class="p">)</span><span class="mh">0x7fffe0010358</span>  <span class="cm">/* 第三个 record lock. */</span>
</span></span><span class="line"><span class="cl"><span class="err">$</span><span class="mi">50</span> <span class="o">=</span> <span class="p">{</span><span class="n">trx</span> <span class="o">=</span> <span class="mh">0x7fffe506cc78</span><span class="p">,</span> <span class="n">trx_locks</span> <span class="o">=</span> <span class="p">{</span><span class="n">prev</span> <span class="o">=</span> <span class="mh">0x7fffe00101f8</span><span class="p">,</span> <span class="n">next</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">},</span> <span class="n">index</span> <span class="o">=</span> <span class="mh">0x7ff8740971f8</span><span class="p">,</span> <span class="n">hash</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">,</span> <span class="p">{</span><span class="n">tab_lock</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">table</span> <span class="o">=</span> <span class="mh">0x500000002</span><span class="p">,</span> <span class="n">locks</span> <span class="o">=</span> <span class="p">{</span><span class="n">prev</span> <span class="o">=</span> <span class="mh">0x48</span><span class="p">,</span> <span class="n">next</span> <span class="o">=</span> <span class="mh">0x0</span><span class="p">}},</span> <span class="n">rec_lock</span> <span class="o">=</span> <span class="p">{</span><span class="n">space</span> <span class="o">=</span> <span class="mi">2</span><span class="p">,</span> <span class="n">page_no</span> <span class="o">=</span> <span class="mi">5</span><span class="p">,</span> <span class="n">n_bits</span> <span class="o">=</span> <span class="mi">72</span><span class="p">}},</span>
</span></span><span class="line"><span class="cl">  <span class="n">m_psi_internal_thread_id</span> <span class="o">=</span> <span class="mi">43</span><span class="p">,</span> <span class="n">m_psi_event_id</span> <span class="o">=</span> <span class="mi">66</span><span class="p">,</span> <span class="n">type_mode</span> <span class="o">=</span> <span class="mi">291</span><span class="p">,</span> <span class="n">m_seq</span> <span class="o">=</span> <span class="mi">72</span><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 当前事务与 record 的关系如下:
</span></span></span><span class="line"><span class="cl"><span class="cm">发起死锁检测是 trx 是: 0x7fffe506cc78, trx 0x7fffe506cc78 等待的 lock 是 0x7fffe0010358.
</span></span></span><span class="line"><span class="cl"><span class="cm">
</span></span></span><span class="line"><span class="cl"><span class="cm">lock: 0x7fffe0010098 所属的 trx: 0x7fffe506cc78 type_mode: 1059 LOCK_X | LOCK_REC | LOCK_REC_NOT_GAP 即持有 X record lock.
</span></span></span><span class="line"><span class="cl"><span class="cm">lock: 0x7fffe0011918 所属的 trx: 0x7fffe506d090 type_mode: 1315 LOCK_X | LOCK_WAIT | LOCK_REC | LOCK_REC_NOT_GAP 即等待 X record lock.
</span></span></span><span class="line"><span class="cl"><span class="cm">lock: 0x7fffe0010358 所属的 trx: 0x7fffe506cc78 type_mode: 291  LOCK_X | LOCK_REC | LOCK_WAIT 即等待 X record lock. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 死锁检测流程结束后选择的 victim_trx 是: */</span>
</span></span><span class="line"><span class="cl"><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="n">victim_trx</span>
</span></span><span class="line"><span class="cl"><span class="err">$</span><span class="mi">58</span> <span class="o">=</span> <span class="p">(</span><span class="k">const</span> <span class="n">trx_t</span> <span class="o">*</span><span class="p">)</span> <span class="mh">0x7fffe506d090</span></span></span></code></pre></div><p>通过上述分析我们可以得出结论 t1 事务的 t1-2 语句触发了死锁检测，选择的 victim_trx 是事务 t2, 我们需要明确以下几个问题:</p>
<ol>
<li>发起死锁检测的原因是因为事务 t1 无法立即获得 X record lock.</li>
<li>事务 t1 认为可能会发生的死锁原因是因为在整个 lock 的等待关系中存在一个环, 即 t1 不 commit 提交事务, t2 事务也无法获取 X record lock, 从而导致 t1-2 的 UPDATE 语句也无法获得 X record lock 组成 Next-key record lock, 即使 t1 已经持有了 X record lock.</li>
</ol>
<h3 id="解决方案">解决方案</h3>
<p>根据最近的 Release Note, 我二分验证 8.0.16 - 8.0.22 的版本, 发现在 8.0.17 存在问题, 8.0.18 不存在, 所以根据现象我仔细查看了 8.0.18 的 <a href="https://dev.mysql.com/doc/relnotes/mysql/8.0/en/news-8-0-18.html">Release Note</a>, 发现了疑似这个现象的 Bugfix:</p>
<blockquote>
<ul>
<li>InnoDB: A deadlock was possible when a transaction tries to upgrade a record lock to a next key lock. (Bug #23755664, Bug #82127)</li>
</ul>
</blockquote>
<p>根据 Bug ID, 可以通过 Github 的 MySQL 提交记录来查找这个 <a href="https://github.com/mysql/mysql-server/commit/85927b60bc658ddfffcc3aeab15d7553d163b0be">Patch</a>:</p>
<blockquote>
<p>Bug #23755664 DEADLOCK WITH 3 CONCURRENT DELETES BY UNIQUE KEY</p>
<p>PROBLEM:
A deadlock was possible when a transaction tried to &ldquo;upgrade&rdquo; an already held Record Lock to Next Key Lock.</p>
<p>SOLUTION:
This patch is based on observations that:
(1) a Next Key Lock is equivalent to Record Lock combined with Gap Lock
(2) a GAP Lock never has to wait for any other lock
In case we request a Next Key Lock, we check if we already own a Record Lock of equal or stronger mode,
and if so, then we either upgrade it to Next Key Lock, or if it is not possible (because the single lock_t
struct is shared by more than one row) we change the requested lock type to GAP Lock, which we either already
have, or can be granted immediately.
(I don&rsquo;t consider Insert Intention Locks a Gap Lock in above statements).</p>
<p>Reviewed-by: Debarun Banerjee <a href="mailto:debarun.banerjee@oracle.com">debarun.banerjee@oracle.com</a>
RB:19879</p>
</blockquote>
<p>经过验证确实是这个 Patch 修复了这个死锁的问题.</p>
<h2 id="patch-分析">Patch 分析</h2>
<p>这个 Patch 具体的原理是当尝试获取 Next-key record lock 时，不再与旧的逻辑一样，旧的逻辑是先直接尝试申请 Next-key lock, 现在改为先判断当前 trx 是否持有 X record lock, 假如持有就复用这个 X record lock, 从而直接申请 GAP lock, 以达到 Next-key Lock 的效果.</p>
<p>所以在我们上面的例子中，申请 Next-key record lock 时跳过申请 X record lock, 就不会进入等待队列，也不会产生死锁的回环.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* 使用 8.0.26 最新版分析 lock 持有情况.
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 执行 t1：
</span></span></span><span class="line"><span class="cl"><span class="cm">		select * from t where account_id = &#39;1&#39; and type =1 for update;
</span></span></span><span class="line"><span class="cl"><span class="cm">		update t set state = 2 where account_id = &#39;1&#39;;
</span></span></span><span class="line"><span class="cl"><span class="cm">		show engine innodb status\G; */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">show</span> <span class="n">engine</span> <span class="n">innodb</span> <span class="n">status</span>
</span></span><span class="line"><span class="cl"><span class="n">TABLE</span> <span class="n">LOCK</span> <span class="n">table</span> <span class="err">`</span><span class="n">sbtest</span><span class="err">`</span><span class="p">.</span><span class="err">`</span><span class="n">t</span><span class="err">`</span> <span class="n">trx</span> <span class="n">id</span> <span class="mi">1323</span> <span class="n">lock</span> <span class="n">mode</span> <span class="n">IX</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 下列为 Record: 1,&#39;1&#39;,1,100,1 的 X record lock 基于索引 uk_account. (笔者注). */</span>
</span></span><span class="line"><span class="cl"><span class="n">RECORD</span> <span class="n">LOCKS</span> <span class="n">space</span> <span class="n">id</span> <span class="mi">2</span> <span class="n">page</span> <span class="n">no</span> <span class="mi">5</span> <span class="n">n</span> <span class="n">bits</span> <span class="mi">72</span> <span class="n">index</span> <span class="n">uk_account</span> <span class="n">of</span> <span class="n">table</span> <span class="err">`</span><span class="n">sbtest</span><span class="err">`</span><span class="p">.</span><span class="err">`</span><span class="n">t</span><span class="err">`</span> <span class="n">trx</span> <span class="n">id</span> <span class="mi">1323</span> <span class="n">lock_mode</span> <span class="n">X</span> <span class="n">locks</span> <span class="n">rec</span> <span class="n">but</span> <span class="n">not</span> <span class="n">gap</span>
</span></span><span class="line"><span class="cl"><span class="n">Record</span> <span class="n">lock</span><span class="p">,</span> <span class="n">heap</span> <span class="n">no</span> <span class="mi">2</span> <span class="n">PHYSICAL</span> <span class="nl">RECORD</span><span class="p">:</span> <span class="n">n_fields</span> <span class="mi">3</span><span class="p">;</span> <span class="n">compact</span> <span class="n">format</span><span class="p">;</span> <span class="n">info</span> <span class="n">bits</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"> <span class="mi">0</span><span class="o">:</span> <span class="n">len</span> <span class="mi">1</span><span class="p">;</span> <span class="n">hex</span> <span class="mi">31</span><span class="p">;</span> <span class="n">asc</span> <span class="mi">1</span><span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">1</span><span class="o">:</span> <span class="n">len</span> <span class="mi">1</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">01</span><span class="p">;</span> <span class="n">asc</span>  <span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">2</span><span class="o">:</span> <span class="n">len</span> <span class="mi">8</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">0000000000000001</span><span class="p">;</span> <span class="n">asc</span>         <span class="p">;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 下列为 Record: 1,&#39;1&#39;,1,100,1 的 X record lock 基于主键索引. (笔者注). */</span>
</span></span><span class="line"><span class="cl"><span class="n">RECORD</span> <span class="n">LOCKS</span> <span class="n">space</span> <span class="n">id</span> <span class="mi">2</span> <span class="n">page</span> <span class="n">no</span> <span class="mi">4</span> <span class="n">n</span> <span class="n">bits</span> <span class="mi">72</span> <span class="n">index</span> <span class="n">PRIMARY</span> <span class="n">of</span> <span class="n">table</span> <span class="err">`</span><span class="n">sbtest</span><span class="err">`</span><span class="p">.</span><span class="err">`</span><span class="n">t</span><span class="err">`</span> <span class="n">trx</span> <span class="n">id</span> <span class="mi">1323</span> <span class="n">lock_mode</span> <span class="n">X</span> <span class="n">locks</span> <span class="n">rec</span> <span class="n">but</span> <span class="n">not</span> <span class="n">gap</span>
</span></span><span class="line"><span class="cl"><span class="n">Record</span> <span class="n">lock</span><span class="p">,</span> <span class="n">heap</span> <span class="n">no</span> <span class="mi">2</span> <span class="n">PHYSICAL</span> <span class="nl">RECORD</span><span class="p">:</span> <span class="n">n_fields</span> <span class="mi">7</span><span class="p">;</span> <span class="n">compact</span> <span class="n">format</span><span class="p">;</span> <span class="n">info</span> <span class="n">bits</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"> <span class="mi">0</span><span class="o">:</span> <span class="n">len</span> <span class="mi">8</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">0000000000000001</span><span class="p">;</span> <span class="n">asc</span>         <span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">1</span><span class="o">:</span> <span class="n">len</span> <span class="mi">6</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">00000000052</span><span class="n">b</span><span class="p">;</span> <span class="n">asc</span>      <span class="o">+</span><span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">2</span><span class="o">:</span> <span class="n">len</span> <span class="mi">7</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">020000011</span><span class="n">b0110</span><span class="p">;</span> <span class="n">asc</span>        <span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">3</span><span class="o">:</span> <span class="n">len</span> <span class="mi">1</span><span class="p">;</span> <span class="n">hex</span> <span class="mi">31</span><span class="p">;</span> <span class="n">asc</span> <span class="mi">1</span><span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">4</span><span class="o">:</span> <span class="n">len</span> <span class="mi">1</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">01</span><span class="p">;</span> <span class="n">asc</span>  <span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">5</span><span class="o">:</span> <span class="n">len</span> <span class="mi">8</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">0000000000000064</span><span class="p">;</span> <span class="n">asc</span>        <span class="n">d</span><span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">6</span><span class="o">:</span> <span class="n">len</span> <span class="mi">4</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">00000002</span><span class="p">;</span> <span class="n">asc</span>     <span class="p">;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 下列为 Record: 1,&#39;1&#39;,1,100,1 的 GAP record lock 基于索引 uk_account, 与上面的 uk_account 的 X lock 组成 Next-key Lock. (笔者注). */</span>
</span></span><span class="line"><span class="cl"><span class="n">RECORD</span> <span class="n">LOCKS</span> <span class="n">space</span> <span class="n">id</span> <span class="mi">2</span> <span class="n">page</span> <span class="n">no</span> <span class="mi">5</span> <span class="n">n</span> <span class="n">bits</span> <span class="mi">72</span> <span class="n">index</span> <span class="n">uk_account</span> <span class="n">of</span> <span class="n">table</span> <span class="err">`</span><span class="n">sbtest</span><span class="err">`</span><span class="p">.</span><span class="err">`</span><span class="n">t</span><span class="err">`</span> <span class="n">trx</span> <span class="n">id</span> <span class="mi">1323</span> <span class="n">lock_mode</span> <span class="n">X</span> <span class="n">locks</span> <span class="n">gap</span> <span class="n">before</span> <span class="n">rec</span>
</span></span><span class="line"><span class="cl"><span class="n">Record</span> <span class="n">lock</span><span class="p">,</span> <span class="n">heap</span> <span class="n">no</span> <span class="mi">2</span> <span class="n">PHYSICAL</span> <span class="nl">RECORD</span><span class="p">:</span> <span class="n">n_fields</span> <span class="mi">3</span><span class="p">;</span> <span class="n">compact</span> <span class="n">format</span><span class="p">;</span> <span class="n">info</span> <span class="n">bits</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"> <span class="mi">0</span><span class="o">:</span> <span class="n">len</span> <span class="mi">1</span><span class="p">;</span> <span class="n">hex</span> <span class="mi">31</span><span class="p">;</span> <span class="n">asc</span> <span class="mi">1</span><span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">1</span><span class="o">:</span> <span class="n">len</span> <span class="mi">1</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">01</span><span class="p">;</span> <span class="n">asc</span>  <span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">2</span><span class="o">:</span> <span class="n">len</span> <span class="mi">8</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">0000000000000001</span><span class="p">;</span> <span class="n">asc</span>         <span class="p">;;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 下列为 Record: 2,&#39;2&#39;,1,100,1 的 GAP record lock 基于索引 uk_account. (笔者注: lock 的类型与上列一致, InnoDB 在这里对相同类型的 lock 做了省略). */</span>
</span></span><span class="line"><span class="cl"><span class="n">Record</span> <span class="n">lock</span><span class="p">,</span> <span class="n">heap</span> <span class="n">no</span> <span class="mi">3</span> <span class="n">PHYSICAL</span> <span class="nl">RECORD</span><span class="p">:</span> <span class="n">n_fields</span> <span class="mi">3</span><span class="p">;</span> <span class="n">compact</span> <span class="n">format</span><span class="p">;</span> <span class="n">info</span> <span class="n">bits</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"> <span class="mi">0</span><span class="o">:</span> <span class="n">len</span> <span class="mi">1</span><span class="p">;</span> <span class="n">hex</span> <span class="mi">32</span><span class="p">;</span> <span class="n">asc</span> <span class="mi">2</span><span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">1</span><span class="o">:</span> <span class="n">len</span> <span class="mi">1</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">01</span><span class="p">;</span> <span class="n">asc</span>  <span class="p">;;</span>
</span></span><span class="line"><span class="cl"> <span class="mi">2</span><span class="o">:</span> <span class="n">len</span> <span class="mi">8</span><span class="p">;</span> <span class="n">hex</span> <span class="mo">0000000000000002</span><span class="p">;</span> <span class="n">asc</span>         <span class="p">;</span></span></span></code></pre></div><h2 id="总结">总结</h2>
<p>根据例子我们分析了一个 InnoDB 的死锁场景, 以及 Bug 产生的原因. 通过 gdb 调试的方式分析 InnoDB 的死锁原因，最主要任务就是梳理整个锁的等待依赖关系, 这能帮助我们更直观的分析真正的原因. 这是一个 X record lock &ldquo;升级&rdquo; 至 Next-key record lock 的 Bug, 官方在 8.0.18 已经修复了这个存在了几年的问题.</p>
]]></content>
  </entry><entry>
    <title>InnoDB 事务 sharded 锁系统优化</title>
    <link href="https://leviathan.vip/2020/12/22/mysql-understand-trx-lock/" />
    <id>https://leviathan.vip/2020/12/22/mysql-understand-trx-lock/</id>
    <updated>2020-12-22T13:14:33Z</updated>
    <summary type="html"><![CDATA[<h2 id="准备">准备</h2>
<p><strong>MySQL内核版本: 8.0.21</strong></p>
<h2 id="背景">背景</h2>
<ul>
<li>
<p>latch
数据库中的 latch 和我们通常代码编程中保证并发多线程操作操作临界资源的锁意义一样，通过 latch 的中文翻译“闩”就可以理解，这是为了维护一段临界区域.</p>
</li>
<li>
<p>lock
而 lock 则是数据库 MySQL 中在事务使用的”锁”, 锁定的对象是表或者行.</p>
</li>
</ul>
<p>数据库内核月报<a href="http://mysql.taobao.org/monthly/2016/01/01/"> InnoDB 事务锁系统简介</a>对 InnoDB 的事务锁系统: record lock 和 table lock 做了具体的介绍, 其中对于 record 和 table 会将所有 <code>GRANTED</code> 或者 <code>WAITING</code> 插入对应的 hash table.</p>
<p>在官方 MySQL 实现中, 事务锁系统由 <code>lock_sys_t *lock_sys</code> 统一管理,  当事务尝试申请一个 lock 时，会首先尝试获取 <code>lock_sys-&gt;mutex</code>, 在 lock 创建成功后，会插入对应类型的 hash table, 下面是官方MySQL实现中的 hash table:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/** The lock system struct */</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">lock_sys_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">LockMutex</span> <span class="n">mutex</span><span class="p">;</span>              <span class="cm">/*!&lt; Mutex protecting the
</span></span></span><span class="line"><span class="cl"><span class="cm">                                locks */</span>
</span></span><span class="line"><span class="cl">  <span class="n">hash_table_t</span> <span class="o">*</span><span class="n">rec_hash</span><span class="p">;</span>       <span class="cm">/*!&lt; hash table of the record
</span></span></span><span class="line"><span class="cl"><span class="cm">                                locks */</span>
</span></span><span class="line"><span class="cl">  <span class="n">hash_table_t</span> <span class="o">*</span><span class="n">prdt_hash</span><span class="p">;</span>      <span class="cm">/*!&lt; hash table of the predicate
</span></span></span><span class="line"><span class="cl"><span class="cm">                                lock */</span>
</span></span><span class="line"><span class="cl">  <span class="n">hash_table_t</span> <span class="o">*</span><span class="n">prdt_page_hash</span><span class="p">;</span> <span class="cm">/*!&lt; hash table of the page
</span></span></span><span class="line"><span class="cl"><span class="cm">                                lock */</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/*  ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>通过上面的简述可以理解当每一个事务需要尝试申请一个 lock 时，都需要获取这个 <code>lock_sys-&gt;mutex</code> 全局的 latch, 这对于高并发的事务处理来说是一个瓶颈. MySQL 官方在 8.0.21 版本针对这个问题使用分区 latch 来解决: <a href="https://dev.mysql.com/worklog/task/?id=10314">worklog #10314</a>.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="准备">准备</h2>
<p><strong>MySQL内核版本: 8.0.21</strong></p>
<h2 id="背景">背景</h2>
<ul>
<li>
<p>latch
数据库中的 latch 和我们通常代码编程中保证并发多线程操作操作临界资源的锁意义一样，通过 latch 的中文翻译“闩”就可以理解，这是为了维护一段临界区域.</p>
</li>
<li>
<p>lock
而 lock 则是数据库 MySQL 中在事务使用的”锁”, 锁定的对象是表或者行.</p>
</li>
</ul>
<p>数据库内核月报<a href="http://mysql.taobao.org/monthly/2016/01/01/"> InnoDB 事务锁系统简介</a>对 InnoDB 的事务锁系统: record lock 和 table lock 做了具体的介绍, 其中对于 record 和 table 会将所有 <code>GRANTED</code> 或者 <code>WAITING</code> 插入对应的 hash table.</p>
<p>在官方 MySQL 实现中, 事务锁系统由 <code>lock_sys_t *lock_sys</code> 统一管理,  当事务尝试申请一个 lock 时，会首先尝试获取 <code>lock_sys-&gt;mutex</code>, 在 lock 创建成功后，会插入对应类型的 hash table, 下面是官方MySQL实现中的 hash table:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/** The lock system struct */</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">lock_sys_t</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">LockMutex</span> <span class="n">mutex</span><span class="p">;</span>              <span class="cm">/*!&lt; Mutex protecting the
</span></span></span><span class="line"><span class="cl"><span class="cm">                                locks */</span>
</span></span><span class="line"><span class="cl">  <span class="n">hash_table_t</span> <span class="o">*</span><span class="n">rec_hash</span><span class="p">;</span>       <span class="cm">/*!&lt; hash table of the record
</span></span></span><span class="line"><span class="cl"><span class="cm">                                locks */</span>
</span></span><span class="line"><span class="cl">  <span class="n">hash_table_t</span> <span class="o">*</span><span class="n">prdt_hash</span><span class="p">;</span>      <span class="cm">/*!&lt; hash table of the predicate
</span></span></span><span class="line"><span class="cl"><span class="cm">                                lock */</span>
</span></span><span class="line"><span class="cl">  <span class="n">hash_table_t</span> <span class="o">*</span><span class="n">prdt_page_hash</span><span class="p">;</span> <span class="cm">/*!&lt; hash table of the page
</span></span></span><span class="line"><span class="cl"><span class="cm">                                lock */</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/*  ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>通过上面的简述可以理解当每一个事务需要尝试申请一个 lock 时，都需要获取这个 <code>lock_sys-&gt;mutex</code> 全局的 latch, 这对于高并发的事务处理来说是一个瓶颈. MySQL 官方在 8.0.21 版本针对这个问题使用分区 latch 来解决: <a href="https://dev.mysql.com/worklog/task/?id=10314">worklog #10314</a>.</p>
<h2 id="lock_sys">lock_sys</h2>
<p>在 8.0.21 之前的版本申请 record lock 时需要获取全局的 <code>lock_sys-&gt;mutex</code>, 以 record lock 为例:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">dberr_t</span> <span class="nf">lock_clust_rec_modify_check_and_lock</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 获取全局的 lock_sys-&gt;mutex. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">lock_mutex_enter</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">lock_table_has</span><span class="p">(</span><span class="n">thr_get_trx</span><span class="p">(</span><span class="n">thr</span><span class="p">),</span> <span class="n">index</span><span class="o">-&gt;</span><span class="n">table</span><span class="p">,</span> <span class="n">LOCK_IX</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 尝试获取 record lock. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">err</span> <span class="o">=</span> <span class="n">lock_rec_lock</span><span class="p">(</span><span class="nb">true</span><span class="p">,</span> <span class="n">SELECT_ORDINARY</span><span class="p">,</span> <span class="n">LOCK_X</span> <span class="o">|</span> <span class="n">LOCK_REC_NOT_GAP</span><span class="p">,</span> <span class="n">block</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                      <span class="n">heap_no</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">thr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">MONITOR_INC</span><span class="p">(</span><span class="n">MONITOR_NUM_RECLOCK_REQ</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 放锁. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">lock_mutex_exit</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>尤其当高并发事务处理，<code>lock_sys-&gt;mutex</code> 的瓶颈会凸显. 为此官方将 <code>lock_sys-&gt;mutex</code> 的进行拆分, 引入了 3 个类型的 latch, 一个全局的 <code>global latch</code>, 512 组 <code>table latches</code> 和 512 组 <code>page latches</code>:</p>
<ul>
<li>
<p><code>global latch (lock_sys-&gt;latches.global_latch)</code>: 一个全局读写锁, 当 <code>lock_sys</code> 全局操作时, 直接对 <code>global_latch</code> 上 X 锁, 其他操作仅需要 S 锁.</p>
</li>
<li>
<p><code>table shard latches (lock_sys-&gt;latches.table_shards.mutexes)</code>: 512 个 table latches, 用来分片 table lock.</p>
</li>
<li>
<p><code>page shard latches (lock_sys-&gt;latches.page_shards.mutexes)</code>: 512 个 page latches, 用来分片 record lock.</p>
</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">  <span class="o">[</span>                           global latch                                <span class="o">]</span>
</span></span><span class="line"><span class="cl">                                  <span class="p">|</span>
</span></span><span class="line"><span class="cl">                                  v
</span></span><span class="line"><span class="cl">  <span class="o">[</span>table shard 1<span class="o">]</span> ... <span class="o">[</span>table shard 512<span class="o">]</span> <span class="o">[</span>page shard 1<span class="o">]</span> ... <span class="o">[</span>page shard 512<span class="o">]</span></span></span></code></pre></div><ul>
<li>
<p><code>Shard_latch_guard</code>: 针对 global latch 使用 s-latch 并对单个 shard mutex 上锁.</p>
</li>
<li>
<p><code>Shard_latches_guard</code>: 针对 global latch 使用 s-latch 并对两个 shard mutex 上锁.</p>
</li>
<li>
<p><code>Global_exclusive_latch_guard</code>: 针对 global latch 使用 x-latch.</p>
</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* global_latch X 锁. */</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Global_exclusive_latch_guard</span> <span class="o">:</span> <span class="k">private</span> <span class="n">ut</span><span class="o">::</span><span class="n">Non_copyable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">Global_exclusive_latch_guard</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="o">~</span><span class="n">Global_exclusive_latch_guard</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* global_latch S 锁. */</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Global_shared_latch_guard</span> <span class="o">:</span> <span class="k">private</span> <span class="n">ut</span><span class="o">::</span><span class="n">Non_copyable</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">Global_shared_latch_guard</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="o">~</span><span class="n">Global_shared_latch_guard</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 单个 shard mutex. */</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Shard_latch_guard</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">Global_shared_latch_guard</span> <span class="n">m_global_shared_latch_guard</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">Shard_naked_latch_guard</span> <span class="n">m_shard_naked_latch_guard</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">  <span class="k">explicit</span> <span class="n">Shard_latch_guard</span><span class="p">(</span><span class="k">const</span> <span class="n">dict_table_t</span> <span class="o">&amp;</span><span class="n">table</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">:</span> <span class="n">m_global_shared_latch_guard</span><span class="p">{},</span> <span class="n">m_shard_naked_latch_guard</span><span class="p">{</span><span class="n">table</span><span class="p">}</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">explicit</span> <span class="nf">Shard_latch_guard</span><span class="p">(</span><span class="k">const</span> <span class="n">page_id_t</span> <span class="o">&amp;</span><span class="n">page_id</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">:</span> <span class="n">m_global_shared_latch_guard</span><span class="p">{},</span> <span class="n">m_shard_naked_latch_guard</span><span class="p">{</span><span class="n">page_id</span><span class="p">}</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 两个 shard mutex, 用于两个 Page 的 record lock 处理, 例如页迁移. */</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Shard_latches_guard</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">  <span class="k">explicit</span> <span class="n">Shard_latches_guard</span><span class="p">(</span><span class="k">const</span> <span class="n">buf_block_t</span> <span class="o">&amp;</span><span class="n">block_a</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                               <span class="k">const</span> <span class="n">buf_block_t</span> <span class="o">&amp;</span><span class="n">block_b</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="o">:</span> <span class="n">m_global_shared_latch_guard</span><span class="p">{},</span>
</span></span><span class="line"><span class="cl">        <span class="n">m_shard_naked_latches_guard</span><span class="p">{</span><span class="n">block_a</span><span class="p">,</span> <span class="n">block_b</span><span class="p">}</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">Global_shared_latch_guard</span> <span class="n">m_global_shared_latch_guard</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">Shard_naked_latches_guard</span> <span class="n">m_shard_naked_latches_guard</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* Shard_naked_latches_guard 并不直接使用, 由 Shard_latch_guard 或 Shard_latches_guard
</span></span></span><span class="line"><span class="cl"><span class="cm">包装 global_latch 联合使用. */</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Shard_naked_latches_guard</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="k">explicit</span> <span class="nf">Shard_naked_latches_guard</span><span class="p">(</span><span class="n">Lock_mutex</span> <span class="o">&amp;</span><span class="n">shard_mutex_a</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                     <span class="n">Lock_mutex</span> <span class="o">&amp;</span><span class="n">shard_mutex_b</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">  <span class="k">explicit</span> <span class="n">Shard_naked_latches_guard</span><span class="p">(</span><span class="k">const</span> <span class="n">buf_block_t</span> <span class="o">&amp;</span><span class="n">block_a</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                     <span class="k">const</span> <span class="n">buf_block_t</span> <span class="o">&amp;</span><span class="n">block_b</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="o">~</span><span class="n">Shard_naked_latches_guard</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The &#34;smallest&#34; of the two shards&#39; mutexes in the latching order */</span>
</span></span><span class="line"><span class="cl">  <span class="n">Lock_mutex</span> <span class="o">&amp;</span><span class="n">m_shard_mutex_1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The &#34;largest&#34; of the two shards&#39; mutexes in the latching order */</span>
</span></span><span class="line"><span class="cl">  <span class="n">Lock_mutex</span> <span class="o">&amp;</span><span class="n">m_shard_mutex_2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** The ordering on shard mutexes used to avoid deadlocks */</span>
</span></span><span class="line"><span class="cl">  <span class="k">static</span> <span class="k">constexpr</span> <span class="n">std</span><span class="o">::</span><span class="n">less</span><span class="o">&lt;</span><span class="n">Lock_mutex</span> <span class="o">*&gt;</span> <span class="n">MUTEX_ORDER</span><span class="p">{};</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p><img src="/images/sharded_lock.png" alt="sharded_lock"></p>
<p>在使用 shard lock 后, 申请 record lock 只需要获取对应 Page 的 <code>lock_rec_hash(page_id) % SHARDS_COUNT</code> 槽位的 mutex 即可:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">dberr_t</span> <span class="nf">lock_clust_rec_modify_check_and_lock</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 1. global_latch 2. 对应 page_id 的 Shard_latch_guard. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">locksys</span><span class="o">::</span><span class="n">Shard_latch_guard</span> <span class="n">guard</span><span class="p">{</span><span class="n">block</span><span class="o">-&gt;</span><span class="n">get_page_id</span><span class="p">()};</span>
</span></span><span class="line"><span class="cl">    <span class="n">ut_ad</span><span class="p">(</span><span class="n">lock_table_has</span><span class="p">(</span><span class="n">thr_get_trx</span><span class="p">(</span><span class="n">thr</span><span class="p">),</span> <span class="n">index</span><span class="o">-&gt;</span><span class="n">table</span><span class="p">,</span> <span class="n">LOCK_IX</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 申请锁. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">err</span> <span class="o">=</span> <span class="n">lock_rec_lock</span><span class="p">(</span><span class="nb">true</span><span class="p">,</span> <span class="n">SELECT_ORDINARY</span><span class="p">,</span> <span class="n">LOCK_X</span> <span class="o">|</span> <span class="n">LOCK_REC_NOT_GAP</span><span class="p">,</span> <span class="n">block</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                        <span class="n">heap_no</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">thr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">MONITOR_INC</span><span class="p">(</span><span class="n">MONITOR_NUM_RECLOCK_REQ</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* RAII 模式, 作用域结束即释放. */</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut_d</span><span class="p">(</span><span class="n">locksys</span><span class="o">::</span><span class="n">rec_queue_latch_and_validate</span><span class="p">(</span><span class="n">block</span><span class="p">,</span> <span class="n">rec</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">offsets</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>上述代码是以 record lock 举例，使用 shard lock 后 record 申请的流程为:</p>
<ul>
<li>
<p>针对 global_latch 使用 s-latch</p>
</li>
<li>
<p>获取对应 page_id 在 lock_sys 中 page_shards 的 latch:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">lock_sys</span><span class="o">-&gt;</span><span class="n">latches</span><span class="p">.</span><span class="n">page_shards</span><span class="p">.</span><span class="n">get_mutex</span><span class="p">(</span><span class="n">page_id</span><span class="p">)</span></span></span></code></pre></div></li>
<li>
<p>对 latch 上锁: <code>mutex_enter(&amp;m_shard_mutex)</code>.</p>
</li>
</ul>
<p>Shard_latch_guard 等实现均为 RAII 模式, 离开作用域后自动析构.</p>
<h2 id="死锁检测">死锁检测</h2>
<p>官方在 8.0.18 版本对死锁检测进行了优化, 将原先的死锁检测机制<a href="https://leviathan.vip/2020/02/02/mysql-deadlock-check/"> MySQL 死锁检测源码分析
</a> 交给了 background thread 来处理, 具体的 Patch 链接: <a href="https://github.com/mysql/mysql-server/commit/3859219875b62154b921e8c6078c751198071b9c">MySQL-8.0.18 死锁检测优化</a>. 具体的思路是将当前事务系统的 lock 信息打一份快照, 由这份快照判断是否存在回环, 假如存在死锁即唤醒等待事务.</p>
<p>使用 shard lock 优化后, 因为存在多个 thread 并发更新当前 trx 的锁操作, 所以死锁检测使用 <code>Global_exclusive_latch_guard</code> 来互斥当前的 lock 操作.</p>
<h2 id="总结">总结</h2>
<p>MySQL 官方针对 lock_sys 的 mutex 瓶颈使用了 sharded lock 的方法进行优化，这依然延续了系统设计的优化思路, 将一个 bottleneck 的全局锁拆分为 sharded, 这也符合当前多核设计下, 充分利用硬件特性以此提高并行处理能力的趋势.</p>
]]></content>
  </entry><entry>
    <title>InnoDB 并行读取框架</title>
    <link href="https://leviathan.vip/2020/10/02/innodb-parallel-read-of-index/" />
    <id>https://leviathan.vip/2020/10/02/innodb-parallel-read-of-index/</id>
    <updated>2020-10-02T20:15:38Z</updated>
    <summary type="html"><![CDATA[<h2 id="版本">版本</h2>
<ul>
<li>MySQL 8.0.14</li>
</ul>
<h2 id="准备">准备</h2>
<p>在 MySQL 8.0.14 版本 InnoDB 引擎发布了一个新的特性 Parallel read of index (并行索引读取), 主要用于并行的读取索引数据, 目前仅仅支持 SELECT COUNT() 和 CHECK TABLE 操作, InnoDB 后续对于其他操作还会有更多的优化支持. 通过这个并行索引读取框架, InnoDB 可以支持同步、异步的并发读取索引数据, 异步的读取索引数据可以用来实现逻辑预读操作. 在此之前的预读逻辑, InnoDB 只有线性预读和随机预读这两种物理预读处理方法, 而对于 B+ tree 这种树形结构显然逻辑预读才更合适.</p>
<h2 id="并行索引读取">并行索引读取</h2>
<h3 id="参数">参数</h3>
<ul>
<li>innodb_parallel_read_threads: 当前并行读取的 worker 线程数量.</li>
</ul>
<p><code>innodb_parallel_read_threads</code> 是 session 级别的变量, 假如需要打开并行扫描框架即:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">set</span><span class="w"> </span><span class="k">local</span><span class="w"> </span><span class="n">innodb_parallel_read_threads</span><span class="o">=</span><span class="mi">4</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="k">count</span><span class="p">(</span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">sbtest</span><span class="p">.</span><span class="k">table</span><span class="p">;</span></span></span></code></pre></div><h3 id="设计思想">设计思想</h3>
<p>Parallel read of index 主要利用当前的多核硬件优势, 针对当前可以并行读取的逻辑例如 SELECT COUNT() 或者 CHECK TABLE, 其主要逻辑是收集数据叶子节点的 Page Number, 使用多个 worker 并行读取数据 Page, 利用不同的回调函数来处理获取后的 rows. 目前 SELET COUNT() 和 CHECK TABLE 都是同步读取, 但 InnoDB 依然提供了接口处理对应的异步读取, 后续会针对需要异步读取的场景提供更多的优化路径.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="版本">版本</h2>
<ul>
<li>MySQL 8.0.14</li>
</ul>
<h2 id="准备">准备</h2>
<p>在 MySQL 8.0.14 版本 InnoDB 引擎发布了一个新的特性 Parallel read of index (并行索引读取), 主要用于并行的读取索引数据, 目前仅仅支持 SELECT COUNT() 和 CHECK TABLE 操作, InnoDB 后续对于其他操作还会有更多的优化支持. 通过这个并行索引读取框架, InnoDB 可以支持同步、异步的并发读取索引数据, 异步的读取索引数据可以用来实现逻辑预读操作. 在此之前的预读逻辑, InnoDB 只有线性预读和随机预读这两种物理预读处理方法, 而对于 B+ tree 这种树形结构显然逻辑预读才更合适.</p>
<h2 id="并行索引读取">并行索引读取</h2>
<h3 id="参数">参数</h3>
<ul>
<li>innodb_parallel_read_threads: 当前并行读取的 worker 线程数量.</li>
</ul>
<p><code>innodb_parallel_read_threads</code> 是 session 级别的变量, 假如需要打开并行扫描框架即:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">set</span><span class="w"> </span><span class="k">local</span><span class="w"> </span><span class="n">innodb_parallel_read_threads</span><span class="o">=</span><span class="mi">4</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="k">select</span><span class="w"> </span><span class="k">count</span><span class="p">(</span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="k">from</span><span class="w"> </span><span class="n">sbtest</span><span class="p">.</span><span class="k">table</span><span class="p">;</span></span></span></code></pre></div><h3 id="设计思想">设计思想</h3>
<p>Parallel read of index 主要利用当前的多核硬件优势, 针对当前可以并行读取的逻辑例如 SELECT COUNT() 或者 CHECK TABLE, 其主要逻辑是收集数据叶子节点的 Page Number, 使用多个 worker 并行读取数据 Page, 利用不同的回调函数来处理获取后的 rows. 目前 SELET COUNT() 和 CHECK TABLE 都是同步读取, 但 InnoDB 依然提供了接口处理对应的异步读取, 后续会针对需要异步读取的场景提供更多的优化路径.</p>
<h3 id="实现">实现</h3>
<h4 id="row_scan_index_for_mysql">row_scan_index_for_mysql()</h4>
<p><code>row_scan_index_for_mysql()</code> 作为 SELECT COUNT() 和 CHECK TABLE 的入口函数:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"> <span class="cm">/* 扫描索引数据 */</span>
</span></span><span class="line"><span class="cl"> <span class="o">----------------------------</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="n">row_scan_index_for_mysql</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">----------------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>         <span class="cm">/* SELECT COUNT() */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">------------------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">parallel_select_count_star</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">------------------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>     <span class="cm">/* CHECK TABLE */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">------------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">parallel_check_table</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">       <span class="o">------------------------</span></span></span></code></pre></div><h4 id="基本数据结构">基本数据结构</h4>
<ul>
<li>
<p>Parallel_reader::Scan_range: 代表当前并行扫描的范围.</p>
</li>
<li>
<p>Parallel_reader::Config 并行扫描的 configuration.</p>
</li>
<li>
<p>Parallel_reader::Scan_ctx 并行扫描的上下文 (context).</p>
</li>
<li>
<p>Parallel_reader::Ctx 并行读取的执行上下文 (Parallel reader execution context)</p>
</li>
<li>
<p>Parallel_reader 并行扫描 reader</p>
</li>
</ul>
<h3 id="select-count">SELECT COUNT()</h3>
<p>我们以全表扫描 SELECT COUNT() 为例, 根据源码分析 Parallel Read 的原理:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* SELECT COUNT() 的入口函数 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="n">dberr_t</span> <span class="nf">parallel_select_count_star</span><span class="p">(</span><span class="n">Key_reader</span> <span class="o">&amp;</span><span class="n">reader</span><span class="p">,</span> <span class="n">ulint</span> <span class="o">*</span><span class="n">n_rows</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">Counter</span><span class="o">::</span><span class="n">Shards</span> <span class="n">n_recs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">Counter</span><span class="o">::</span><span class="n">clear</span><span class="p">(</span><span class="n">n_recs</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">const</span> <span class="n">buf_block_t</span> <span class="o">*</span><span class="n">prev_block</span> <span class="o">=</span> <span class="k">nullptr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">dberr_t</span> <span class="n">err</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">      <span class="n">reader</span><span class="p">.</span><span class="n">read</span><span class="p">([</span><span class="o">&amp;</span><span class="p">](</span><span class="n">size_t</span> <span class="n">id</span><span class="p">,</span> <span class="k">const</span> <span class="n">buf_block_t</span> <span class="o">*</span><span class="n">block</span><span class="p">,</span> <span class="k">const</span> <span class="n">rec_t</span> <span class="o">*</span><span class="n">rec</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                      <span class="n">dict_index_t</span> <span class="o">*</span><span class="n">index</span><span class="p">,</span> <span class="n">row_prebuilt_t</span> <span class="o">*</span><span class="n">prebuilt</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Counter</span><span class="o">::</span><span class="n">inc</span><span class="p">(</span><span class="n">n_recs</span><span class="p">,</span> <span class="n">id</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* Only check the THD state for the first thread. */</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">id</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">&amp;&amp;</span> <span class="n">block</span> <span class="o">!=</span> <span class="n">prev_block</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">prev_block</span> <span class="o">=</span> <span class="n">block</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">          <span class="k">if</span> <span class="p">(</span><span class="n">trx_is_interrupted</span><span class="p">(</span><span class="n">reader</span><span class="p">.</span><span class="n">trx</span><span class="p">()))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">(</span><span class="n">DB_INTERRUPTED</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="n">DB_SUCCESS</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">});</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 统计计数 */</span>
</span></span><span class="line"><span class="cl">  <span class="o">*</span><span class="n">n_rows</span> <span class="o">=</span> <span class="n">Counter</span><span class="o">::</span><span class="n">total</span><span class="p">(</span><span class="n">n_recs</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="n">err</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>Key_reader</code> 使用 partition() 将 B+ tree 分片, 分配各个 worker 线程, InnoDB 的 B+ 树将数据存放在所有的叶子节点, 即叶子节点为 level 0, 分配策略是从 root 节点遍历, 使用 <code>left_leaf()</code> 从左边由上至下直到 level N 层的节点数量大于等于 worker 线程数量:</p>
<p><img src="/images/parallel_reader.png" alt="parallel_reader"></p>
<p><code>Key_reader</code> 会在指定 level 的 sub-tree 的 &ldquo;root page&rdquo; 中分别选择第一个 record, 从而找到其在 leaf level 层的 page no(create_range()-&gt;create_persistent_cursor()), 新建 scan context 交由 worker 线程.</p>
<h4 id="并行读取流程">并行读取流程</h4>
<p>启动 worker 线程, worker 线程也就是真正的读取线程，对一个切好的 sub-tree 做 scan, worker 线程分别根据被分配的 leaf page cursor 进行顺序读取.</p>
<p>并行读取线程会根据创建的读取对应叶子节点的 record, 并且会根据 <code>trx-&gt;read_view</code> 来判断可见性.</p>
<h2 id="总结">总结</h2>
<p>我们通过 SELECT COUNT() 分析了 InnoDB 实现的 Parallel Read 框架，虽然目前仅支持 CHECK TABLE 和 SELECT COUNT(), 但整个框架支持了足够多的接口，后续应该会支持更多的场景. 例如目前 CHECK TABLE 和 SELECT COUNT() 都是同步的并行读取, 使用 Parallel Read 框架可以考虑针对 SELECT * 的全表扫描可以优化为异步的逻辑预读.</p>
]]></content>
  </entry><entry>
    <title>理解 InnoDB 自适应刷脏</title>
    <link href="https://leviathan.vip/2020/05/19/mysql-understand-adaptive-flushing/" />
    <id>https://leviathan.vip/2020/05/19/mysql-understand-adaptive-flushing/</id>
    <updated>2020-05-19T16:28:09Z</updated>
    <summary type="html"><![CDATA[<h2 id="背景">背景</h2>
<h2 id="adaptive-flushing-分析">adaptive flushing 分析</h2>
<p>InnoDB 采用 adaptive flushing (自适应刷脏)的刷脏策略来处理从 Buffer Pool 写入脏页至磁盘. 如何理解 adaptive flushing 的作用，我们可以假设不采用自适应刷脏策略，我们该如何进行刷脏? 假如没有自适应刷脏算法，我们可以利用阈值的方式来进行刷脏，比如 Buffer Pool 的脏页比例达到了 70% 就触发刷脏，在一般的业务压力下，这个方法没有问题. 但是对于用户业务不确定的场景, 简单的采用阈值的方式容易造成在用户业务压力大的情况下数据库的剧烈抖动. 所以采用自适应的刷脏策略，尽可能在所有的用户场景达到系统平滑运行.</p>
<h3 id="相关参数">相关参数</h3>
<ul>
<li>innodb_flushing_avg_loops: 重新生成刷脏建议的间隔时间, 默认30s.</li>
<li>innodb_adaptive_flushing: 是否打开自适应刷脏.</li>
<li>innodb_adaptive_flushing_lwm:  设置 Redo Log 空闲容量的低水位.</li>
<li>innodb_max_dirty_pages_pct_lwm: 设置一个脏页低水位, 当 InnoDB中 的脏页比例超过 <code>innodb_max_dirty_pages_pct_lwm</code> 的值时, InnoDB 就会触发刷脏.</li>
<li>innodb_max_dirty_pages_pct: 设置一个脏页比例上限，假如脏页比例超过这个值，将会触发激烈刷脏(达到系统 IO 上限).</li>
<li>innodb_io_capacity:  系统 IO 吞吐能力.</li>
<li>innodb_io_capacity_max:  系统最大的 IO 吞吐能力.</li>
</ul>
<h3 id="原理分析">原理分析</h3>
<p>当启用 <code>innodb_max_dirty_pages_pct_lwm</code> 参数时, 表示设置了预刷脏，Buffer Pool 的刷脏线程会避免脏页比超过这个值. 后台刷脏的动作由后台刷脏协调线程触发，该线程的所有工作内容均由 <code>buf_flush_page_cleaner_coordinator()</code> 函数完成. 在执行刷脏任务前，会调用 <code>page_cleaner_flush_pages_recommendation()</code> 生成刷脏建议.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="背景">背景</h2>
<h2 id="adaptive-flushing-分析">adaptive flushing 分析</h2>
<p>InnoDB 采用 adaptive flushing (自适应刷脏)的刷脏策略来处理从 Buffer Pool 写入脏页至磁盘. 如何理解 adaptive flushing 的作用，我们可以假设不采用自适应刷脏策略，我们该如何进行刷脏? 假如没有自适应刷脏算法，我们可以利用阈值的方式来进行刷脏，比如 Buffer Pool 的脏页比例达到了 70% 就触发刷脏，在一般的业务压力下，这个方法没有问题. 但是对于用户业务不确定的场景, 简单的采用阈值的方式容易造成在用户业务压力大的情况下数据库的剧烈抖动. 所以采用自适应的刷脏策略，尽可能在所有的用户场景达到系统平滑运行.</p>
<h3 id="相关参数">相关参数</h3>
<ul>
<li>innodb_flushing_avg_loops: 重新生成刷脏建议的间隔时间, 默认30s.</li>
<li>innodb_adaptive_flushing: 是否打开自适应刷脏.</li>
<li>innodb_adaptive_flushing_lwm:  设置 Redo Log 空闲容量的低水位.</li>
<li>innodb_max_dirty_pages_pct_lwm: 设置一个脏页低水位, 当 InnoDB中 的脏页比例超过 <code>innodb_max_dirty_pages_pct_lwm</code> 的值时, InnoDB 就会触发刷脏.</li>
<li>innodb_max_dirty_pages_pct: 设置一个脏页比例上限，假如脏页比例超过这个值，将会触发激烈刷脏(达到系统 IO 上限).</li>
<li>innodb_io_capacity:  系统 IO 吞吐能力.</li>
<li>innodb_io_capacity_max:  系统最大的 IO 吞吐能力.</li>
</ul>
<h3 id="原理分析">原理分析</h3>
<p>当启用 <code>innodb_max_dirty_pages_pct_lwm</code> 参数时, 表示设置了预刷脏，Buffer Pool 的刷脏线程会避免脏页比超过这个值. 后台刷脏的动作由后台刷脏协调线程触发，该线程的所有工作内容均由 <code>buf_flush_page_cleaner_coordinator()</code> 函数完成. 在执行刷脏任务前，会调用 <code>page_cleaner_flush_pages_recommendation()</code> 生成刷脏建议.</p>
<p>函数 <code>page_cleaner_flush_pages_recommendation()</code> 生成的建议刷脏的 Page 数量是 adaptive flushing 自适应刷脏策略的核心，它每隔 <code>srv_flushing_avg_loops</code> 秒(默认30s)重新根据<strong>redo log产生的速度</strong>，参考<strong>当前刷脏的平均数量</strong>和设置的<strong>系统IO参数(<code>innodb_io_capacity</code>, <code>innodb_io_capacity_max</code>)</strong> 三者的平均值生成一个合理的建议刷脏的Page数量. 下面我们分别对这三个调节因子做出对应的解释.</p>
<h3 id="redo-log-产生的平均速度">Redo Log 产生的平均速度</h3>
<p>因为刷脏协调线程会每隔 <code>srv_flushing_avg_loops</code> 生成一次刷脏建议，关于 Redo Log 产生的平均速度公式即为:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="cl"><span class="cm"> * cur_lsn: 当前最大的lsn
</span></span></span><span class="line"><span class="cl"><span class="cm"> * prev_lsn: 上次记录的lsn
</span></span></span><span class="line"><span class="cl"><span class="cm"> * time_elapsed: 间隔时间
</span></span></span><span class="line"><span class="cl"><span class="cm"> * 计算当前 redo log 的产生速度. */</span>
</span></span><span class="line"><span class="cl"><span class="n">lsn_rate</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">lsn_t</span><span class="o">&gt;</span><span class="p">(</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">cur_lsn</span> <span class="o">-</span> <span class="n">prev_lsn</span><span class="p">)</span> <span class="o">/</span> <span class="n">time_elapsed</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 计算与上次 Redo Log 产生速度的平均值 */</span>
</span></span><span class="line"><span class="cl"><span class="n">lsn_avg_rate</span> <span class="o">=</span> <span class="p">(</span><span class="n">lsn_avg_rate</span> <span class="o">+</span> <span class="n">lsn_rate</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span></span></span></code></pre></div><p>计算 Redo Log 产生的平均速度这个比较好理解，Redo Log 产生的平均速度反应了当前系统的压力情况，压力越大，Redo Log 产生的速度越快. 在 MySQL 中 Redo Log 是复用的，经过 Log Checkpoint 操作之前的 Redo Log 都可以被复用, 所以 Log Checkpoint 本身就会推进 Buffer Pool 的刷脏, 所以为了保证数据库有足够空闲的 Redo Log 空闲, 自适应刷脏同样需要考虑 Redo Log 产生的速度.</p>
<p>InnoDB 根据当前 <code>lsn_avg_rate</code> 来估算一个target_lsn, <code>flush_list</code> 所有 <code>oldest_modification_lsn</code> 小于该 lsn 值的 Page 都被考虑进行刷盘. 估算公式如下:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="n">lsn_t</span> <span class="n">target_lsn</span> <span class="o">=</span> <span class="n">oldest_lsn</span> <span class="o">+</span> <span class="n">lsn_avg_rate</span> <span class="o">*</span> <span class="n">buf_flush_lsn_scan_factor</span><span class="p">;</span></span></span></code></pre></div><p>因子 <code>buf_flush_lsn_scan_factor</code> 被硬编码为3. 由上面的公式 InnoDB 的刷脏协调线程遍历 Buffer Pool 估算出 <code>flush_list</code> 需要被刷脏的 Page 数量, 但最后的数量会再除以 <code>buf_flush_lsn_scan_factor</code>.</p>
<h3 id="当前刷脏的平均速度">当前刷脏的平均速度</h3>
<p>考虑将当前刷脏的平均速度作为影响因子可能的原因应该是避免生成的建议刷脏 Page 数量与上次刷脏的数量差距过大或过小. 这也符合自适应刷脏的初衷，尽力避免IO抖动.</p>
<h3 id="系统io参数">系统IO参数</h3>
<p>通过我们设置的 <code>innodb_io_capacity</code> 和 <code>innodb_io_capacity_max</code> 可以得出系统IO的能力, 通过计算 <code>flush_list</code> 占所有 Page 数量的百分比我们可以得出脏页比. 根据脏页比与 <code>innodb_max_dirty_pages_pct</code> 大小比较我们决定是否触发激烈刷脏, 假如超过了 <code>innodb_max_dirty_pages_pct</code> 设定的大小，我们即认为需要全力进行刷脏了, 这里会充分调动系统的IO能力. 否则则需要与 <code>innodb_max_dirty_pages_pct_lwm</code> 比较，从而考虑利用系统多少的IO带宽.</p>
<p>通过上面的计算，我们将这三个因子的建议刷脏 Page 数量计算平均值，得出综合建议刷脏Page数量，由变量 <code>n_pages</code> 保存. 接下来，这个建议刷新的总量 <code>n_pages</code> 与 <code>innodb_io_capacity_max</code> 这个参数进行比较，即建议刷新的总量最大不能超过所设置的磁盘最大随机IO能力。</p>
<p>最后我们需要为每个 Buffer Pool 设置 <code>n_pages_requested</code>, 即要求的刷脏 Page 数量. 具体的细节我们将在下节的源码分析展出.</p>
<h3 id="源码分析">源码分析</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">static</span> <span class="n">ulint</span> <span class="nf">page_cleaner_flush_pages_recommendation</span><span class="p">(</span><span class="n">lsn_t</span> <span class="o">*</span><span class="n">lsn_limit</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                                     <span class="n">ulint</span> <span class="n">last_pages_in</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* 请注意以下5个变量类型均为static. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">static</span> <span class="n">lsn_t</span> <span class="n">prev_lsn</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">static</span> <span class="n">ulint</span> <span class="n">sum_pages</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">static</span> <span class="n">ulint</span> <span class="n">avg_page_rate</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">static</span> <span class="n">ulint</span> <span class="n">n_iterations</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">static</span> <span class="n">time_t</span> <span class="n">prev_time</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">lsn_t</span> <span class="n">oldest_lsn</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">lsn_t</span> <span class="n">cur_lsn</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">lsn_t</span> <span class="n">age</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">lsn_t</span> <span class="n">lsn_rate</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">n_pages</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">pct_for_dirty</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">pct_for_lsn</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">pct_total</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 当前写入redo log最大的lsn. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">cur_lsn</span> <span class="o">=</span> <span class="n">log_buffer_dirty_pages_added_up_to_lsn</span><span class="p">(</span><span class="o">*</span><span class="n">log_sys</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">prev_lsn</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 第一次进入该函数, 更新prev_lsn, prev_time. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">prev_lsn</span> <span class="o">=</span> <span class="n">cur_lsn</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">prev_time</span> <span class="o">=</span> <span class="n">ut_time</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 假如 prev_lsn 等于 cur_lsn 即没有 Redo Log 产生, 直接返回. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">prev_lsn</span> <span class="o">==</span> <span class="n">cur_lsn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 累计刷脏的 Page 数量, last_pages_in是上次 Flush 的脏页数量. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">sum_pages</span> <span class="o">+=</span> <span class="n">last_pages_in</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">time_t</span> <span class="n">curr_time</span> <span class="o">=</span> <span class="n">ut_time</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="kt">double</span> <span class="n">time_elapsed</span> <span class="o">=</span> <span class="n">difftime</span><span class="p">(</span><span class="n">curr_time</span><span class="p">,</span> <span class="n">prev_time</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 计算是否超过srv_flushing_avg_loops, InnoDB 设置间隔 srv_flushing_avg_loops 生成一次刷脏建议. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">++</span><span class="n">n_iterations</span> <span class="o">&gt;=</span> <span class="n">srv_flushing_avg_loops</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">      <span class="n">time_elapsed</span> <span class="o">&gt;=</span> <span class="n">srv_flushing_avg_loops</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">time_elapsed</span> <span class="o">&lt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">time_elapsed</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 计算刷脏的平均 Page 数量. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">avg_page_rate</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">ulint</span><span class="o">&gt;</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">((</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">sum_pages</span><span class="p">)</span> <span class="o">/</span> <span class="n">time_elapsed</span><span class="p">)</span> <span class="o">+</span> <span class="n">avg_page_rate</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 计算上次 Redo Log 的产生速度. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">lsn_rate</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">lsn_t</span><span class="o">&gt;</span><span class="p">(</span><span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">(</span><span class="n">cur_lsn</span> <span class="o">-</span> <span class="n">prev_lsn</span><span class="p">)</span> <span class="o">/</span>
</span></span><span class="line"><span class="cl">                                  <span class="n">time_elapsed</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 计算 Redo Log 的平均产生速度. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">lsn_avg_rate</span> <span class="o">=</span> <span class="p">(</span><span class="n">lsn_avg_rate</span> <span class="o">+</span> <span class="n">lsn_rate</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 更新 prev_lsn, prev_time. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">prev_lsn</span> <span class="o">=</span> <span class="n">cur_lsn</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">prev_time</span> <span class="o">=</span> <span class="n">curr_time</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">n_iterations</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">sum_pages</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 获取 flush_list 中最老的oldest_modification. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">oldest_lsn</span> <span class="o">=</span> <span class="n">buf_pool_get_oldest_modification_approx</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">oldest_lsn</span> <span class="o">&lt;=</span> <span class="n">log_get_lsn</span><span class="p">(</span><span class="o">*</span><span class="n">log_sys</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 计算lsn的增量. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">age</span> <span class="o">=</span> <span class="n">cur_lsn</span> <span class="o">&gt;</span> <span class="n">oldest_lsn</span> <span class="o">?</span> <span class="n">cur_lsn</span> <span class="o">-</span> <span class="nl">oldest_lsn</span> <span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 计算根据脏页比需要使用 io_capacity 的百分比. 假如超过了 srv_max_buf_pool_modified_pct, 需要使用激烈刷脏即100%. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">pct_for_dirty</span> <span class="o">=</span> <span class="n">af_get_pct_for_dirty</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 计算根据 Redo Log 产生速率需要调动 io_capacity 的百分比. 假如超过了 srv_max_buf_pool_modified_pct, 需要使用激烈刷脏即100%. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">pct_for_lsn</span> <span class="o">=</span> <span class="n">af_get_pct_for_lsn</span><span class="p">(</span><span class="n">age</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 取一个最大值. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">pct_total</span> <span class="o">=</span> <span class="n">ut_max</span><span class="p">(</span><span class="n">pct_for_dirty</span><span class="p">,</span> <span class="n">pct_for_lsn</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">sum_pages_for_lsn</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 下面的for循环即为根据 lsn_avg_rate 估算 Buffer Pool 中的 instance 刷脏目标 Page 数量. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">lsn_t</span> <span class="n">target_lsn</span> <span class="o">=</span> <span class="n">oldest_lsn</span> <span class="o">+</span> <span class="n">lsn_avg_rate</span> <span class="o">*</span> <span class="n">buf_flush_lsn_scan_factor</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="n">ulint</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">srv_buf_pool_instances</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf_pool_t</span> <span class="o">*</span><span class="n">buf_pool</span> <span class="o">=</span> <span class="n">buf_pool_from_array</span><span class="p">(</span><span class="n">i</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">ulint</span> <span class="n">pages_for_lsn</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 遍历 Buffer Pool 中的 instance 的 flush_list, 根据符合 target_lsn 的 Page, 递增 pages_for_lsn */</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf_flush_list_mutex_enter</span><span class="p">(</span><span class="n">buf_pool</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="n">buf_page_t</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="n">UT_LIST_GET_LAST</span><span class="p">(</span><span class="n">buf_pool</span><span class="o">-&gt;</span><span class="n">flush_list</span><span class="p">);</span> <span class="n">b</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">         <span class="n">b</span> <span class="o">=</span> <span class="n">UT_LIST_GET_PREV</span><span class="p">(</span><span class="n">list</span><span class="p">,</span> <span class="n">b</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="n">b</span><span class="o">-&gt;</span><span class="n">oldest_modification</span> <span class="o">&gt;</span> <span class="n">target_lsn</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="o">++</span><span class="n">pages_for_lsn</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="n">buf_flush_list_mutex_exit</span><span class="p">(</span><span class="n">buf_pool</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">sum_pages_for_lsn</span> <span class="o">+=</span> <span class="n">pages_for_lsn</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">mutex_enter</span><span class="p">(</span><span class="o">&amp;</span><span class="n">page_cleaner</span><span class="o">-&gt;</span><span class="n">mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">ut_ad</span><span class="p">(</span><span class="n">page_cleaner</span><span class="o">-&gt;</span><span class="n">slots</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">state</span> <span class="o">==</span> <span class="n">PAGE_CLEANER_STATE_NONE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 更新page_cleaner的n_pages_requested, 除以 buf_flush_lsn_scan_factor 的原因是之前计算
</span></span></span><span class="line"><span class="cl"><span class="cm">     * target_lsn的时候乘以了 buf_flush_lsn_scan_factor 因子. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">page_cleaner</span><span class="o">-&gt;</span><span class="n">slots</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">n_pages_requested</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">        <span class="n">pages_for_lsn</span> <span class="o">/</span> <span class="n">buf_flush_lsn_scan_factor</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutex_exit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">page_cleaner</span><span class="o">-&gt;</span><span class="n">mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* sum_pages_for 是根据 lsn_avg_rate 估算的全局刷脏 Page 总的数量. 这里除以 buf_flush_lsn_scan_factor 因子即恢复.*/</span>
</span></span><span class="line"><span class="cl">  <span class="n">sum_pages_for_lsn</span> <span class="o">/=</span> <span class="n">buf_flush_lsn_scan_factor</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">sum_pages_for_lsn</span> <span class="o">&lt;</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">sum_pages_for_lsn</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* Cap the maximum IO capacity that we are going to use by
</span></span></span><span class="line"><span class="cl"><span class="cm">  max_io_capacity. Limit the value to avoid too quick increase */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">pages_for_lsn</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">      <span class="n">std</span><span class="o">::</span><span class="n">min</span><span class="o">&lt;</span><span class="n">ulint</span><span class="o">&gt;</span><span class="p">(</span><span class="n">sum_pages_for_lsn</span><span class="p">,</span> <span class="n">srv_max_io_capacity</span> <span class="o">*</span> <span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 根据 srv_io_capacity、历次 flush 脏页的平均数量和 redo log 产生速度需要 flush 的 Page 数量三者的平均值.
</span></span></span><span class="line"><span class="cl"><span class="cm">   * pct_total 代表根据脏页比 和 redo log 产生的速率来决定使用多大的 IO 吞吐. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">n_pages</span> <span class="o">=</span> <span class="p">(</span><span class="n">PCT_IO</span><span class="p">(</span><span class="n">pct_total</span><span class="p">)</span> <span class="o">+</span> <span class="n">avg_page_rate</span> <span class="o">+</span> <span class="n">pages_for_lsn</span><span class="p">)</span> <span class="o">/</span> <span class="mi">3</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">n_pages</span> <span class="o">&gt;</span> <span class="n">srv_max_io_capacity</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* n_pages不能超过设置的srv_max_io_capacity. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">n_pages</span> <span class="o">=</span> <span class="n">srv_max_io_capacity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">mutex_enter</span><span class="p">(</span><span class="o">&amp;</span><span class="n">page_cleaner</span><span class="o">-&gt;</span><span class="n">mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(</span><span class="n">ulint</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">srv_buf_pool_instances</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 为每一个page_cleanr设置刷脏的目标数量:
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 1. 假如 pct_for_lsn 超过了 30，这里可以理解为 Buffer Pool 的 instance 存在 flush_list 中还有较旧的脏页,
</span></span></span><span class="line"><span class="cl"><span class="cm">          因此根据之前计算的 n_pages_requested, 从而使存在较旧脏页的 instance 刷更多的脏页, 所以这里的脏页数量分配并不是均匀的.
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 2. 否则采用平均分配的方法直接分配给各个page_cleaner. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">page_cleaner</span><span class="o">-&gt;</span><span class="n">slots</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">n_pages_requested</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">        <span class="n">pct_for_lsn</span> <span class="o">&gt;</span> <span class="mi">30</span> <span class="o">?</span> <span class="n">page_cleaner</span><span class="o">-&gt;</span><span class="n">slots</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">n_pages_requested</span> <span class="o">*</span> <span class="n">n_pages</span> <span class="o">/</span>
</span></span><span class="line"><span class="cl">                                   <span class="n">sum_pages_for_lsn</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">                               <span class="mi">1</span>
</span></span><span class="line"><span class="cl">                         <span class="o">:</span> <span class="n">n_pages</span> <span class="o">/</span> <span class="n">srv_buf_pool_instances</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="n">mutex_exit</span><span class="p">(</span><span class="o">&amp;</span><span class="n">page_cleaner</span><span class="o">-&gt;</span><span class="n">mutex</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">  <span class="o">*</span><span class="n">lsn_limit</span> <span class="o">=</span> <span class="n">LSN_MAX</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 返回根据自适应刷脏生成的刷脏数量建议. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="n">n_pages</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="总结">总结</h3>
<p>InnoDB 的自适应刷脏比较容易理解，重要的是提供了一种对于系统开发过程中对于容易造成性能瓶颈的关键路径优化思路，例如基于 LSM 设计的 RocksDB 中的 compaction 过程经常造成IO瓶颈从而饱受诟病，参考 InnoDB 的自适应刷脏算法针对不同的IO压力选择合适的 compaction 时机是否能使系统更平滑?</p>
]]></content>
  </entry><entry>
    <title>理解 InnoDB 的 simulated AIO</title>
    <link href="https://leviathan.vip/2020/03/24/mysql-understand-simulated-aio/" />
    <id>https://leviathan.vip/2020/03/24/mysql-understand-simulated-aio/</id>
    <updated>2020-03-24T14:30:07Z</updated>
    <summary type="html"><![CDATA[<h2 id="准备">准备</h2>
<p><strong>MySQL内核版本: 8.0.19</strong></p>
<h2 id="simulated-aio">simulated-AIO</h2>
<p>simulated-AIO 是一套由 InnoDB 早先实现的异步 I/O 模型. 在 MySQL 的存储引擎 InnoDB 中分别实现了同步IO以及异步IO, Redo Log 的写入方式采用同步IO, 而数据页的写入由于 Redo Log 的保护则采用异步 IO 的写入方式. 在 Linux AIO 引入之前, InnoDB 实现了一套异步 IO 框架, 即 simulated-AIO. simulated-AIO 的原理类似于 libaio, 原理实现都较为简单.</p>
<p>在Linux平台, 假如安装了 libaio, MySQL 是默认使用 libaio, 只有在设置了 <code>innodb_use_native_aio = 0</code> 的情况下才会使用 simulated-AIO.</p>
<p>InnoDB的异步IO主要是用来处理预读和数据Page的写请求，对于正常Page的数据读取则是通过同步 IO 进行.</p>
<h2 id="simulated-aio-原理">simulated-AIO 原理</h2>
<h3 id="数据结构">数据结构</h3>
<p>simulated-AIO 预分配 n 个大小 slot 数组, 每个用户的读写请求通过申请数组中的 slot, 构造对应的 IO 类型、写入 offset 等等. 而 simulated-AIO 的工作线程则根据slot的内容来完成对应的 IO 请求.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="准备">准备</h2>
<p><strong>MySQL内核版本: 8.0.19</strong></p>
<h2 id="simulated-aio">simulated-AIO</h2>
<p>simulated-AIO 是一套由 InnoDB 早先实现的异步 I/O 模型. 在 MySQL 的存储引擎 InnoDB 中分别实现了同步IO以及异步IO, Redo Log 的写入方式采用同步IO, 而数据页的写入由于 Redo Log 的保护则采用异步 IO 的写入方式. 在 Linux AIO 引入之前, InnoDB 实现了一套异步 IO 框架, 即 simulated-AIO. simulated-AIO 的原理类似于 libaio, 原理实现都较为简单.</p>
<p>在Linux平台, 假如安装了 libaio, MySQL 是默认使用 libaio, 只有在设置了 <code>innodb_use_native_aio = 0</code> 的情况下才会使用 simulated-AIO.</p>
<p>InnoDB的异步IO主要是用来处理预读和数据Page的写请求，对于正常Page的数据读取则是通过同步 IO 进行.</p>
<h2 id="simulated-aio-原理">simulated-AIO 原理</h2>
<h3 id="数据结构">数据结构</h3>
<p>simulated-AIO 预分配 n 个大小 slot 数组, 每个用户的读写请求通过申请数组中的 slot, 构造对应的 IO 类型、写入 offset 等等. 而 simulated-AIO 的工作线程则根据slot的内容来完成对应的 IO 请求.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/** The asynchronous I/O context */</span>
</span></span><span class="line"><span class="cl"><span class="cm">/** 异步 IO 请求单元 */</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">Slot</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/** 在 array 中的下标 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">uint16_t</span> <span class="n">pos</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 是否已被申请分配 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">bool</span> <span class="n">is_reserved</span><span class="p">{</span><span class="nb">false</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 已被分配的时间长度 */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ib_time_monotonic_t</span> <span class="n">reservation_time</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** buffer used in i/o */</span>
</span></span><span class="line"><span class="cl">  <span class="n">byte</span> <span class="o">*</span><span class="n">buf</span><span class="p">{</span><span class="k">nullptr</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** Buffer pointer used for actual IO. We advance this
</span></span></span><span class="line"><span class="cl"><span class="cm">  when partial IO is required and not buf */</span>
</span></span><span class="line"><span class="cl">  <span class="n">byte</span> <span class="o">*</span><span class="n">ptr</span><span class="p">{</span><span class="k">nullptr</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** IO 类型 OS_FILE_READ or OS_FILE_WRITE */</span>
</span></span><span class="line"><span class="cl">  <span class="n">IORequest</span> <span class="n">type</span><span class="p">{</span><span class="n">IORequest</span><span class="o">::</span><span class="n">UNSET</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 在文件中的偏移量 */</span>
</span></span><span class="line"><span class="cl">  <span class="n">os_offset_t</span> <span class="n">offset</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 文件描述符 */</span>
</span></span><span class="line"><span class="cl">  <span class="n">pfs_os_file_t</span> <span class="n">file</span><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef UNIV_PFS_IO
</span></span></span><span class="line"><span class="cl">      <span class="k">nullptr</span><span class="p">,</span>  <span class="c1">// m_psi
</span></span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span><span class="line"><span class="cl">      <span class="mi">0</span>  <span class="c1">// m_file
</span></span></span><span class="line"><span class="cl">  <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 文件名 */</span>
</span></span><span class="line"><span class="cl">  <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">{</span><span class="k">nullptr</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** IO 是否已经完成 */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">bool</span> <span class="n">io_already_done</span><span class="p">{</span><span class="nb">false</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** fil_node_t 节点 参考 Fil_system */</span>
</span></span><span class="line"><span class="cl">  <span class="n">fil_node_t</span> <span class="o">*</span><span class="n">m1</span><span class="p">{</span><span class="k">nullptr</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** the requester of an aio operation and which can be used
</span></span></span><span class="line"><span class="cl"><span class="cm">  to identify which pending aio operation was completed */</span>
</span></span><span class="line"><span class="cl">  <span class="kt">void</span> <span class="o">*</span><span class="n">m2</span><span class="p">{</span><span class="k">nullptr</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** AIO 状态 */</span>
</span></span><span class="line"><span class="cl">  <span class="n">dberr_t</span> <span class="n">err</span><span class="p">{</span><span class="n">DB_ERROR_UNSET</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 读写的 block 长度 */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">len</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 读写字节数 */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">n_bytes</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** 读写的 block 压缩前的长度 */</span>
</span></span><span class="line"><span class="cl">  <span class="n">uint32</span> <span class="n">original_len</span><span class="p">{</span><span class="mi">0</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** block */</span>
</span></span><span class="line"><span class="cl">  <span class="n">Block</span> <span class="o">*</span><span class="n">buf_block</span><span class="p">{</span><span class="k">nullptr</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>simulated-AIO 原理非常简单，可以理解为一个生产者-消费者模型, 示意图如下:</p>
<p><img src="/images/simulated-aio.png" alt="simulated_aio"></p>
<h3 id="生产者用户读写流程">生产者(用户读写流程)</h3>
<ul>
<li><code>buf_page_get_gen()</code>(预读):</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"> <span class="cm">/* 获取数据页 */</span>
</span></span><span class="line"><span class="cl"> <span class="o">--------------------</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="n">buf_page_get_gen</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">--------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">---------------------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">Buf_fetch_normal</span><span class="o">::</span><span class="n">single_page</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">       <span class="o">---------------------------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="cm">/* 调用线性预读 */</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">buf_read_ahead_linear</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">              <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl">               <span class="o">|</span>
</span></span><span class="line"><span class="cl">               <span class="o">|</span>   <span class="cm">/* 读Page */</span>
</span></span><span class="line"><span class="cl">               <span class="o">|</span>   <span class="o">---------------------</span>
</span></span><span class="line"><span class="cl">               <span class="o">-&gt;</span> <span class="o">|</span> <span class="n">buf_read_page_low</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                   <span class="o">---------------------</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|</span>    <span class="cm">/* 文件读写操作 */</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|</span>    <span class="o">----------</span>
</span></span><span class="line"><span class="cl">                    <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">fil_io</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                         <span class="o">----------</span>
</span></span><span class="line"><span class="cl">                          <span class="o">|</span>
</span></span><span class="line"><span class="cl">                          <span class="o">|</span>    <span class="o">----------------</span>
</span></span><span class="line"><span class="cl">                          <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">shard</span><span class="o">-&gt;</span><span class="n">do_io</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                               <span class="o">----------------</span>
</span></span><span class="line"><span class="cl">                                <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                <span class="o">|</span>   <span class="cm">/* 异步 IO 接口 */</span>
</span></span><span class="line"><span class="cl">                                <span class="o">|</span>    <span class="o">----------</span>
</span></span><span class="line"><span class="cl">                                <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">os_aio</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                                     <span class="o">----------</span></span></span></code></pre></div><ul>
<li><code>buf_flush_page()</code>(写):</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"> <span class="cm">/* 刷 Page 至文件 */</span>
</span></span><span class="line"><span class="cl"> <span class="o">------------------</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="n">buf_flush_page</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="cm">/* 刷 Page */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">-----------------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">buf_flush_write_block_low</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">       <span class="o">-----------------------------</span>
</span></span><span class="line"><span class="cl">        <span class="o">|</span>
</span></span><span class="line"><span class="cl">        <span class="o">|</span>    <span class="o">----------</span>
</span></span><span class="line"><span class="cl">        <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">fil_io</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">             <span class="o">----------</span>
</span></span><span class="line"><span class="cl">              <span class="o">|</span>
</span></span><span class="line"><span class="cl">              <span class="o">|</span>    <span class="o">----------------</span>
</span></span><span class="line"><span class="cl">              <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">shard</span><span class="o">-&gt;</span><span class="n">do_io</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                   <span class="o">----------------</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|</span>    <span class="cm">/* 异步IO接口 */</span>
</span></span><span class="line"><span class="cl">                    <span class="o">|</span>    <span class="o">----------</span>
</span></span><span class="line"><span class="cl">                    <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">os_aio</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                         <span class="o">----------</span></span></span></code></pre></div><p>无论是读操作还是写操作，都要交由 <code>os_aio()</code> 处理, <code>os_aio</code> 是一个通用的接口, 在Linux平台封装了 libaio 和 simulated AIO. 具体的处理逻辑如下:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"> <span class="o">----------</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="n">os_aio</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">----------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="cm">/* 申请 slot */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">---------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">AIO</span><span class="o">::</span><span class="n">reserve_slot</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>     <span class="o">--------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="cm">/* 唤醒 simulated-AIO 后台处理线程 */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">--------------------------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">AIO</span><span class="o">::</span><span class="n">wake_simulated_handler_thread</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">       <span class="o">--------------------------------------</span></span></span></code></pre></div><ul>
<li>
<p>根据IO类型选择对应的 I/O slot 数组(<code>select_slot_array())</code>.</p>
</li>
<li>
<p>向 I/O slot 数组申请 slot (<code>reserve_slot()</code>).</p>
</li>
<li>
<p>唤醒对应的异步IO线程处理IO请求(<code>AIO::wake_simulated_handler_thread()</code>).</p>
</li>
</ul>
<h3 id="消费者异步io处理流程">消费者(异步I/O处理流程)</h3>
<p>在MySQL启动时，会分别创建1个ibuf处理线程, 1个log处理线程, n个(<code>srv_n_read_io_threads</code>)读处理线程, n个(<code>srv_n_write_io_threads</code>)写处理线程.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="cm">/* DB启动 */</span>
</span></span><span class="line"><span class="cl"> <span class="o">-------------</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="n">srv_start</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">-------------</span>
</span></span><span class="line"><span class="cl">     <span class="o">|</span>
</span></span><span class="line"><span class="cl">     <span class="o">|</span>    <span class="cm">/* 根据 srv_n_file_io_threads 参数创建 IO 处理线程 */</span>
</span></span><span class="line"><span class="cl">     <span class="o">|</span>    <span class="o">---------------------</span>
</span></span><span class="line"><span class="cl">     <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">io_handler_thread</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="o">---------------------</span>
</span></span><span class="line"><span class="cl">            <span class="o">|</span>
</span></span><span class="line"><span class="cl">            <span class="o">|</span>   <span class="cm">/* 监控异步 IO 请求 */</span>
</span></span><span class="line"><span class="cl">            <span class="o">|</span>   <span class="o">----------------</span>
</span></span><span class="line"><span class="cl">            <span class="o">-&gt;</span> <span class="o">|</span> <span class="n">fil_aio_wait</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                <span class="o">----------------</span>
</span></span><span class="line"><span class="cl">                  <span class="o">|</span>
</span></span><span class="line"><span class="cl">                  <span class="o">|</span>    <span class="cm">/* 根据设定的 AIO mode 选择不同的AIO处理函数 */</span>
</span></span><span class="line"><span class="cl">                  <span class="o">|</span>    <span class="o">------------------</span>
</span></span><span class="line"><span class="cl">                  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">os_aio_handler</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                       <span class="o">------------------</span>
</span></span><span class="line"><span class="cl">                         <span class="o">|</span>
</span></span><span class="line"><span class="cl">                         <span class="o">|</span>    <span class="cm">/* simulated-AIO 负责处理异步IO的函数 */</span>
</span></span><span class="line"><span class="cl">                         <span class="o">|</span>    <span class="o">----------------------------</span>
</span></span><span class="line"><span class="cl">                         <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">os_aio_simulated_handler</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                         <span class="o">|</span>     <span class="o">----------------------------</span>
</span></span><span class="line"><span class="cl">                         <span class="o">|</span>
</span></span><span class="line"><span class="cl">                         <span class="o">|</span>    <span class="cm">/* 异步 IO 完成后的清理工作 */</span>
</span></span><span class="line"><span class="cl">                         <span class="o">|</span>    <span class="o">------------------------</span>
</span></span><span class="line"><span class="cl">                         <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">buf_page_io_complete</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">                              <span class="o">------------------------</span></span></span></code></pre></div><p><code>io_handler_thread()</code> 会持续监控 IO 请求，直到 MySQL shutdown:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* storage/innobase/srv/srv0start.cc */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">io_handler_thread</span><span class="p">(</span><span class="n">ulint</span> <span class="n">segment</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">srv_shutdown_state</span><span class="p">.</span><span class="n">load</span><span class="p">()</span> <span class="o">!=</span> <span class="n">SRV_SHUTDOWN_EXIT_THREADS</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">                    <span class="n">buf_flush_page_cleaner_is_active</span><span class="p">()</span> <span class="o">||</span> <span class="o">!</span><span class="n">os_aio_all_slots_free</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="n">fil_aio_wait</span><span class="p">(</span><span class="n">segment</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>fil_aio_wait()</code> 会调用 <code>os_aio_handler()</code> 根据不同的IO模型选择不同的函数处理IO请求, simulated AIO 的处理函数是 <code>os_aio_simulated_handler()</code>:</p>
<ol>
<li>
<p>根据 global segment id 选择对应I/O工作线程的event, 计算在该array的segment id.</p>
</li>
<li>
<p>检查是否有已经完成但状态尚未更新的IO请求:</p>
</li>
</ol>
<ul>
<li>假如存在已经完成但状态尚未更新的IO请求, 则调用 <code>AIO::release()</code> 更新slot状态.</li>
</ul>
<ol start="3">
<li>
<p>需要判断是否MySQL准备shutdown, 假如需要shutdown则立即返回.</p>
</li>
<li>
<p>否则从 <code>AIO::m_slots</code> 选择等待的IO请求:</p>
</li>
</ol>
<ul>
<li>
<p>选择策略是先选择一个等待时间超过2s的IO请求, 防止等待时间过长.</p>
</li>
<li>
<p>否则选择写入偏移量最小的一个slot.</p>
</li>
</ul>
<ol start="5">
<li>
<p>假如目前没有待处理的IO请求，则进入wait状态.</p>
</li>
<li>
<p>处理选中的IO请求前，会调用 <code>merge()</code> 进行IO合并, 选择文件偏移量offset连续的IO请求进行合并.</p>
</li>
<li>
<p>调用 simulated-AIO 封装的同步IO接口(<code>pwrite()</code>/<code>pread()</code>)完成IO操作.</p>
</li>
</ol>
<h3 id="源码分析">源码分析</h3>
<p>核心处理函数 <code>os_aio_simulated_handler()</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* storage/innobase/os/os0file.cc */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* 参数解释: 
</span></span></span><span class="line"><span class="cl"><span class="cm">  global_segment:
</span></span></span><span class="line"><span class="cl"><span class="cm">  m1:
</span></span></span><span class="line"><span class="cl"><span class="cm">  m2:
</span></span></span><span class="line"><span class="cl"><span class="cm">  type: 
</span></span></span><span class="line"><span class="cl"><span class="cm">*/</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="n">dberr_t</span> <span class="nf">os_aio_simulated_handler</span><span class="p">(</span><span class="n">ulint</span> <span class="n">global_segment</span><span class="p">,</span> <span class="n">fil_node_t</span> <span class="o">**</span><span class="n">m1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                        <span class="kt">void</span> <span class="o">**</span><span class="n">m2</span><span class="p">,</span> <span class="n">IORequest</span> <span class="o">*</span><span class="n">type</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">Slot</span> <span class="o">*</span><span class="n">slot</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">AIO</span> <span class="o">*</span><span class="n">array</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">segment</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">os_event_t</span> <span class="n">event</span> <span class="o">=</span> <span class="n">os_aio_segment_wait_events</span><span class="p">[</span><span class="n">global_segment</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 计算对应的子segment */</span>
</span></span><span class="line"><span class="cl">  <span class="n">segment</span> <span class="o">=</span> <span class="n">AIO</span><span class="o">::</span><span class="n">get_array_and_local_segment</span><span class="p">(</span><span class="o">&amp;</span><span class="n">array</span><span class="p">,</span> <span class="n">global_segment</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 构造 simulated-AIO 的 handler */</span>
</span></span><span class="line"><span class="cl">  <span class="n">SimulatedAIOHandler</span> <span class="n">handler</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">segment</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">srv_set_io_thread_op_info</span><span class="p">(</span><span class="n">global_segment</span><span class="p">,</span> <span class="s">&#34;looking for i/o requests (a)&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 检查目前的 slots 数量 */</span>
</span></span><span class="line"><span class="cl">    <span class="n">ulint</span> <span class="n">n_slots</span> <span class="o">=</span> <span class="n">handler</span><span class="p">.</span><span class="n">check_pending</span><span class="p">(</span><span class="n">global_segment</span><span class="p">,</span> <span class="n">event</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">n_slots</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 初始化 handler */</span>
</span></span><span class="line"><span class="cl">    <span class="n">handler</span><span class="p">.</span><span class="n">init</span><span class="p">(</span><span class="n">n_slots</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">srv_set_io_thread_op_info</span><span class="p">(</span><span class="n">global_segment</span><span class="p">,</span> <span class="s">&#34;looking for i/o requests (b)&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">array</span><span class="o">-&gt;</span><span class="n">acquire</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ulint</span> <span class="n">n_reserved</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 检查是否有已经完成但状态尚未更新的IO请求 */</span>
</span></span><span class="line"><span class="cl">    <span class="n">slot</span> <span class="o">=</span> <span class="n">handler</span><span class="p">.</span><span class="n">check_completed</span><span class="p">(</span><span class="o">&amp;</span><span class="n">n_reserved</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">slot</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 存在已完成但状态未更新的slot */</span>
</span></span><span class="line"><span class="cl">      <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">n_reserved</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl"><span class="cp">#ifndef UNIV_HOTBACKUP
</span></span></span><span class="line"><span class="cl">               <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">buf_flush_page_cleaner_is_active</span><span class="p">()</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">               <span class="n">srv_shutdown_state</span><span class="p">.</span><span class="n">load</span><span class="p">()</span> <span class="o">==</span> <span class="n">SRV_SHUTDOWN_EXIT_THREADS</span>
</span></span><span class="line"><span class="cl"><span class="cp">#endif </span><span class="cm">/* !UNIV_HOTBACKUP */</span><span class="cp">
</span></span></span><span class="line"><span class="cl">    <span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* 目前没有待处理的 IO 请求，并且 MySQL 准备 shutdown, 则返回 */</span>
</span></span><span class="line"><span class="cl">      <span class="n">array</span><span class="o">-&gt;</span><span class="n">release</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="o">*</span><span class="n">m1</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="o">*</span><span class="n">m2</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="p">(</span><span class="n">DB_SUCCESS</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">handler</span><span class="p">.</span><span class="n">select</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 否则根据 slot 选择策略，选择对应的 slot */</span>
</span></span><span class="line"><span class="cl">      <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 假如目前没有待处理的IO请求，则进入wait状态 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">srv_set_io_thread_op_info</span><span class="p">(</span><span class="n">global_segment</span><span class="p">,</span> <span class="s">&#34;resetting wait event&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* We wait here until tbere are more IO requests
</span></span></span><span class="line"><span class="cl"><span class="cm">    for this segment. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">os_event_reset</span><span class="p">(</span><span class="n">event</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">array</span><span class="o">-&gt;</span><span class="n">release</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">srv_set_io_thread_op_info</span><span class="p">(</span><span class="n">global_segment</span><span class="p">,</span> <span class="s">&#34;waiting for i/o request&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">os_event_wait</span><span class="p">(</span><span class="n">event</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/** Found a slot that has already completed its IO */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">slot</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* slot == NULL 代表所有已完成的 slot 状态都已经更新，并且我们通过
</span></span></span><span class="line"><span class="cl"><span class="cm">     * select() 选择了合适的 slot 需要完成 I/O 处理 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 合并 I/O 操作 */</span>
</span></span><span class="line"><span class="cl">    <span class="n">handler</span><span class="p">.</span><span class="n">merge</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">srv_set_io_thread_op_info</span><span class="p">(</span><span class="n">global_segment</span><span class="p">,</span> <span class="s">&#34;consecutive i/o requests&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">array</span><span class="o">-&gt;</span><span class="n">release</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">srv_set_io_thread_op_info</span><span class="p">(</span><span class="n">global_segment</span><span class="p">,</span> <span class="s">&#34;doing file i/o&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* IO 操作(pwrite()/pread()) */</span>
</span></span><span class="line"><span class="cl">    <span class="n">handler</span><span class="p">.</span><span class="n">io</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">srv_set_io_thread_op_info</span><span class="p">(</span><span class="n">global_segment</span><span class="p">,</span> <span class="s">&#34;file i/o done&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* simulated-AIO 中 io_complete() 为空实现 */</span>
</span></span><span class="line"><span class="cl">    <span class="n">handler</span><span class="p">.</span><span class="n">io_complete</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">array</span><span class="o">-&gt;</span><span class="n">acquire</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 设置 slot-&gt;io_already_done = true 即表示已完成，但其他状态尚未更新, 交由下次
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 循环更新其他状态 */</span>
</span></span><span class="line"><span class="cl">    <span class="n">handler</span><span class="p">.</span><span class="n">done</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 返回 handler 的第一个 slot */</span>
</span></span><span class="line"><span class="cl">    <span class="n">slot</span> <span class="o">=</span> <span class="n">handler</span><span class="p">.</span><span class="n">first_slot</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 更新 slot 的状态 */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">slot</span><span class="o">-&gt;</span><span class="n">is_reserved</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="o">*</span><span class="n">m1</span> <span class="o">=</span> <span class="n">slot</span><span class="o">-&gt;</span><span class="n">m1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="o">*</span><span class="n">m2</span> <span class="o">=</span> <span class="n">slot</span><span class="o">-&gt;</span><span class="n">m2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="o">*</span><span class="n">type</span> <span class="o">=</span> <span class="n">slot</span><span class="o">-&gt;</span><span class="n">type</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">array</span><span class="o">-&gt;</span><span class="n">release</span><span class="p">(</span><span class="n">slot</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">array</span><span class="o">-&gt;</span><span class="n">release</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="n">DB_SUCCESS</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h2 id="q--a">Q &amp; A</h2>
<ul>
<li>关于 simulated AIO 多个线程同时写入一个文件的问题?</li>
</ul>
<p>simulated AIO 不能保证多线程同时写一个文件, 但 simulated AIO 底层调用的文件接口是 pwrite(), 通过指定参数 offset, 以及每次写的时候加上 Page 锁, 就能保证不写在同一个 offset.</p>
<h2 id="总结">总结</h2>
<p>综上所述，通过源码分析我们详细的了解 MySQL 实现的模拟异步 I/O 的框架, 原理非常简单，由用户线程获取 slot 并记录相关的 I/O 信息，而 simulated-AIO 的后台工作线程则通过一定的策略来逐一处理 I/O 请求, 并且通过合并 I/O 的策略来对 I/O 读写做了一些优化.</p>
]]></content>
  </entry><entry>
    <title>MySQL 死锁检测源码分析</title>
    <link href="https://leviathan.vip/2020/02/02/mysql-deadlock-check/" />
    <id>https://leviathan.vip/2020/02/02/mysql-deadlock-check/</id>
    <updated>2020-02-02T13:51:05Z</updated>
    <summary type="html"><![CDATA[<h1 id="准备">准备</h1>
<p><strong>MySQL 内核版本: 8.0.17</strong></p>
<p>在MySQL中，当两个或两个以上的事务相互持有或者请求锁，并形成一个循环的依赖关系，就会产生死锁. 多个事务同时锁定同一个资源时，也会产生死锁. 在一个事务系统中，死锁是确切存在并且是不能完全避免的. InnoDB 会在每一个事务申请锁时触发死锁检测，并选择一个事务回滚.</p>
<p>在 MySQL 中，事务在申请 record lock 后假如无法立即获取锁会进行死锁检测. 在事务的回滚中，会释放该事务持有的所有 lock.</p>
<p>用户可以配置 <code>--innodb-deadlock-detect[={OFF|ON}]</code> 选择是否打开死锁检测.</p>
<h2 id="死锁检测">死锁检测</h2>
<p>我们从源码层面分析 MySQL 的死锁检测机制，直接通过源码分析可以更直观的介绍死锁检测机制. MySQL 的死锁检测算法是深度优先搜索，如果在搜索过程中发现了环，就说明发生了死锁. 为了避免死锁检测开销过大，如果搜索深度超过了 200（LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK)也同样认为发生了死锁。</p>
<p>基本的代码流程如下, <code>add_to_waitq()</code> 是申请 Record Lock 的入口函数:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* storage/innobase/lock/lock0lock.cc */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">add_to_waitq</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="cm">/* 创建 lock. */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">-------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">create</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">-------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>    <span class="cm">/* 分配 lock, 初始化 lock_t. */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>    <span class="o">-----------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">lock_alloc</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>    <span class="o">-----------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>    <span class="cm">/* 插入 lock_sys-&gt;rec_hash. */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>    <span class="o">---------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">lock_add</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>           <span class="o">---------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="cm">/* 假如事务的优先级较高，尝试跳过低优先级的事务直接获取 lock. */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">-----------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">jump_queue</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">-----------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>   <span class="cm">/* 否则需要进行死锁检测. */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">---------------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">deadlock_check</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">       <span class="o">---------------------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="cm">/* 死锁检测，假如存在死锁返回一个需要被回滚的事务. */</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="o">--------------------------------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">DeadlockChecker</span><span class="o">::</span><span class="n">check_and_resolve</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="o">--------------------------------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>   
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="cm">/* 检查死锁检测的结果. */</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="o">----------------------------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">check_deadlock_result</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">              <span class="o">----------------------------------</span></span></span></code></pre></div><p>死锁检测的主流程代码在 <code>DeadlockChecker::check_and_resolve()</code>:</p>]]></summary>
    <content type="html"><![CDATA[<h1 id="准备">准备</h1>
<p><strong>MySQL 内核版本: 8.0.17</strong></p>
<p>在MySQL中，当两个或两个以上的事务相互持有或者请求锁，并形成一个循环的依赖关系，就会产生死锁. 多个事务同时锁定同一个资源时，也会产生死锁. 在一个事务系统中，死锁是确切存在并且是不能完全避免的. InnoDB 会在每一个事务申请锁时触发死锁检测，并选择一个事务回滚.</p>
<p>在 MySQL 中，事务在申请 record lock 后假如无法立即获取锁会进行死锁检测. 在事务的回滚中，会释放该事务持有的所有 lock.</p>
<p>用户可以配置 <code>--innodb-deadlock-detect[={OFF|ON}]</code> 选择是否打开死锁检测.</p>
<h2 id="死锁检测">死锁检测</h2>
<p>我们从源码层面分析 MySQL 的死锁检测机制，直接通过源码分析可以更直观的介绍死锁检测机制. MySQL 的死锁检测算法是深度优先搜索，如果在搜索过程中发现了环，就说明发生了死锁. 为了避免死锁检测开销过大，如果搜索深度超过了 200（LOCK_MAX_DEPTH_IN_DEADLOCK_CHECK)也同样认为发生了死锁。</p>
<p>基本的代码流程如下, <code>add_to_waitq()</code> 是申请 Record Lock 的入口函数:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* storage/innobase/lock/lock0lock.cc */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">add_to_waitq</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl"> <span class="o">-------------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="cm">/* 创建 lock. */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">-------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">create</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">-------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>    <span class="cm">/* 分配 lock, 初始化 lock_t. */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>    <span class="o">-----------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">lock_alloc</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>    <span class="o">-----------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>    <span class="cm">/* 插入 lock_sys-&gt;rec_hash. */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">|</span>    <span class="o">---------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>      <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">lock_add</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>           <span class="o">---------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="cm">/* 假如事务的优先级较高，尝试跳过低优先级的事务直接获取 lock. */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">-----------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">jump_queue</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">-----------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>   <span class="cm">/* 否则需要进行死锁检测. */</span>
</span></span><span class="line"><span class="cl">  <span class="o">|</span>    <span class="o">---------------------------</span>
</span></span><span class="line"><span class="cl">  <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">deadlock_check</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">       <span class="o">---------------------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="cm">/* 死锁检测，假如存在死锁返回一个需要被回滚的事务. */</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="o">--------------------------------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">DeadlockChecker</span><span class="o">::</span><span class="n">check_and_resolve</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="o">--------------------------------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>   
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="cm">/* 检查死锁检测的结果. */</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>    <span class="o">----------------------------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">--&gt;</span> <span class="o">|</span> <span class="n">RecLock</span><span class="o">::</span><span class="n">check_deadlock_result</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">              <span class="o">----------------------------------</span></span></span></code></pre></div><p>死锁检测的主流程代码在 <code>DeadlockChecker::check_and_resolve()</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* storage/innobase/lock/lock0lock.cc */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* lock: 当前事务申请的 lock
</span></span></span><span class="line"><span class="cl"><span class="cm"> * trx:  当前事务
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span> 
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="n">trx_t</span> <span class="o">*</span><span class="n">DeadlockChecker</span><span class="o">::</span><span class="n">check_and_resolve</span><span class="p">(</span><span class="k">const</span> <span class="n">lock_t</span> <span class="o">*</span><span class="n">lock</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                                <span class="n">trx_t</span> <span class="o">*</span><span class="n">trx</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* 确保同时持有 lock_sys-&gt;mutex 和 trx-&gt;mutex. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">lock_mutex_own</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">trx_mutex_own</span><span class="p">(</span><span class="n">trx</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="n">check_trx_state</span><span class="p">(</span><span class="n">trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="o">!</span><span class="n">srv_read_only_mode</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">trx</span><span class="o">-&gt;</span><span class="n">in_innodb</span> <span class="o">&amp;</span> <span class="n">TRX_FORCE_ROLLBACK_ASYNC</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 假如 trx 设置了 TRX_FORCE_ROLLBACK_ASYNC, 即不允许该事务等待锁从而
</span></span></span><span class="line"><span class="cl"><span class="cm">     * 造成可能的死锁，我们应该选择该事务进行回滚操作. */</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="n">trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="o">!</span><span class="n">innobase_deadlock_detect</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 假如用户关闭了死锁检测，直接返回 NULL. */</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">(</span><span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">const</span> <span class="kt">bool</span> <span class="n">was_trx_mutex_ownership_tracked</span> <span class="o">=</span> <span class="n">trx</span><span class="o">-&gt;</span><span class="n">owns_mutex</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">trx</span><span class="o">-&gt;</span><span class="n">owns_mutex</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* 释放 trx-&gt;mutex: trx 的事务状态只能被当前 thread 修改, 所以是安全的. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">trx_mutex_exit</span><span class="p">(</span><span class="n">trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">const</span> <span class="n">trx_t</span> <span class="o">*</span><span class="n">victim_trx</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">do</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* 构建死锁检测 DeadlockChecker. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">DeadlockChecker</span> <span class="nf">checker</span><span class="p">(</span><span class="n">trx</span><span class="p">,</span> <span class="n">lock</span><span class="p">,</span> <span class="n">s_lock_mark_counter</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="cm">/* 进行死锁检测，并返回选中要回滚的事务. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">victim_trx</span> <span class="o">=</span> <span class="n">checker</span><span class="p">.</span><span class="n">search</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">checker</span><span class="p">.</span><span class="n">is_too_deep</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 假如死锁检测过深, 打印死锁信息. */</span>
</span></span><span class="line"><span class="cl">      <span class="n">ut_ad</span><span class="p">(</span><span class="n">trx</span> <span class="o">==</span> <span class="n">checker</span><span class="p">.</span><span class="n">m_start</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="n">ut_ad</span><span class="p">(</span><span class="n">trx</span> <span class="o">==</span> <span class="n">victim_trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">rollback_print</span><span class="p">(</span><span class="n">victim_trx</span><span class="p">,</span> <span class="n">lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">MONITOR_INC</span><span class="p">(</span><span class="n">MONITOR_DEADLOCK</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">victim_trx</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">&amp;&amp;</span> <span class="n">victim_trx</span> <span class="o">!=</span> <span class="n">trx</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="n">ut_ad</span><span class="p">(</span><span class="n">victim_trx</span> <span class="o">==</span> <span class="n">checker</span><span class="p">.</span><span class="n">m_wait_lock</span><span class="o">-&gt;</span><span class="n">trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* 进行回滚. 释放持有的锁并唤醒 thread. */</span>
</span></span><span class="line"><span class="cl">      <span class="n">checker</span><span class="p">.</span><span class="n">trx_rollback</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">lock_deadlock_found</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">MONITOR_INC</span><span class="p">(</span><span class="n">MONITOR_DEADLOCK</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">victim_trx</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">&amp;&amp;</span> <span class="n">victim_trx</span> <span class="o">!=</span> <span class="n">trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 重新持有 trx-&gt;mutex 锁. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">trx_mutex_enter</span><span class="p">(</span><span class="n">trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">trx</span><span class="o">-&gt;</span><span class="n">owns_mutex</span> <span class="o">=</span> <span class="n">was_trx_mutex_ownership_tracked</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="n">victim_trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>关于MySQL死锁检测如何判断是否存在死锁核心代码在函数 <code>DeadlockChecker::search()</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* storage/innobase/lock/lock0lock.cc */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="n">trx_t</span> <span class="o">*</span><span class="n">DeadlockChecker</span><span class="o">::</span><span class="n">search</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* 确保持有 lock_sys-&gt;mutex. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">lock_mutex_own</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* 确保没有持有 trx-&gt;mutex. */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="o">!</span><span class="n">trx_mutex_own</span><span class="p">(</span><span class="n">m_start</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* m_start: 发起死锁检测的事务, 死锁检测全程不会改变, 以该 trx 为基准判断是否存在环.
</span></span></span><span class="line"><span class="cl"><span class="cm">   * m_wait_lock: 发起死锁检测的事务等待的 lock, m_wait_lock 会随着 DFS 深度搜索过程改变.
</span></span></span><span class="line"><span class="cl"><span class="cm">   */</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">m_start</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">m_wait_lock</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">check_trx_state</span><span class="p">(</span><span class="n">m_wait_lock</span><span class="o">-&gt;</span><span class="n">trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">ut_ad</span><span class="p">(</span><span class="n">m_mark_start</span> <span class="o">&lt;=</span> <span class="n">s_lock_mark_counter</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ulint</span> <span class="n">heap_no</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="cm">/* 获取 m_wait_lock 指向的 heap_no 上的第一个 lock. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">const</span> <span class="n">lock_t</span> <span class="o">*</span><span class="n">lock</span> <span class="o">=</span> <span class="n">get_first_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">heap_no</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">for</span> <span class="p">(;;)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* We should never visit the same sub-tree more than once. */</span>
</span></span><span class="line"><span class="cl">    <span class="n">ut_ad</span><span class="p">(</span><span class="n">lock</span> <span class="o">==</span> <span class="nb">NULL</span> <span class="o">||</span> <span class="o">!</span><span class="n">is_visited</span><span class="p">(</span><span class="n">lock</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">m_n_elems</span> <span class="o">&gt;</span> <span class="mi">1</span> <span class="o">&amp;&amp;</span> <span class="n">lock</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 假如栈的元素数量大于1且 lock 为 NULL, 则代表某一条路径已经被搜索至尽头, 则进行
</span></span></span><span class="line"><span class="cl"><span class="cm">       * 回溯从而重新搜索未被访问的节点, 即存在一行数据上有多个锁.  */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">pop</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">heap_no</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* 获取同一行数据上的下一个锁. */</span>
</span></span><span class="line"><span class="cl">      <span class="n">lock</span> <span class="o">=</span> <span class="n">get_next_lock</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">heap_no</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">lock</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 假如 lock 为 NULL, DFS 搜索结束, 结束循环. */</span>
</span></span><span class="line"><span class="cl">      <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">lock</span> <span class="o">==</span> <span class="n">m_wait_lock</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 假如 lock == m_wait_lock, 需要标记该子树已经被访问过. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* 这种情况只存在 DFS 回溯的阶段:
</span></span></span><span class="line"><span class="cl"><span class="cm">       * lock_t 维护的 hash table 插入的顺序排列, 在 DFS 回溯阶段, 
</span></span></span><span class="line"><span class="cl"><span class="cm">       * 假如存在一个 record 上有多个 lock_t 在等待,
</span></span></span><span class="line"><span class="cl"><span class="cm">       * 死锁检测算法会调用 get_next_lock(), 假如拿到了 lock == m_wait_lock,
</span></span></span><span class="line"><span class="cl"><span class="cm">       * 即代表后面的 lock 应该都是 wait 的顺序的, 所以没有必要再去看那些等待的 trx.
</span></span></span><span class="line"><span class="cl"><span class="cm">      ut_ad(lock-&gt;trx-&gt;lock.deadlock_mark &lt;= m_mark_start);
</span></span></span><span class="line"><span class="cl"><span class="cm">
</span></span></span><span class="line"><span class="cl"><span class="cm">      /* 设置已经被访问的标记. */</span>
</span></span><span class="line"><span class="cl">      <span class="n">lock</span><span class="o">-&gt;</span><span class="n">trx</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">.</span><span class="n">deadlock_mark</span> <span class="o">=</span> <span class="o">++</span><span class="n">s_lock_mark_counter</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="n">ut_ad</span><span class="p">(</span><span class="n">s_lock_mark_counter</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* 设置 lock 为 NULL. */</span>
</span></span><span class="line"><span class="cl">      <span class="n">lock</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="o">!</span><span class="n">lock_has_to_wait</span><span class="p">(</span><span class="n">m_wait_lock</span><span class="p">,</span> <span class="n">lock</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 假如 m_wait_lock 和 lock 之间不存在等待关系，则需要
</span></span></span><span class="line"><span class="cl"><span class="cm">       * 获取 heap_no 对应链表上的下一个lock. */</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* No conflict, next lock */</span>
</span></span><span class="line"><span class="cl">      <span class="n">lock</span> <span class="o">=</span> <span class="n">get_next_lock</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">heap_no</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">trx</span> <span class="o">==</span> <span class="n">m_start</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 假如 lock 所指向的事务是当前发起死锁检测的事务, 即存在环. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* 打印关于死锁信息的Log. */</span>
</span></span><span class="line"><span class="cl">      <span class="n">notify</span><span class="p">(</span><span class="n">lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="p">(</span><span class="n">select_victim</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">is_too_deep</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 假如 DFS 搜索的栈元素超过了200或者访问的节点数目超过了 1000000,
</span></span></span><span class="line"><span class="cl"><span class="cm">       * 则返回 m_start 作为回滚的事务. */</span>
</span></span><span class="line"><span class="cl">      <span class="n">m_too_deep</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="p">(</span><span class="n">m_start</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">lock</span><span class="o">-&gt;</span><span class="n">trx_que_state</span><span class="p">()</span> <span class="o">==</span> <span class="n">TRX_QUE_LOCK_WAIT</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* Another trx ahead has requested a lock in an
</span></span></span><span class="line"><span class="cl"><span class="cm">      incompatible mode, and is itself waiting for a lock. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* 假如 lock 所属的 trx 处于 TRX_QUE_LOCK_WAIT，即处于锁等待的状态.
</span></span></span><span class="line"><span class="cl"><span class="cm">       * 需要将&lt;lock, heap_no&gt; 入栈，DeadlockChecker 利用数组实现栈. */</span>
</span></span><span class="line"><span class="cl">      <span class="o">++</span><span class="n">m_cost</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">push</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">heap_no</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="cm">/* 假如入栈失败，即栈的元素数量超过了4096, 标记 m_too_deep, 并返回
</span></span></span><span class="line"><span class="cl"><span class="cm">         * m_start 事务回滚. */</span>
</span></span><span class="line"><span class="cl">        <span class="n">m_too_deep</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="p">(</span><span class="n">m_start</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* 使用 lock 替换 m_wait_lock, 用作下一次搜索. */</span>
</span></span><span class="line"><span class="cl">      <span class="n">m_wait_lock</span> <span class="o">=</span> <span class="n">lock</span><span class="o">-&gt;</span><span class="n">trx</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">.</span><span class="n">wait_lock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* 获取当前 m_wait_lock 所属的 heap_no 的第一个 lock. */</span>
</span></span><span class="line"><span class="cl">      <span class="n">lock</span> <span class="o">=</span> <span class="n">get_first_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">heap_no</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">      <span class="cm">/* 假如该 lock 已经被访问过，则获取下一个 lock. */</span>
</span></span><span class="line"><span class="cl">      <span class="k">if</span> <span class="p">(</span><span class="n">is_visited</span><span class="p">(</span><span class="n">lock</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">lock</span> <span class="o">=</span> <span class="n">get_next_lock</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">heap_no</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="cm">/* 否则获取下一个 lock. */</span>
</span></span><span class="line"><span class="cl">      <span class="n">lock</span> <span class="o">=</span> <span class="n">get_next_lock</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">heap_no</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">ut_a</span><span class="p">(</span><span class="n">lock</span> <span class="o">==</span> <span class="nb">NULL</span> <span class="o">&amp;&amp;</span> <span class="n">m_n_elems</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="cm">/* 没有发现死锁. */</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>select_victim()</code> 返回一个选中需要被回滚的事务，MySQL 并不会迭代所有的 trx 来选择一个代价较小的事务，仅仅在 <code>m_start</code> 和 <code>m_wait_lock-&gt;trx</code> 这两个事务中选一个优先级较低的事务回滚.</p>
]]></content>
  </entry><entry>
    <title>理解 MySQL 意向锁</title>
    <link href="https://leviathan.vip/2019/12/18/understand-mysql-intention-lock/" />
    <id>https://leviathan.vip/2019/12/18/understand-mysql-intention-lock/</id>
    <updated>2019-12-18T01:59:20Z</updated>
    <summary type="html"><![CDATA[<h2 id="准备">准备</h2>
<p><strong>MySQL内核版本: 8.0.17</strong></p>
<h2 id="理解-lock-和-latch">理解 lock 和 latch</h2>
<h3 id="latch">latch</h3>
<p>数据库中的 latch 和我们通常代码编程中保证并发多线程操作操作临界资源的锁意义一样，通过 latch 的中文翻译“闩”就可以理解，这是为了维护一段临界区域.</p>
<h3 id="lock">lock</h3>
<p>而 lock 则是数据库 MySQL 中在事务使用的&quot;锁&quot;, 锁定的对象是表或者行. 关于 MySQL 的死锁可以查看另外一篇文章<a href="">MySQL死锁检测</a>.</p>
<h2 id="锁的类型">锁的类型</h2>
<ul>
<li>
<p>行锁</p>
</li>
<li>
<p>意向锁</p>
</li>
<li>
<p>GAP 锁</p>
</li>
</ul>
<h2 id="意向锁">意向锁</h2>
<p>表级别锁的兼容互斥矩阵:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>X</th>
          <th>IX</th>
          <th>S</th>
          <th>IS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>X</td>
          <td>Conflict</td>
          <td>Conflict</td>
          <td>Conflict</td>
          <td>Conflict</td>
      </tr>
      <tr>
          <td>IX</td>
          <td>Conflict</td>
          <td>Compatible</td>
          <td>Conflict</td>
          <td>Compatible</td>
      </tr>
      <tr>
          <td>S</td>
          <td>Conflict</td>
          <td>Conflict</td>
          <td>Compatible</td>
          <td>Compatible</td>
      </tr>
      <tr>
          <td>IS</td>
          <td>Conflict</td>
          <td>Compatible</td>
          <td>Compatible</td>
          <td>Compatible</td>
      </tr>
  </tbody>
</table>
<p>需要注意上图矩阵的 <code>X</code>, <code>IX</code>, <code>S</code>, <code>IS</code> 锁均为表锁，并不代表行锁.</p>
<p>锁的含义:</p>
<p><code>X</code>: 排他锁
<code>IX</code>: 意向排他锁
<code>S</code>: 共享锁
<code>IS</code>: 意向共享锁</p>
<p>在一个事务 <code>trx_t</code> 中用结果 <code>trx_lock_t</code> 来存放事务申请的锁信息, 包括行锁和表锁, 即 <code>trx-&gt;lock.trx_locks</code> 和 <code>trx-&gt;lock.table_locks</code>.</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="准备">准备</h2>
<p><strong>MySQL内核版本: 8.0.17</strong></p>
<h2 id="理解-lock-和-latch">理解 lock 和 latch</h2>
<h3 id="latch">latch</h3>
<p>数据库中的 latch 和我们通常代码编程中保证并发多线程操作操作临界资源的锁意义一样，通过 latch 的中文翻译“闩”就可以理解，这是为了维护一段临界区域.</p>
<h3 id="lock">lock</h3>
<p>而 lock 则是数据库 MySQL 中在事务使用的&quot;锁&quot;, 锁定的对象是表或者行. 关于 MySQL 的死锁可以查看另外一篇文章<a href="">MySQL死锁检测</a>.</p>
<h2 id="锁的类型">锁的类型</h2>
<ul>
<li>
<p>行锁</p>
</li>
<li>
<p>意向锁</p>
</li>
<li>
<p>GAP 锁</p>
</li>
</ul>
<h2 id="意向锁">意向锁</h2>
<p>表级别锁的兼容互斥矩阵:</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>X</th>
          <th>IX</th>
          <th>S</th>
          <th>IS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>X</td>
          <td>Conflict</td>
          <td>Conflict</td>
          <td>Conflict</td>
          <td>Conflict</td>
      </tr>
      <tr>
          <td>IX</td>
          <td>Conflict</td>
          <td>Compatible</td>
          <td>Conflict</td>
          <td>Compatible</td>
      </tr>
      <tr>
          <td>S</td>
          <td>Conflict</td>
          <td>Conflict</td>
          <td>Compatible</td>
          <td>Compatible</td>
      </tr>
      <tr>
          <td>IS</td>
          <td>Conflict</td>
          <td>Compatible</td>
          <td>Compatible</td>
          <td>Compatible</td>
      </tr>
  </tbody>
</table>
<p>需要注意上图矩阵的 <code>X</code>, <code>IX</code>, <code>S</code>, <code>IS</code> 锁均为表锁，并不代表行锁.</p>
<p>锁的含义:</p>
<p><code>X</code>: 排他锁
<code>IX</code>: 意向排他锁
<code>S</code>: 共享锁
<code>IS</code>: 意向共享锁</p>
<p>在一个事务 <code>trx_t</code> 中用结果 <code>trx_lock_t</code> 来存放事务申请的锁信息, 包括行锁和表锁, 即 <code>trx-&gt;lock.trx_locks</code> 和 <code>trx-&gt;lock.table_locks</code>.</p>
<p>MySQL为了支持多粒度的锁, 引入了意向锁，意向锁是一种可以与行锁共存的锁, 例如 <code>SELECT ... FOR SHARE</code> 设置了 <code>IS</code> 意向共享锁, 而<code> SELECT ... FOR UPDATE</code>设置了<code>IX</code>意向排他锁. 意向锁的上锁原则如下:</p>
<ul>
<li>
<p>当一个事务对一个表的某一行记录申请 record 共享锁(行锁), 需要先申请 <code>IS</code> 意向共享锁(表锁).</p>
</li>
<li>
<p>当一个事务对一个表的某一行记录申请 record 排他锁(行锁), 需要先申请 <code>IX</code> 意向排他锁(表锁).</p>
</li>
</ul>
<p>X，IS是表级锁，不会和行级的X，S锁发生冲突, 只会和表级的X，S发生冲突. 行级别的X和S只与其它行锁存在普通的共享、排他规则. 而意向锁的意义是当需要向一张表添加表级X锁时，假如没有意向锁，需要遍历 <code>lock_sys-&gt;rec_hash</code> 判断是否与该X锁存在冲突的锁.</p>
<h2 id="源码分析">源码分析</h2>
<p>我们以源码分析的方式来直观的理解意向锁的加锁过程，我们以 update 一条 record 获取 IX 锁为例:</p>
<p>在 IX 锁申请之前，会对当前表(<code>dict_table_t</code>)记录的锁信息的兼容情况进行判断(<code>lock_table_other_has_incompatible()</code>), 符合兼容矩阵的从而在 <code>row_upd_step()</code> 函数中调用 <code>lock_table()</code> 申请 IX 锁, 表级锁的申请过程如下:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cm">/* storage/innobase/lock/lock0lock.cc */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">UNIV_INLINE</span>
</span></span><span class="line"><span class="cl"><span class="n">lock_t</span><span class="o">*</span>
</span></span><span class="line"><span class="cl"><span class="nf">lock_table_create</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"><span class="cm">/*==============*/</span>
</span></span><span class="line"><span class="cl">	<span class="n">dict_table_t</span><span class="o">*</span>	<span class="n">table</span><span class="p">,</span>	<span class="cm">/*!&lt; in/out: database table
</span></span></span><span class="line"><span class="cl"><span class="cm">				in dictionary cache */</span>
</span></span><span class="line"><span class="cl">	<span class="n">ulint</span>		<span class="n">type_mode</span><span class="p">,</span><span class="cm">/*!&lt; in: lock mode possibly ORed with
</span></span></span><span class="line"><span class="cl"><span class="cm">				LOCK_WAIT */</span>
</span></span><span class="line"><span class="cl">	<span class="n">trx_t</span><span class="o">*</span>		<span class="n">trx</span><span class="p">)</span>	<span class="cm">/*!&lt; in: trx */</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="n">lock_t</span><span class="o">*</span>		<span class="n">lock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">ut_ad</span><span class="p">(</span><span class="n">table</span> <span class="o">&amp;&amp;</span> <span class="n">trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">ut_ad</span><span class="p">(</span><span class="n">lock_mutex_own</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">	<span class="n">ut_ad</span><span class="p">(</span><span class="n">trx_mutex_own</span><span class="p">(</span><span class="n">trx</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* 检查事务状态. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">check_trx_state</span><span class="p">(</span><span class="n">trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">((</span><span class="n">type_mode</span> <span class="o">&amp;</span> <span class="n">LOCK_MODE_MASK</span><span class="p">)</span> <span class="o">==</span> <span class="n">LOCK_AUTO_INC</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="o">++</span><span class="n">table</span><span class="o">-&gt;</span><span class="n">n_waiting_or_granted_auto_inc_locks</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">type_mode</span> <span class="o">==</span> <span class="n">LOCK_AUTO_INC</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="cm">/* 对于AUTOINC 锁可以直接复用. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">lock</span> <span class="o">=</span> <span class="n">table</span><span class="o">-&gt;</span><span class="n">autoinc_lock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="n">table</span><span class="o">-&gt;</span><span class="n">autoinc_trx</span> <span class="o">=</span> <span class="n">trx</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="n">ib_vector_push</span><span class="p">(</span><span class="n">trx</span><span class="o">-&gt;</span><span class="n">autoinc_locks</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">trx</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">.</span><span class="n">table_cached</span> <span class="o">&lt;</span> <span class="n">trx</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">.</span><span class="n">table_pool</span><span class="p">.</span><span class="n">size</span><span class="p">())</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="cm">/* 假如trx的table_pool有预先申请的table lock. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">lock</span> <span class="o">=</span> <span class="n">trx</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">.</span><span class="n">table_pool</span><span class="p">[</span><span class="n">trx</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">.</span><span class="n">table_cached</span><span class="o">++</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="cm">/* 否则通过内存分配一个table lock. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">lock</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">lock_t</span><span class="o">*&gt;</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">			<span class="n">mem_heap_alloc</span><span class="p">(</span><span class="n">trx</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">.</span><span class="n">lock_heap</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">lock</span><span class="p">)));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* 设置lock相关的数据变量. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">lock</span><span class="o">-&gt;</span><span class="n">type_mode</span> <span class="o">=</span> <span class="n">ib_uint32_t</span><span class="p">(</span><span class="n">type_mode</span> <span class="o">|</span> <span class="n">LOCK_TABLE</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">lock</span><span class="o">-&gt;</span><span class="n">trx</span> <span class="o">=</span> <span class="n">trx</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">lock</span><span class="o">-&gt;</span><span class="n">un_member</span><span class="p">.</span><span class="n">tab_lock</span><span class="p">.</span><span class="n">table</span> <span class="o">=</span> <span class="n">table</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">ut_ad</span><span class="p">(</span><span class="n">table</span><span class="o">-&gt;</span><span class="n">n_ref_count</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">||</span> <span class="o">!</span><span class="n">table</span><span class="o">-&gt;</span><span class="n">can_be_evicted</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* 插入trx-&gt;lock的trx_locks. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">UT_LIST_ADD_LAST</span><span class="p">(</span><span class="n">trx</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">.</span><span class="n">trx_locks</span><span class="p">,</span> <span class="n">lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">ut_list_append</span><span class="p">(</span><span class="n">table</span><span class="o">-&gt;</span><span class="n">locks</span><span class="p">,</span> <span class="n">lock</span><span class="p">,</span> <span class="n">TableLockGetNode</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">type_mode</span> <span class="o">&amp;</span> <span class="n">LOCK_WAIT</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="cm">/* 假如设置了LOCK_WAIT状态，需要设置lock.wait_lock. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">lock_set_lock_and_trx_wait</span><span class="p">(</span><span class="n">lock</span><span class="p">,</span> <span class="n">trx</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* 插入trx-&gt;lock的table_locks. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">lock</span><span class="o">-&gt;</span><span class="n">trx</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">.</span><span class="n">table_locks</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">MONITOR_INC</span><span class="p">(</span><span class="n">MONITOR_TABLELOCK_CREATED</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">MONITOR_INC</span><span class="p">(</span><span class="n">MONITOR_NUM_TABLELOCK</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span><span class="p">(</span><span class="n">lock</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p><code>row_upd_step()</code> 完成申请IX意向排他锁后继续调用 <code>row_upd_clust_step()</code>, 而 <code>row_upd_clust_step()</code> 调用 <code>lock_clust_rec_modify_check_and_lock()</code> 对修改的 record 申请 X 锁:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"> <span class="o">----------------</span>
</span></span><span class="line"><span class="cl"><span class="o">|</span> <span class="n">row_upd_step</span><span class="p">()</span> <span class="o">|</span>   <span class="cm">/* 申请 IX 锁. */</span>
</span></span><span class="line"><span class="cl"> <span class="o">----------------</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>
</span></span><span class="line"><span class="cl">   <span class="o">|</span>   <span class="o">----------------</span>
</span></span><span class="line"><span class="cl">   <span class="o">-&gt;</span> <span class="o">|</span>      <span class="p">...</span>       <span class="o">|</span>
</span></span><span class="line"><span class="cl">       <span class="o">----------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>
</span></span><span class="line"><span class="cl">         <span class="o">|</span>   <span class="o">----------------------</span>
</span></span><span class="line"><span class="cl">         <span class="o">-&gt;</span> <span class="o">|</span> <span class="n">row_upd_clust_step</span><span class="p">()</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">             <span class="o">----------------------</span>
</span></span><span class="line"><span class="cl">               <span class="o">|</span>
</span></span><span class="line"><span class="cl">               <span class="o">|</span>   <span class="o">----------------------------------------</span>
</span></span><span class="line"><span class="cl">               <span class="o">-&gt;</span> <span class="o">|</span> <span class="n">lock_clust_rec_modify_check_and_lock</span><span class="p">()</span> <span class="o">|</span>    <span class="cm">/* 申请 record 的 X 锁. */</span>
</span></span><span class="line"><span class="cl">               <span class="o">|</span>   <span class="o">----------------------------------------</span>
</span></span><span class="line"><span class="cl">               <span class="o">|</span>
</span></span><span class="line"><span class="cl">               <span class="o">|</span>   <span class="o">---------------</span>
</span></span><span class="line"><span class="cl">               <span class="o">-&gt;</span> <span class="o">|</span>     <span class="p">...</span>       <span class="o">|</span>
</span></span><span class="line"><span class="cl">                   <span class="o">---------------</span></span></span></code></pre></div><p>例如此时某一个用户正在使用 <code>lock table</code> 语句锁表，依然会进入 <code>lock_table_other_has_incompatible()</code> 判断表级锁的兼容情况，假如产生冲突，该用户线程则会进入 wait 状态.</p>
<h2 id="总结">总结</h2>
<ol>
<li>MySQL支持的意向锁之间互不排斥，除了 IS 与 S 锁兼容外，意向锁会与 共享锁/ 排他锁 互斥.</li>
<li>IX，IS是表级锁，不会和行级的X，S锁发生冲突.</li>
</ol>
]]></content>
  </entry><entry>
    <title>Linux 进程调度-基于 ARM64</title>
    <link href="https://leviathan.vip/2019/11/26/Linux%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6-%E5%9F%BA%E4%BA%8EARM64/" />
    <id>https://leviathan.vip/2019/11/26/Linux%E8%BF%9B%E7%A8%8B%E8%B0%83%E5%BA%A6-%E5%9F%BA%E4%BA%8EARM64/</id>
    <updated>2019-11-26T21:27:09Z</updated>
    <summary type="html"><![CDATA[<h2 id="进程">进程</h2>
<p>在 Linux 内核中，进程一般称为任务(task), 进程的虚拟地址空间在内存管理模块中被分为用户虚拟地址空间和内核虚拟地址空间，所有的进程共享内核虚拟地址空间, 每一个进程有独立的用户虚拟地址空间. 在内核中，进程有两种特殊形式，没有使用用户虚拟地址空间的进程称为内核线程，共享用户虚拟地址空间的进程称为用户线程.</p>
<p>我们通常开发过程中提及的进程与线程在 Linux 内核中并没有明确的区别，它们都拥有数据结构 <code>task_struct</code> 作为描述符，我们通常所讲的进程与线程的主要区别即是否共享用户虚拟空间.</p>
<p>本文着重介绍了 CFS 公平调度算法，它的公平性主要体现在按照优先级将一个完整的调度周期分配给不同的进程, 尽管每个进程因为优先级分得的时间片不同，但保证在一个调度周期内所有的进程都会被运行一次.</p>
<h3 id="进程描述符task_struct">进程描述符task_struct</h3>
<p>(<code>task_struct</code> 数据成员较多，仅列出重要的数据成员)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">task_struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* -1 unrunnable, 0 runnable, &gt;0 stopped: */</span>
</span></span><span class="line"><span class="cl">	<span class="k">volatile</span> <span class="kt">long</span>			<span class="n">state</span><span class="p">;</span>		<span class="cm">/* 进程状态位 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="kt">void</span>				<span class="o">*</span><span class="n">stack</span><span class="p">;</span>		<span class="cm">/* 指向内核栈 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 优先级 */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">prio</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">static_prio</span><span class="p">;</span>	<span class="cm">/* 普通进程的静态优先级 */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">normal_prio</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">unsigned</span> <span class="kt">int</span>			<span class="n">rt_priority</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">struct</span> <span class="nc">sched_entity</span>   <span class="n">se</span><span class="p">;</span>  <span class="cm">/* 记录进程的调度信息, 用来插入 rb-tree 供调度使用 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 调度策略 */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">unsigned</span> <span class="kt">int</span>			<span class="n">policy</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">cpumask_t</span>			<span class="n">cpus_mask</span><span class="p">;</span>	<span class="cm">/* 允许进程在哪些CPU执行, cpuset相关 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">sched_info</span>		<span class="n">sched_info</span><span class="p">;</span>	<span class="cm">/* 调度信息 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">mm_struct</span>		<span class="o">*</span><span class="n">mm</span><span class="p">;</span>		<span class="cm">/* 内存描述符 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">vmacache</span>			<span class="n">vmacache</span><span class="p">;</span>	<span class="cm">/* 虚拟内存管理 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 进程退出相关 */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">exit_state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">exit_code</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">exit_signal</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* The signal sent when the parent dies: */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">pdeath_signal</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">pid_t</span>				<span class="n">pid</span><span class="p">;</span>   <span class="cm">/* 进程号 */</span>
</span></span><span class="line"><span class="cl">	<span class="n">pid_t</span>				<span class="n">tgid</span><span class="p">;</span>  <span class="cm">/* 线程组标识符 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* Real parent process: */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">task_struct</span> <span class="n">__rcu</span>	<span class="o">*</span><span class="n">real_parent</span><span class="p">;</span>		<span class="cm">/* 指向真实父进程 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">char</span>				<span class="n">comm</span><span class="p">[</span><span class="n">TASK_COMM_LEN</span><span class="p">];</span>	<span class="cm">/* 进程名 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* Filesystem information: */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">fs_struct</span>		<span class="o">*</span><span class="n">fs</span><span class="p">;</span>		<span class="cm">/* 文件系统描述符 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* Open file information: */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">files_struct</span>		<span class="o">*</span><span class="n">files</span><span class="p">;</span>		<span class="cm">/* 打开的文件信息 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 信号处理相关 */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">signal_struct</span>		<span class="o">*</span><span class="n">signal</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">sighand_struct</span>		<span class="o">*</span><span class="n">sighand</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">sigset_t</span>			<span class="n">blocked</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">sigset_t</span>			<span class="n">real_blocked</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">thread_struct</span>		<span class="kr">thread</span><span class="p">;</span>  <span class="cm">/* CPU的部分状态(寄存器)保存在thread中 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><h3 id="进程优先级">进程优先级</h3>
<ul>
<li>限期进程的优先级比实时进程高，实时进程比普通用户进程优先级高</li>
<li>限期进程的优先级是-1</li>
<li>实时进程的优先级是1~99, 优先级数值越大，优先级越高</li>
<li>普通进程的优先级是100~139, 优先级数值越小，优先级越高</li>
</ul>
<h3 id="调度策略">调度策略</h3>
<p>Linux 内核支持以下调度策略:</p>]]></summary>
    <content type="html"><![CDATA[<h2 id="进程">进程</h2>
<p>在 Linux 内核中，进程一般称为任务(task), 进程的虚拟地址空间在内存管理模块中被分为用户虚拟地址空间和内核虚拟地址空间，所有的进程共享内核虚拟地址空间, 每一个进程有独立的用户虚拟地址空间. 在内核中，进程有两种特殊形式，没有使用用户虚拟地址空间的进程称为内核线程，共享用户虚拟地址空间的进程称为用户线程.</p>
<p>我们通常开发过程中提及的进程与线程在 Linux 内核中并没有明确的区别，它们都拥有数据结构 <code>task_struct</code> 作为描述符，我们通常所讲的进程与线程的主要区别即是否共享用户虚拟空间.</p>
<p>本文着重介绍了 CFS 公平调度算法，它的公平性主要体现在按照优先级将一个完整的调度周期分配给不同的进程, 尽管每个进程因为优先级分得的时间片不同，但保证在一个调度周期内所有的进程都会被运行一次.</p>
<h3 id="进程描述符task_struct">进程描述符task_struct</h3>
<p>(<code>task_struct</code> 数据成员较多，仅列出重要的数据成员)</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">task_struct</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* -1 unrunnable, 0 runnable, &gt;0 stopped: */</span>
</span></span><span class="line"><span class="cl">	<span class="k">volatile</span> <span class="kt">long</span>			<span class="n">state</span><span class="p">;</span>		<span class="cm">/* 进程状态位 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="kt">void</span>				<span class="o">*</span><span class="n">stack</span><span class="p">;</span>		<span class="cm">/* 指向内核栈 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 优先级 */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">prio</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">static_prio</span><span class="p">;</span>	<span class="cm">/* 普通进程的静态优先级 */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">normal_prio</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">unsigned</span> <span class="kt">int</span>			<span class="n">rt_priority</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">struct</span> <span class="nc">sched_entity</span>   <span class="n">se</span><span class="p">;</span>  <span class="cm">/* 记录进程的调度信息, 用来插入 rb-tree 供调度使用 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 调度策略 */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">unsigned</span> <span class="kt">int</span>			<span class="n">policy</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">cpumask_t</span>			<span class="n">cpus_mask</span><span class="p">;</span>	<span class="cm">/* 允许进程在哪些CPU执行, cpuset相关 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">sched_info</span>		<span class="n">sched_info</span><span class="p">;</span>	<span class="cm">/* 调度信息 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">mm_struct</span>		<span class="o">*</span><span class="n">mm</span><span class="p">;</span>		<span class="cm">/* 内存描述符 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">vmacache</span>			<span class="n">vmacache</span><span class="p">;</span>	<span class="cm">/* 虚拟内存管理 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 进程退出相关 */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">exit_state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">exit_code</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">exit_signal</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* The signal sent when the parent dies: */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>				<span class="n">pdeath_signal</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">pid_t</span>				<span class="n">pid</span><span class="p">;</span>   <span class="cm">/* 进程号 */</span>
</span></span><span class="line"><span class="cl">	<span class="n">pid_t</span>				<span class="n">tgid</span><span class="p">;</span>  <span class="cm">/* 线程组标识符 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* Real parent process: */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">task_struct</span> <span class="n">__rcu</span>	<span class="o">*</span><span class="n">real_parent</span><span class="p">;</span>		<span class="cm">/* 指向真实父进程 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">char</span>				<span class="n">comm</span><span class="p">[</span><span class="n">TASK_COMM_LEN</span><span class="p">];</span>	<span class="cm">/* 进程名 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* Filesystem information: */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">fs_struct</span>		<span class="o">*</span><span class="n">fs</span><span class="p">;</span>		<span class="cm">/* 文件系统描述符 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* Open file information: */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">files_struct</span>		<span class="o">*</span><span class="n">files</span><span class="p">;</span>		<span class="cm">/* 打开的文件信息 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 信号处理相关 */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">signal_struct</span>		<span class="o">*</span><span class="n">signal</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">sighand_struct</span>		<span class="o">*</span><span class="n">sighand</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">sigset_t</span>			<span class="n">blocked</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">sigset_t</span>			<span class="n">real_blocked</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="nc">thread_struct</span>		<span class="kr">thread</span><span class="p">;</span>  <span class="cm">/* CPU的部分状态(寄存器)保存在thread中 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><h3 id="进程优先级">进程优先级</h3>
<ul>
<li>限期进程的优先级比实时进程高，实时进程比普通用户进程优先级高</li>
<li>限期进程的优先级是-1</li>
<li>实时进程的优先级是1~99, 优先级数值越大，优先级越高</li>
<li>普通进程的优先级是100~139, 优先级数值越小，优先级越高</li>
</ul>
<h3 id="调度策略">调度策略</h3>
<p>Linux 内核支持以下调度策略:</p>
<ul>
<li>停机进程使用停机调度策略:</li>
</ul>
<p>停机进程是优先级最高的进程，停机就是我们通常理解的使处理器停下来，做更紧急的任务.</p>
<ul>
<li>限期进程使用限期调度策略:</li>
</ul>
<p>限期进程使用最早期限优先算法，使用红黑书把进程按照绝对截止期限从小到大排序，每次调度时选择绝对截止期限最小的进程.</p>
<ul>
<li>
<p>实时进程支持两种调度策略: 先进先出调度和轮流调度</p>
</li>
<li>
<p>普通进程支持两种调度策略, 标准轮流分时和空闲调度</p>
</li>
<li>
<p>处理器上的空闲进程使用空闲调度策略:</p>
</li>
</ul>
<p>每个处理器上有一个空闲进程，即0号进程. 空闲进程的优先级最低，只有当没有其他进程可以调度的时候，才会调度空闲进程.</p>
<h3 id="完全公平调度算法cfs">完全公平调度算法CFS</h3>
<p>我们这里介绍相对重要的普通用户进程的调度策略: 完全公平调度策略 CFS(Completely Fair Scheduler):</p>
<p>普通进程使用完全公平调度(CFS)算法. 为了保证在一个周期内所有的进程都能被调度, 完全公平调度算法引入了虚拟运行时间 <code>vruntime</code> 的概念:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">  <span class="nv">虚拟运行时间</span> <span class="o">=</span> 实际运行时间 * nice0 对应的权重 / 进程的权重<span class="o">(</span> nice 值对应的权重<span class="o">)</span></span></span></code></pre></div><ul>
<li>实际运行时间</li>
</ul>
<p>实际运行时间就是字面意思，进程在 CPU 上运行的实际时间. 每一个进程(<code>task_struct</code>)的调度信息结构体 <code>sched_entity</code> 都记录了进程调度开始时间点(exec_start), 实际运行时间(sum_exec_runtime), 虚拟运行时间(vruntime), 上一次时间运行时间(prev_sum_exec_runtime).</p>
<p>调度进程的时候，选中 next 进程, 并开始记录 next 进程的开始运行时间点，运行结束后计算时间差即为进程的实际运行时间.</p>
<ul>
<li>nice0 对应的权重</li>
</ul>
<p>在 <code>kernel/sched/core.c</code> 中定义了 nice 值与权重的对应关系，nice0 的值为1024.</p>
<ul>
<li>进程的权重</li>
</ul>
<p>普通进程的 nice 值的取值范围是-20~19, 以下是 nice 值与权重的对应关系如下:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">int</span> <span class="n">sched_prio_to_weight</span><span class="p">[</span><span class="mi">40</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/* -20 */</span>     <span class="mi">88761</span><span class="p">,</span>     <span class="mi">71755</span><span class="p">,</span>     <span class="mi">56483</span><span class="p">,</span>     <span class="mi">46273</span><span class="p">,</span>     <span class="mi">36291</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/* -15 */</span>     <span class="mi">29154</span><span class="p">,</span>     <span class="mi">23254</span><span class="p">,</span>     <span class="mi">18705</span><span class="p">,</span>     <span class="mi">14949</span><span class="p">,</span>     <span class="mi">11916</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/* -10 */</span>      <span class="mi">9548</span><span class="p">,</span>      <span class="mi">7620</span><span class="p">,</span>      <span class="mi">6100</span><span class="p">,</span>      <span class="mi">4904</span><span class="p">,</span>      <span class="mi">3906</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/*  -5 */</span>      <span class="mi">3121</span><span class="p">,</span>      <span class="mi">2501</span><span class="p">,</span>      <span class="mi">1991</span><span class="p">,</span>      <span class="mi">1586</span><span class="p">,</span>      <span class="mi">1277</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/*   0 */</span>      <span class="mi">1024</span><span class="p">,</span>       <span class="mi">820</span><span class="p">,</span>       <span class="mi">655</span><span class="p">,</span>       <span class="mi">526</span><span class="p">,</span>       <span class="mi">423</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/*   5 */</span>       <span class="mi">335</span><span class="p">,</span>       <span class="mi">272</span><span class="p">,</span>       <span class="mi">215</span><span class="p">,</span>       <span class="mi">172</span><span class="p">,</span>       <span class="mi">137</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/*  10 */</span>       <span class="mi">110</span><span class="p">,</span>        <span class="mi">87</span><span class="p">,</span>        <span class="mi">70</span><span class="p">,</span>        <span class="mi">56</span><span class="p">,</span>        <span class="mi">45</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/*  15 */</span>        <span class="mi">36</span><span class="p">,</span>        <span class="mi">29</span><span class="p">,</span>        <span class="mi">23</span><span class="p">,</span>        <span class="mi">18</span><span class="p">,</span>        <span class="mi">15</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><p>nice 值越小，进程的权重也就越大.</p>
<p>完全公平调度算法利用 rb-tree 将进程按虚拟运行时间从小到大的排序，每次调度选择虚拟运行时间最小的进程. nice0 对应的权重为常量，即可以理解在实际运行时间相同的情况下，进程的权重( nice 值对应的权重值)越大，被调度的机会就越大.</p>
<ul>
<li>调度最小粒度</li>
</ul>
<p>内核设置了调度最小粒度，默认为 0.75 毫秒，可以通过文件 <code>/proc/sys/kernel/sched_min_granularity_ns</code> 调整. 调度最小粒度表示进程在 CPU 至少运行的时间长度.</p>
<ul>
<li>调度周期</li>
</ul>
<p>在某个时间长度可以保证运行队列的每个进程都至少运行一次, 这个时间长度称为调度周期，如果运行队列的进程数量大于 8, 那么调度周期等于调度最小粒度 * 进程数量，否则调度周期为6ms</p>
<ul>
<li>进程的时间片</li>
</ul>
<p>进程的时间片公式如下:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">进程的时间片<span class="o">(</span>实际运行时间<span class="o">)</span> <span class="o">=</span> <span class="o">(</span>调度周期 * 进程权重 / 运行队列中所有进程的权重总和<span class="o">)</span></span></span></code></pre></div><p>介绍了CFS的基本概念, 我们来举例来分析为什么 CFS 算法是一个公平调度算法:</p>
<p>假如有两个进程A和B, 进程的 nice 值0和1, 即 A 进程的权重为1024, B 进程的权重为820. 以6ms的调度周期来计算, 根据进程的时间片公式，两个进程分别的运行时间片为A进程 <code>6 * 1024 / (1024+820) = 3.33ms</code>, B进程 <code>6 * 820 / (1024+820) = 2.66ms</code>. 通过进程时间片公式计算我们可以看到不同的优先级的进程运行时间片不同，但为了保证在 CPU 选择进程调度时，尽可能保证每个进程被选择的可能性是相同的，这里就要反推我们上面提到的虚拟运行时间. A 进程的虚拟运行时间为 <code>3.33 * 1024(nice 0) / 1024 = 3.33</code>, B 进程的虚拟运行时间为 <code>2.6 * 1024(nice 0) / 820 = 3.33</code>. 通过虚拟运行时间的公式我们得出A 进程和B 进程尽管优先级不同，但是在 rb-tree 的位置是接近的, 即被调度的优先级是相同的.</p>
<h3 id="cfs调度算法的公平性体现在哪里">CFS调度算法的公平性体现在哪里?</h3>
<p>我们可以先通过公式推导发现:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">进程的时间片<span class="o">(</span>实际运行时间<span class="o">)</span> <span class="o">=</span> <span class="o">(</span>调度周期 * 进程权重 / 运行队列中所有进程的权重总和<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">虚拟运行时间</span> <span class="o">=</span> 实际运行时间 * nice0 对应的权重 / 进程的权重<span class="o">(</span> nice 值对应的权重<span class="o">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                调度周期 * 进程权重 * nice0 权重     调度周期 * nice0 权重
</span></span><span class="line"><span class="cl"><span class="nv">虚拟运行时间</span><span class="o">=</span>  ---------------------------------  <span class="o">=</span> ----------------------------
</span></span><span class="line"><span class="cl">                 所有进程的权重总和 * 进程权重        所有进程的权重总和</span></span></code></pre></div><p>CFS 调度算法利用虚拟运行时间保证在一个调度周期每个进程被调度的优先级尽可能的一样.</p>
<h3 id="新进程的vruntime的初始值">新进程的vruntime的初始值</h3>
<p>对于新创建的进程我们如何设置虚拟运行时间, 假如设为0, 则调度器会因为 <code>vruntime</code> 较小频繁的调度新建的进程直到它的虚拟运行时间追上就绪队列里其他的进程. 这个现象则违背了 CFS 调度算法的公平性. 所以在有一个数据字段 <code>min_vruntime</code>, 当新进程创建时，我们将其 <code>vruntime</code> 初始化为就绪队列里的 <code>min_vruntime</code>, 则确保新进程与大部分的进程之间的虚拟运行时间的 GAP 不会过大, 从而避免被频繁调度.</p>
<h2 id="进程调度源码分析">进程调度源码分析</h2>
<h3 id="运行队列">运行队列</h3>
<p>每一个处理器都有一个运行队列，定义如下:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/* kernel/sched/sched.h */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">rq</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* runqueue lock: */</span>
</span></span><span class="line"><span class="cl">	<span class="kt">raw_spinlock_t</span>		<span class="n">lock</span><span class="p">;</span>     <span class="cm">/* 运行队列的锁 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="kt">unsigned</span> <span class="kt">int</span>    <span class="n">nr_running</span><span class="p">;</span> <span class="cm">/* 运行队列的进程数量 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">cfs_rq</span>		<span class="n">cfs</span><span class="p">;</span>  <span class="cm">/* 公平运行进程队列 */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">rt_rq</span>		<span class="n">rt</span><span class="p">;</span>   <span class="cm">/* 实时运行进程队列 */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">dl_rq</span>		<span class="n">dl</span><span class="p">;</span>   <span class="cm">/* 限期运行进程队列 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">task_struct</span>	<span class="o">*</span><span class="n">curr</span><span class="p">;</span>  <span class="cm">/* 正在运行的进程 */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">task_struct</span>	<span class="o">*</span><span class="n">idle</span><span class="p">;</span>  <span class="cm">/* 空闲进程 */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">task_struct</span>	<span class="o">*</span><span class="n">stop</span><span class="p">;</span>  <span class="cm">/* 迁移进程 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div><h3 id="调度进程">调度进程</h3>
<p>调度进程的核心函数是 <code>__schedule</code>, 函数 <code>__schedule()</code> 的处理流程如下:</p>
<ul>
<li>调用 <code>pick_next_task()</code> 以选择下一个进程</li>
<li>调用 <code>context_switch()</code> 以切换进程</li>
</ul>
<h3 id="pick_next_task"><code>pick_next_task()</code></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="kr">inline</span> <span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span>
</span></span><span class="line"><span class="cl"><span class="nf">pick_next_task</span><span class="p">(</span><span class="k">struct</span> <span class="n">rq</span> <span class="o">*</span><span class="n">rq</span><span class="p">,</span> <span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">prev</span><span class="p">,</span> <span class="k">struct</span> <span class="n">rq_flags</span> <span class="o">*</span><span class="n">rf</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* sched_class是Linux抽象的调度类，类别和优先级分别如下:
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * 停机调度类，限期调度类，实时调度类，公平调度类和空闲调度类. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">const</span> <span class="k">struct</span> <span class="n">sched_class</span> <span class="o">*</span><span class="n">class</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">p</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 假如所有进程属于公平调度类,
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * 我们可以直接调用公平调度类的pick_next_task函数. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="nf">likely</span><span class="p">((</span><span class="n">prev</span><span class="o">-&gt;</span><span class="n">sched_class</span> <span class="o">==</span> <span class="o">&amp;</span><span class="n">idle_sched_class</span> <span class="o">||</span>
</span></span><span class="line"><span class="cl">		    <span class="n">prev</span><span class="o">-&gt;</span><span class="n">sched_class</span> <span class="o">==</span> <span class="o">&amp;</span><span class="n">fair_sched_class</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">		   <span class="n">rq</span><span class="o">-&gt;</span><span class="n">nr_running</span> <span class="o">==</span> <span class="n">rq</span><span class="o">-&gt;</span><span class="n">cfs</span><span class="p">.</span><span class="n">h_nr_running</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="n">p</span> <span class="o">=</span> <span class="n">fair_sched_class</span><span class="p">.</span><span class="nf">pick_next_task</span><span class="p">(</span><span class="n">rq</span><span class="p">,</span> <span class="n">prev</span><span class="p">,</span> <span class="n">rf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* 假如没有可调度的进程，跳转again, 选择其他优先级的调度类. */</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="nf">unlikely</span><span class="p">(</span><span class="n">p</span> <span class="o">==</span> <span class="n">RETRY_TASK</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">			<span class="k">goto</span> <span class="n">again</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="cm">/* 假如公平调度类选择的下一个进程属于空闲调度类，直接调用
</span></span></span><span class="line"><span class="cl"><span class="cm">		 * 空闲调度类的pikc_next_task(). */</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="nf">unlikely</span><span class="p">(</span><span class="o">!</span><span class="n">p</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">			<span class="n">p</span> <span class="o">=</span> <span class="n">idle_sched_class</span><span class="p">.</span><span class="nf">pick_next_task</span><span class="p">(</span><span class="n">rq</span><span class="p">,</span> <span class="n">prev</span><span class="p">,</span> <span class="n">rf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="n">p</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">again</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 迭代调度类，从优先级最高的调度类开始，调用对应的pick_next_task
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * 选择下一个进程，假如没有可以调度的进程，就选择一个优先级的调度类. */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">for_each_class</span><span class="p">(</span><span class="n">class</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">p</span> <span class="o">=</span> <span class="n">class</span><span class="o">-&gt;</span><span class="nf">pick_next_task</span><span class="p">(</span><span class="n">rq</span><span class="p">,</span> <span class="n">prev</span><span class="p">,</span> <span class="n">rf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="k">if</span> <span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">			<span class="k">if</span> <span class="p">(</span><span class="nf">unlikely</span><span class="p">(</span><span class="n">p</span> <span class="o">==</span> <span class="n">RETRY_TASK</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">				<span class="k">goto</span> <span class="n">again</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="n">p</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 正常的情况下，不会走到这里，因为空闲调度类总会有可以调度的进程. */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">BUG</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><p>用户进程属于公平调度类，即调用 <code>pick_next_task_fair()</code> 选择下一个运行的进程, 公平调度类会从当前 <code>cfs_rq</code> 即公平调度运行队列中选择虚拟运行时间最小的调度进程，所有的调度进程都由 rb-tree (红黑树)维护.</p>
<h3 id="context_switch"><code>context_switch()</code></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/* linux/kernel/sched/core.c */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="n">__always_inline</span> <span class="k">struct</span> <span class="n">rq</span> <span class="o">*</span>
</span></span><span class="line"><span class="cl"><span class="nf">context_switch</span><span class="p">(</span><span class="k">struct</span> <span class="n">rq</span> <span class="o">*</span><span class="n">rq</span><span class="p">,</span> <span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">prev</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	       <span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">next</span><span class="p">,</span> <span class="k">struct</span> <span class="n">rq_flags</span> <span class="o">*</span><span class="n">rf</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">mm_struct</span> <span class="o">*</span><span class="n">mm</span><span class="p">,</span> <span class="o">*</span><span class="n">oldmm</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 执行进程切换的准备工作，ARM64架构为默认定义: 一个空的宏. */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">prepare_task_switch</span><span class="p">(</span><span class="n">rq</span><span class="p">,</span> <span class="n">prev</span><span class="p">,</span> <span class="n">next</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* mm为下一个选择的进程的内存描述符,
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * old_mm为上一个进程的内存描述符. */</span>
</span></span><span class="line"><span class="cl">	<span class="n">mm</span> <span class="o">=</span> <span class="n">next</span><span class="o">-&gt;</span><span class="n">mm</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="n">oldmm</span> <span class="o">=</span> <span class="n">prev</span><span class="o">-&gt;</span><span class="n">active_mm</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="cm">/* 开始执行上下文切换，ARM64架构依旧使用默认定义，是一个空的宏. */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">arch_start_context_switch</span><span class="p">(</span><span class="n">prev</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">mm</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* 假如mm为空, 即下一个选择的进程为内核线程,
</span></span></span><span class="line"><span class="cl"><span class="cm">		 * 内核线程没有用户虚拟地址空间， 所以需要借用上一个进程的mm_struct,
</span></span></span><span class="line"><span class="cl"><span class="cm">		 * 调用enter_lazy_tlb()通知处理器架构不需要切换用户虚拟地址空间. */</span>
</span></span><span class="line"><span class="cl">		<span class="n">next</span><span class="o">-&gt;</span><span class="n">active_mm</span> <span class="o">=</span> <span class="n">oldmm</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="nf">mmgrab</span><span class="p">(</span><span class="n">oldmm</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nf">enter_lazy_tlb</span><span class="p">(</span><span class="n">oldmm</span><span class="p">,</span> <span class="n">next</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span> <span class="k">else</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* 否则需要进行切换进程的地址空间. */</span>
</span></span><span class="line"><span class="cl">		<span class="nf">switch_mm_irqs_off</span><span class="p">(</span><span class="n">oldmm</span><span class="p">,</span> <span class="n">mm</span><span class="p">,</span> <span class="n">next</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ... */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 切换寄存器和堆栈. */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">switch_to</span><span class="p">(</span><span class="n">prev</span><span class="p">,</span> <span class="n">next</span><span class="p">,</span> <span class="n">prev</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">barrier</span><span class="p">();</span>  <span class="cm">/* 内存屏障 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* finish_task_switch负责进程切换后执行的清理工作. */</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nf">finish_task_switch</span><span class="p">(</span><span class="n">prev</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="用户虚拟地址空间切换">用户虚拟地址空间切换</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/* switch_mm_irqs_off() -&gt; switch_mm() -&gt; __switch_mm() */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* arch/arm64/include/asm/mmu_context.h */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">__switch_mm</span><span class="p">(</span><span class="k">struct</span> <span class="n">mm_struct</span> <span class="o">*</span><span class="n">next</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">cpu</span> <span class="o">=</span> <span class="nf">smp_processor_id</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/*
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * init_mm.pgd does not contain any user mappings and it is always
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * active for kernel addresses in TTBR1. Just set the reserved TTBR0.
</span></span></span><span class="line"><span class="cl"><span class="cm">	 */</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">next</span> <span class="o">==</span> <span class="o">&amp;</span><span class="n">init_mm</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nf">cpu_set_reserved_ttbr0</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">check_and_switch_context</span><span class="p">(</span><span class="n">next</span><span class="p">,</span> <span class="n">cpu</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span>
</span></span><span class="line"><span class="cl"><span class="nf">switch_mm</span><span class="p">(</span><span class="k">struct</span> <span class="n">mm_struct</span> <span class="o">*</span><span class="n">prev</span><span class="p">,</span> <span class="k">struct</span> <span class="n">mm_struct</span> <span class="o">*</span><span class="n">next</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">	  <span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">tsk</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">prev</span> <span class="o">!=</span> <span class="n">next</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* 假如切换的两个进程不共享虚拟地址空间,
</span></span></span><span class="line"><span class="cl"><span class="cm">		 * 调用__switch_mm()进行切换. */</span>
</span></span><span class="line"><span class="cl">		<span class="nf">__switch_mm</span><span class="p">(</span><span class="n">next</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 更新TTBR0寄存器 */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">update_saved_ttbr0</span><span class="p">(</span><span class="n">tsk</span><span class="p">,</span> <span class="n">next</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="寄存器和堆栈切换">寄存器和堆栈切换</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/* switch_to() -&gt; __switch_to() */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cm">/* arch/arm64/kernel/process.c */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">__notrace_funcgraph</span> <span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="nf">__switch_to</span><span class="p">(</span><span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">prev</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">				<span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">next</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">last</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">fpsimd_thread_switch</span><span class="p">(</span><span class="n">next</span><span class="p">);</span>         <span class="cm">/* 切换浮点寄存器 */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">tls_thread_switch</span><span class="p">(</span><span class="n">next</span><span class="p">);</span>            <span class="cm">/* 切换线程本地存储相关的寄存器 */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">hw_breakpoint_thread_switch</span><span class="p">(</span><span class="n">next</span><span class="p">);</span>  <span class="cm">/* 切换调试寄存器 */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">contextidr_thread_switch</span><span class="p">(</span><span class="n">next</span><span class="p">);</span>     <span class="cm">/* 切换上下文标识符寄存器 */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">entry_task_switch</span><span class="p">(</span><span class="n">next</span><span class="p">);</span>            <span class="cm">/* 将下一个进程的task_struct存入CPU的__entry_task */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">uao_thread_switch</span><span class="p">(</span><span class="n">next</span><span class="p">);</span>            <span class="cm">/* 用户访问覆盖相关切换 */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">ptrauth_thread_switch</span><span class="p">(</span><span class="n">next</span><span class="p">);</span>        <span class="cm">/* 指针验证相关切换 */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">ssbs_thread_switch</span><span class="p">(</span><span class="n">next</span><span class="p">);</span>           <span class="cm">/* 推测性执行侧通道相关切换 */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 数据同步屏障 */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">dsb</span><span class="p">(</span><span class="n">ish</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* 切换通用寄存器 */</span>
</span></span><span class="line"><span class="cl">	<span class="n">last</span> <span class="o">=</span> <span class="nf">cpu_switch_to</span><span class="p">(</span><span class="n">prev</span><span class="p">,</span> <span class="n">next</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="n">last</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div><h3 id="进程切换与线程切换">进程切换与线程切换</h3>
<p>通过上面的系统的分析，我们可以发现在Linux内核中并没有区分进程和线程，对于线程和进程，我们可以这么理解:</p>
<ul>
<li>当进程只有一个线程时，可以认为进程就等于线程.</li>
<li>当进程拥有多个线程时，这些线程会共享相同的虚拟地址空间, 虚拟地址空间在上下文切换时是不需要切换的。另外，线程也有自己的私有数据，比如栈和寄存器等，这些在上下文切换时也是需要切换保存的.</li>
</ul>
<h2 id="查看进程上下文切换">查看进程上下文切换</h2>
<p>我们通常所说的上下文切换分为CPU上下文切换和进程上下文切换, 例如C/C++中的系统调用即会执行CPU上下文切换，而系统性能分析工具 <code>vmstat</code> 所显示的也是 CPU 上下文切换和中断的次数. 关于进程上下文切换我们可以利用工具 <code>pidstat</code>:</p>
<p>每隔5秒输出1组数据</p>
<p><img src="/images/pidstat.png" alt="pidstat"></p>
<p>这个结果中有两列内容是我们的重点关注对象。一个是  cswch  ，表示每秒自愿上下文切换（voluntary context switches）的次数，另一个则是  nvcswch  ，表示每秒非自愿上下文切换（non voluntary context switches）的次数. 所谓自愿上下文切换，是指进程无法获取所需资源，导致的上下文切换。比如说， I/O、内存等系统资源不足时，就会发生自愿上下文切换。而非自愿上下文切换，则是指进程由于时间片已到等原因，被系统强制调度，进而发生的上下文切换。比如说，大量进程都在争抢 CPU 时，就容易发生非自愿上下文.</p>
<h2 id="调度时机">调度时机</h2>
<p>调度进程的时机如下:</p>
<ul>
<li>进程主动调用schedule()函数.</li>
<li>周期性调度，抢占当前进程，强迫当前进程让出处理器.</li>
<li>唤醒进程的时候，被唤醒的进程可能会抢占当前进程.</li>
<li>创建新进程的时候, 新进程可能抢占当前进程.</li>
</ul>
<h3 id="主动调度">主动调度</h3>
<p>进程在用户模式下运行的时候，无法直接调用 <code>schedule()</code> 函数, 只能通过系统调用进入内核模式，假如系统调用需要等待某个资源，例如互斥锁(mutex)或者信号量，会将进程的状态设为睡眠状态，然后调用 <code>schedule()</code> 来调度进程.</p>
<p>进程也可以通过系统调用 <code>sched_yield()</code> 让出处理器，这种情况下进程不会进入睡眠.</p>
<h3 id="周期性调度">周期性调度</h3>
<p>Linux内核依靠周期性的时钟中断抢夺处理器的控制权，时钟中断处理程序检查当前进程的执行时间有没有超过限额，如果超过了限额，设置需要重新调度的标志.</p>
<p>在CFS算法中，如果当前调度实体的运行时间超过了前面介绍的<strong>进程的时间片</strong>，那么会设置重新调度的标志位.</p>
<h2 id="修改进程优先级">修改进程优先级</h2>
<ul>
<li>
<p>Linux系统可以通过 <code>renice</code> 设置进程优先级，具体使用方法可以通过 <code>man renice</code>.</p>
</li>
<li>
<p>C/C++编程可以使用以下方法:</p>
</li>
</ul>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c++" data-lang="c++"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;sys/time.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;sys/resource.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">getpriority</span><span class="p">(</span><span class="kt">int</span> <span class="n">which</span><span class="p">,</span> <span class="kt">int</span> <span class="n">who</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">setpriority</span><span class="p">(</span><span class="kt">int</span> <span class="n">which</span><span class="p">,</span> <span class="kt">int</span> <span class="n">who</span><span class="p">,</span> <span class="kt">int</span> <span class="n">prio</span><span class="p">);</span></span></span></code></pre></div><h2 id="参考">参考</h2>
<p>&laquo; Linux 内核深度解析&raquo;</p>
]]></content>
  </entry></feed>


