<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://dbhammer.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://dbhammer.github.io/" rel="alternate" type="text/html" /><updated>2026-03-31T13:17:41+00:00</updated><id>https://dbhammer.github.io/feed.xml</id><title type="html">DBHammer</title><subtitle>The official website of DBHammer, DaSE, ECNU.</subtitle><entry><title type="html">DBHammer Paper List Sharing</title><link href="https://dbhammer.github.io/2024/07/01/DBHammer-Paper-List-Sharing.html" rel="alternate" type="text/html" title="DBHammer Paper List Sharing" /><published>2024-07-01T00:00:00+00:00</published><updated>2026-03-31T13:16:28+00:00</updated><id>https://dbhammer.github.io/2024/07/01/DBHammer%20Paper%20List%20Sharing</id><content type="html" xml:base="https://dbhammer.github.io/2024/07/01/DBHammer-Paper-List-Sharing.html"><![CDATA[<p>DBHammer组整理了一些最新发表在各大顶会/刊的科研论文, 存放在GitHub上, 具体链接如下：</p>
<ul>
  <li>OLTP: https://github.com/wengsy150943/Paper4OLTPandConcurrencyTesting</li>
  <li>OLAP: https://github.com/Wind-Gone/awesome-olap-paper</li>
  <li>AI4DB: https://github.com/Wind-Gone/awesome-ai4db-paper</li>
</ul>]]></content><author><name>胡梓锐</name></author><category term="paperlist" /><summary type="html"><![CDATA[DBHammer组整理了一些最新发表在各大顶会/刊的科研论文, 存放在GitHub上, 具体链接如下： OLTP: https://github.com/wengsy150943/Paper4OLTPandConcurrencyTesting OLAP: https://github.com/Wind-Gone/awesome-olap-paper AI4DB: https://github.com/Wind-Gone/awesome-ai4db-paper]]></summary></entry><entry><title type="html">Polardb for PostgreSQL部署（Ceph+PFS）</title><link href="https://dbhammer.github.io/2022/09/01/Polardb-for-PostgreSQL-%E9%83%A8%E7%BD%B2-Ceph+PFS.html" rel="alternate" type="text/html" title="Polardb for PostgreSQL部署（Ceph+PFS）" /><published>2022-09-01T00:00:00+00:00</published><updated>2026-03-31T13:16:28+00:00</updated><id>https://dbhammer.github.io/2022/09/01/Polardb%20for%20PostgreSQL%20%E9%83%A8%E7%BD%B2%EF%BC%88Ceph+PFS%EF%BC%89</id><content type="html" xml:base="https://dbhammer.github.io/2022/09/01/Polardb-for-PostgreSQL-%E9%83%A8%E7%BD%B2-Ceph+PFS.html"><![CDATA[<h2 id="简介">简介</h2>

<blockquote>
  <ol>
    <li>获取在同一网段的虚拟机三台，互相之间配置 ssh 免密登录，用作 ceph 密钥与配置信息的同步；</li>
    <li>在主节点启动 mon 进程，查看状态，将其他主机添加到ceph集群中；</li>
    <li>在三个环境中启动 osd 进程配置存储盘；</li>
    <li>创建存储池与 rbd 块设备镜像，并对创建好的镜像在各个节点进行映射即可实现块设备的共享；</li>
    <li>对块设备进行 PolarFS 的格式化</li>
    <li>进行一个读写节点，两个只读节点的PolarDB 部署。</li>
  </ol>
</blockquote>

<h2 id="环境要求">环境要求</h2>

<blockquote>
  <p>系统盘25G，数据盘40G，image大小111G</p>

  <p>内存大小&gt;4G，CPU</p>
</blockquote>

<p>ceph的部署需要：</p>

<p>1、Python3</p>

<p>2、Systemd【系统自带，不用安装】</p>

<p>3、运行容器的工具（Docker或Podman）</p>

<p>4、时间同步工具（NTP）</p>

<p>5、用于存储管理的工具（LVM2）【系统自带，不用安装】</p>

<h2 id="一配置docker">一、配置Docker</h2>

<h3 id="1-安装dokcer">1. 安装Dokcer</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum <span class="nb">install</span> <span class="nt">-y</span> yum-utils
yum-config-manager <span class="nt">--add-repo</span> https://download.docker.com/linux/centos/docker-ce.repo
yum <span class="nb">install</span> <span class="nt">-y</span> docker-ce docker-ce-cli containerd.io docker-compose-plugin
</code></pre></div></div>

<h3 id="2-配置国内镜像">2. 配置国内镜像</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo mkdir</span> /etc/docker
<span class="nb">sudo </span>vi   /etc/docker/daemon.json
<span class="c">#输入以下内容</span>
<span class="o">{</span>
 <span class="s2">"registry-mirrors"</span>: <span class="o">[</span><span class="s2">"https://registry.docker-cn.com"</span><span class="o">]</span>
<span class="o">}</span>
</code></pre></div></div>

<h3 id="3-启动docker并配置开机自启">3. 启动Docker，并配置开机自启</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl start docker
systemctl <span class="nb">enable </span>docker 
</code></pre></div></div>

<h2 id="二确定ntp服务启动">二、确定NTP服务启动</h2>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl status ntpd
systemctl start ntpd    <span class="c">#如果发现没有启动，则使用这个命令启动</span>
</code></pre></div></div>

<h2 id="三配置ceph">三、配置Ceph</h2>

<h3 id="1-配置环境">1. 配置环境</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum <span class="nb">install</span> <span class="nt">-y</span> python3 
docker pull quay.io/ceph/ceph:v15
</code></pre></div></div>

<p><img src="/auto-image/picrepo/fb83ce8d-7515-4f0a-9d20-ae8f6f8fa569.png" alt="image-20220831162826337" /></p>
<h3 id="2-下载cephadm">2. 下载Cephadm</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl --silent --remote-name --location https://github.com/ceph/ceph/raw/octopus/src/cephadm/cephadm
chmod +x cephadm
</code></pre></div></div>

<h3 id="3-安装cephadm">3. 安装Cephadm</h3>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./cephadm add-repo --release octopus
./cephadm install
cephadm install ceph-common
</code></pre></div></div>

<h3 id="4-启动cephmon">4. 启动Cephmon</h3>

<p>任意选定一个节点作为ceph集群管理的主节点，通过ceph bootstrap在这个节点创建一个监控和管理的daemon进程，生成ceph集群的密钥文件/etc/ceph/ceph.pub、/etc/ceph/ceph.client.admin.keyring，以及集群的配置文件/etc/ceph/ceph.conf，跳过pull容器镜像quay.io/ceph/ceph:v15。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cephadm bootstrap <span class="nt">--mon-ip</span> <span class="si">$(</span><span class="nb">hostname</span> <span class="nt">-I</span> | <span class="nb">cut</span> <span class="nt">-d</span> <span class="s1">' '</span> <span class="nt">-f1</span><span class="si">)</span>  <span class="nt">--skip-pull</span> <span class="c">#已在三.1拉取</span>
</code></pre></div></div>

<h2 id="四添加主机节点">四、添加主机节点</h2>

<h3 id="1-配置主机间免密登录">1. 配置主机间免密登录</h3>

<p>在每台主机生成自己的公钥和私钥对</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen
</code></pre></div></div>

<p>每台主机把自己的公钥发送给其他主机</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-copy-id -i .ssh/id_rsa.pub root@hostip
</code></pre></div></div>

<h3 id="2-添加当前主机到ceph集群中">2. 添加当前主机到ceph集群中</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ceph orch host add <span class="si">$(</span><span class="nb">hostname</span><span class="si">)</span> <span class="si">$(</span><span class="nb">hostname</span> <span class="nt">-I</span> | <span class="nb">cut</span> <span class="nt">-d</span> <span class="s1">' '</span> <span class="nt">-f1</span><span class="si">)</span>
</code></pre></div></div>

<h3 id="4-添加其他主机到ceph集群中">4. 添加其他主机到ceph集群中</h3>

<h4 id="41-传输ceph集群的公钥给其他节点">4.1 传输ceph集群的公钥给其他节点</h4>

<p>把主节点的/etc/ceph/* 文件内的公私钥对和配置文件信息通过scp的方式传输给其他准备加入ceph集群的主机</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">mkdir</span> /etc/ceph
scp /etc/ceph/<span class="k">*</span> hostip:/etc/ceph/
</code></pre></div></div>

<p><img src="/auto-image/picrepo/fc867bae-3d42-43f0-bf75-54fb352d8e8a.png" alt="image-20220831190318250" /></p>
<h4 id="42-添加其他主机到ceph集群中">4.2 添加其他主机到ceph集群中</h4>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># hostname为主机名，hostip为添加的主机ip，例如添加10.24.14.244的主机名为10-24-14-244</span>
ceph orch host add <span class="nb">hostname </span>hostip
</code></pre></div></div>

<p><img src="/auto-image/picrepo/477c7488-918d-497d-be12-15f07bb573a4.png" alt="image-20220831190242062" /></p>
<h4 id="43-检查ceph集群状态">4.3 检查ceph集群状态</h4>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ceph <span class="nt">-s</span>
</code></pre></div></div>

<p>注意看quorum后面是否加入了3个主机</p>

<p><img src="/auto-image/picrepo/ee45b740-02ae-43da-9692-def21f0081ec.png" alt="image-20220831190346124" /></p>
<h2 id="五添加osd存储">五、添加osd存储</h2>

<p>ceph集群中的所有主机都要执行umount和osd添加</p>

<h3 id="1-卸载准备使用的磁盘块">1. 卸载准备使用的磁盘块</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-t</span> hostip umount /dev/vdb
</code></pre></div></div>

<p><img src="/auto-image/picrepo/474d2a0e-2628-4bd7-9dab-43562b5c35f7.png" alt="image-20220831195351749" /></p>
<h3 id="2-添加每个机器的空闲磁盘块为osd存储">2. 添加每个机器的空闲磁盘块为osd存储</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ceph orch daemon add osd <span class="nb">hostname</span>:/dev/vdb
</code></pre></div></div>

<p><img src="/auto-image/picrepo/bb717fbc-0064-4069-971c-120e4fb9d8fa.png" alt="image-20220831195415812" /></p>
<h2 id="六检查ceph集群状态">六、检查ceph集群状态</h2>

<p>检查ceph集群状态是否为health_ok，同时看看是否所有的节点是否都加入了，osd启动的数量是否正确，可能会存在一定的延迟</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ceph <span class="nt">-s</span>
</code></pre></div></div>

<p><img src="/auto-image/picrepo/bf0884d5-61ab-44a9-9ffb-bd251cc4bf98.png" alt="image-20220831195504682" /></p>
<h2 id="七创建osd-pool并准备rbd块">七、创建osd pool并准备rbd块</h2>

<p>存储池和镜像块的创建（1、2步）仅在主节点执行1次，（3、4步）映射镜像文件需要在所有节点执行</p>

<h3 id="1-创建存储池">1. 创建存储池</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ceph osd pool create rbd_polar
</code></pre></div></div>

<h3 id="2-创建镜像块">2. 创建镜像块</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rbd create <span class="nt">--size</span> 113664 rbd_polar/image01  <span class="c">#小于单个/dev/vdb大小的3倍，这里使用的是111GB</span>
ceph osd pool application <span class="nb">enable </span>rbd_polar rbd   <span class="c">#启动osd pool application</span>
rbd feature disable rbd_polar/image01 object-map fast-diff deep-flatten  <span class="c">#关闭 rbd 不支持特性，才可以执行rbd map</span>
</code></pre></div></div>

<h3 id="3-映射镜像文件">3. 映射镜像文件</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rbd map rbd_polar/image01
rbd device list   <span class="c">#检查是否成功创建映射</span>
</code></pre></div></div>

<p><img src="/auto-image/picrepo/dca05129-5c0c-4ddf-97ce-c10bfa324075.png" alt="image-20220831195722378" /></p>
<h3 id="4-检查是否成功为主机存储识别">4. 检查是否成功为主机存储识别</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lsblk   <span class="c">#检查是否成功出现rbd0</span>
</code></pre></div></div>

<p><img src="/auto-image/picrepo/1ab06593-e52d-4c10-b951-0d5b5999d9d4.png" alt="image-20220831195851208" /></p>
<h2 id="八文件系统准备-配置pfs">八、文件系统准备-配置PFS</h2>

<h3 id="1-pfs的编译安装">1. PFS的编译安装</h3>

<p>【定期更新】DockerHub 上的 PolarDB 开发镜像，其中已经包含了编译完毕的 PFS，无需再次编译安装，直接进入容器即可。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker pull polardb/polardb_pg_devel
docker run <span class="nt">-it</span> <span class="se">\</span>
    <span class="nt">--network</span><span class="o">=</span>host <span class="se">\</span>
    <span class="nt">--cap-add</span><span class="o">=</span>SYS_PTRACE <span class="nt">--privileged</span><span class="o">=</span><span class="nb">true</span> <span class="se">\</span>
    <span class="nt">--name</span> polardb_pg <span class="se">\</span>
    polardb/polardb_pg_devel bash
</code></pre></div></div>

<p><img src="/auto-image/picrepo/fffd3c1b-778c-4db3-bb22-3fe28fba407d.png" alt="image-20220831200630435" /></p>
<h3 id="2为块设备建立软连接">2.为块设备建立软连接</h3>

<p>进入容器后执行，在所有的主机上执行（polarfs不能识别rb开头的磁盘，需要重映射）</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo ln</span> <span class="nt">-s</span> /dev/rbd0 /dev/vdc
</code></pre></div></div>

<p><img src="/auto-image/picrepo/487df318-beba-40c8-a55c-76c8df162f5e.png" alt="image-20220831200654083" /></p>
<h3 id="3-格式化块设备">3. 格式化块设备</h3>

<p>选择<strong>任意</strong>一台主机，在共享存储块设备上格式化 PFS 分布式文件系统</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> /usr/local/bin/pfs <span class="nt">-C</span> disk mkfs vdc
</code></pre></div></div>

<p><img src="/auto-image/picrepo/5cc44dcf-355d-4b6d-b83b-004eb1349a04.png" alt="image-20220831200749606" /></p>
<h3 id="4-pfs文件系统挂载">4. PFS文件系统挂载</h3>

<p>在能够访问共享存储的<strong>所有主机节点</strong>上分别启动 PFS 守护进程并挂载 PFS 文件系统</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> /usr/local/polarstore/pfsd/bin/start_pfsd.sh <span class="nt">-p</span> vdc
</code></pre></div></div>

<p><img src="/auto-image/picrepo/cd29498e-bf8d-400b-af3c-4ad1280cb56d.png" alt="image-20220831200830713" /></p>
<h2 id="九编译部署节点">九、编译部署节点</h2>

<h3 id="1-在所有节点编译polardb内核代码">1. 在所有节点编译polardb内核代码</h3>

<p>从github拉取polardb的POLARDB_11_STABLE分支源代码（Gitee国内镜像）</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone <span class="nt">-b</span> POLARDB_11_STABLE https://gitee.com/mirrors/PolarDB-for-PostgreSQL
</code></pre></div></div>

<p>进入源码目录，使用 <code class="language-plaintext highlighter-rouge">--with-pfsd</code> 选项编译 PolarDB 内核编译</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>PolarDB-for-PostgreSQL/
./polardb_build.sh <span class="nt">--with-pfsd</span>
</code></pre></div></div>

<p><img src="/auto-image/picrepo/1a4da392-cee5-475f-a893-ade5c1a0d402.png" alt="image-20220831210945446" />
脚本在编译完成后，会自动部署一个基于本地文件系统的实例，运行于 <code class="language-plaintext highlighter-rouge">5432</code> 端口上。手动键入以下命令停止这个实例，以便 在 PFS 和共享存储上重新部署实例</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$HOME</span>/tmp_basedir_polardb_pg_1100_bld/bin/pg_ctl <span class="se">\</span>
    <span class="nt">-D</span> <span class="nv">$HOME</span>/tmp_master_dir_polardb_pg_1100_bld/ <span class="se">\</span>
    stop
</code></pre></div></div>

<h3 id="2-配置读写节点读写节点只有一个建议选择主节点">2. 配置读写节点（读写节点只有一个，建议选择主节点）</h3>

<h4 id="21-初始化数据目录homeprimary">2.1 初始化数据目录<code class="language-plaintext highlighter-rouge">$HOME/primary/</code></h4>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$HOME</span>/tmp_basedir_polardb_pg_1100_bld/bin/initdb <span class="nt">-D</span> <span class="nv">$HOME</span>/primary
</code></pre></div></div>

<p><img src="/auto-image/picrepo/3a81e3b6-8746-4efe-ada2-40b0ecd34d28.png" alt="image-20220831211957592" /></p>
<h4 id="22-在共享存储的-vdcshared_data-目录上初始化共享数据目录">2.2 在共享存储的 <code class="language-plaintext highlighter-rouge">/vdc/shared_data/</code> 目录上初始化共享数据目录</h4>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo</span> /usr/local/bin/pfs <span class="nt">-C</span> disk <span class="nb">mkdir</span> /vdc/shared_data   <span class="c"># 使用 pfs 创建共享数据目录</span>
<span class="nb">sudo</span> <span class="nv">$HOME</span>/tmp_basedir_polardb_pg_1100_bld/bin/polar-initdb.sh <span class="nv">$HOME</span>/primary/ /vdc/shared_data/    <span class="c"># 初始化 db 的本地和共享数据目录</span>
</code></pre></div></div>

<p><img src="/auto-image/picrepo/cd6dad27-67c9-42e0-9afd-306537a017bf.png" alt="image-20220831212117596" /></p>
<h4 id="23-编辑读写节点的配置">2.3 编辑读写节点的配置</h4>

<p><code class="language-plaintext highlighter-rouge">$HOME/primary/postgresql.conf</code></p>

<p>添加下方文本内容，同时注意修改polar_disk_name、polar_datadir、synchronous_standby_names</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">port</span><span class="o">=</span>5432   <span class="c">#不同机器上相同端口号，相同机器上不同端口号（5433，5434）</span>
<span class="nv">polar_hostid</span><span class="o">=</span>1    <span class="c">#不同节点上编号不同 1,2,3，...，读写节点编号为1</span>
<span class="nv">polar_enable_shared_storage_mode</span><span class="o">=</span>on
<span class="nv">polar_disk_name</span><span class="o">=</span><span class="s1">'nvme1n1'</span>    <span class="c">#需要修改成空闲磁盘名vdc</span>
<span class="nv">polar_datadir</span><span class="o">=</span><span class="s1">'/nvme1n1/shared_data/'</span>    <span class="c">#需要修改成空闲磁盘名vdc</span>
polar_vfs.localfs_mode<span class="o">=</span>off
<span class="nv">shared_preload_libraries</span><span class="o">=</span><span class="s1">'$libdir/polar_vfs,$libdir/polar_worker'</span>
<span class="nv">polar_storage_cluster_name</span><span class="o">=</span><span class="s1">'disk'</span>
<span class="nv">logging_collector</span><span class="o">=</span>on
<span class="nv">log_line_prefix</span><span class="o">=</span><span class="s1">'%p\t%r\t%u\t%m\t'</span>
<span class="nv">log_directory</span><span class="o">=</span><span class="s1">'pg_log'</span>
<span class="nv">listen_addresses</span><span class="o">=</span><span class="s1">'*'</span>
<span class="nv">max_connections</span><span class="o">=</span>1000
<span class="nv">synchronous_standby_names</span><span class="o">=</span><span class="s1">'replica1,replica2'</span>    <span class="c">#有几个只读节点就写几个replica，如果有三个只读节点，那就是'replica1,replica2,replica3'</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">$HOME/primary/pg_hba.conf</code>中添加行</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>host	replication	postgres	0.0.0.0/0	trust
</code></pre></div></div>

<h4 id="24-启动读写节点">2.4 启动读写节点</h4>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$HOME</span>/tmp_basedir_polardb_pg_1100_bld/bin/pg_ctl start <span class="nt">-D</span> <span class="nv">$HOME</span>/primary
</code></pre></div></div>

<p><img src="/auto-image/picrepo/73e77c35-7ee0-4093-b023-946051067350.png" alt="image-20220831223801672" /></p>
<h4 id="25-检查读写节点">2.5 检查读写节点</h4>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>psql
<span class="se">\l</span>   
create database t<span class="p">;</span>
<span class="se">\l</span>
<span class="se">\c</span> t<span class="p">;</span>
create table t1<span class="o">(</span>k int<span class="o">)</span><span class="p">;</span>
<span class="se">\d</span>
insert into t1 values<span class="o">(</span>1<span class="o">)</span><span class="p">;</span>
<span class="k">select</span> <span class="k">*</span> from t1<span class="p">;</span>
</code></pre></div></div>

<p><img src="/auto-image/picrepo/bb1d8d8b-c106-44fd-b086-402b5317ae4b.png" alt="image-20220831224105853" /></p>
<h4 id="26-为对应的只读节点创建相应的-replication-slot">2.6 为对应的只读节点创建相应的 replication slot</h4>

<p>创建相应的 replication slot，用于只读节点的物理流复制，必须逐个进行replica slot的创建</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$HOME</span>/tmp_basedir_polardb_pg_1100_bld/bin/psql <span class="se">\</span>
    <span class="nt">-p</span> 5432 <span class="se">\</span>
    <span class="nt">-d</span> postgres <span class="se">\</span>
    <span class="nt">-c</span> <span class="s2">"select pg_create_physical_replication_slot('replica1');"</span>

<span class="nv">$HOME</span>/tmp_basedir_polardb_pg_1100_bld/bin/psql <span class="se">\</span>
    <span class="nt">-p</span> 5432 <span class="se">\</span>
    <span class="nt">-d</span> postgres <span class="se">\</span>
    <span class="nt">-c</span> <span class="s2">"select pg_create_physical_replication_slot('replica2');"</span>
</code></pre></div></div>

<p><img src="/auto-image/picrepo/5125e0f3-6c78-47db-82da-0c41eb2e80eb.png" alt="image-20220831223925281" /></p>
<h3 id="3-配置只读节点">3. 配置只读节点</h3>

<p>假设有两个只读节点，分别初始化数据目录<code class="language-plaintext highlighter-rouge">replica1</code>和<code class="language-plaintext highlighter-rouge">replica2</code>，初始化需要定期更新</p>

<h4 id="31-初始化数据目录homereplica1homereplica2">3.1 初始化数据目录<code class="language-plaintext highlighter-rouge">$HOME/replica1/</code>,<code class="language-plaintext highlighter-rouge">$HOME/replica2/</code></h4>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$HOME</span>/tmp_basedir_polardb_pg_1100_bld/bin/initdb <span class="nt">-D</span> <span class="nv">$HOME</span>/replica1
<span class="nv">$HOME</span>/tmp_basedir_polardb_pg_1100_bld/bin/initdb <span class="nt">-D</span> <span class="nv">$HOME</span>/replica2
</code></pre></div></div>

<p><img src="/auto-image/picrepo/e35441d3-9de4-4887-8d53-d7a2196fad91.png" alt="image-20220831224105853" /></p>
<h4 id="32-编辑只读节点的配置">3.2 编辑只读节点的配置</h4>

<p><code class="language-plaintext highlighter-rouge">$HOME/replica1/postgresql.conf</code></p>

<p>添加下方文本内容，注意修改polar_hostid、polar_disk_name、polar_datadir</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">port</span><span class="o">=</span>5432   <span class="c">#不同机器上相同端口号，相同机器上不同端口号（5433，5434）</span>
<span class="nv">polar_hostid</span><span class="o">=</span>2   <span class="c">#不同节点上编号不同,第一个读节点为2，第二个为3，...</span>
<span class="nv">polar_enable_shared_storage_mode</span><span class="o">=</span>on
<span class="nv">polar_disk_name</span><span class="o">=</span><span class="s1">'nvme1n1'</span>    <span class="c">#需要修改成空闲磁盘名vdc</span>
<span class="nv">polar_datadir</span><span class="o">=</span><span class="s1">'/nvme1n1/shared_data/'</span>    <span class="c">#需要修改成空闲磁盘名vdc</span>
polar_vfs.localfs_mode<span class="o">=</span>off
<span class="nv">shared_preload_libraries</span><span class="o">=</span><span class="s1">'$libdir/polar_vfs,$libdir/polar_worker'</span>
<span class="nv">polar_storage_cluster_name</span><span class="o">=</span><span class="s1">'disk'</span>
<span class="nv">logging_collector</span><span class="o">=</span>on
<span class="nv">log_line_prefix</span><span class="o">=</span><span class="s1">'%p\t%r\t%u\t%m\t'</span>
<span class="nv">log_directory</span><span class="o">=</span><span class="s1">'pg_log'</span>
<span class="nv">listen_addresses</span><span class="o">=</span><span class="s1">'*'</span>
<span class="nv">max_connections</span><span class="o">=</span>1000
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">$HOME/replica1/recovery.conf</code></p>

<p>此文件为新建，注意修改primary_slot_name、primary_conninfo</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">polar_replica</span><span class="o">=</span><span class="s1">'on'</span>
<span class="nv">recovery_target_timeline</span><span class="o">=</span><span class="s1">'latest'</span>
<span class="nv">primary_slot_name</span><span class="o">=</span><span class="s1">'replica1'</span>
<span class="nv">primary_conninfo</span><span class="o">=</span><span class="s1">'host=[读写节点所在IP] port=5432 user=postgres dbname=postgres application_name=replica1'</span>
</code></pre></div></div>

<h4 id="33-启动只读节点">3.3 启动只读节点</h4>

<p>根据各个节点修改后运行</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$HOME</span>/tmp_basedir_polardb_pg_1100_bld/bin/pg_ctl start <span class="nt">-D</span> <span class="nv">$HOME</span>/replica1
</code></pre></div></div>

<p><img src="/auto-image/picrepo/6634439c-21a5-4b97-98b5-29afde022a5b.png" alt="image-20220831224432459" /></p>
<h4 id="34-检查只读节点">3.4 检查只读节点</h4>

<p>作为只读节点，无法写入，主要检查两个：1、是否能看到读写节点上写下的数据2、在写入时是否能报不允许</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>psql
<span class="se">\l</span>
<span class="se">\c</span> t
<span class="se">\d</span>
<span class="k">select</span> <span class="k">*</span> from t<span class="p">;</span>
create table tt<span class="o">(</span>k int<span class="o">)</span><span class="p">;</span>
</code></pre></div></div>

<p><img src="/auto-image/picrepo/0f8b99a8-bd2a-4277-bf08-3ca51c5da186.png" alt="202208312359375.png" />
至此，三节点部署已完成</p>

<h2 id="相关bug的可能解决方案">相关bug的可能解决方案</h2>

<h3 id="1-主机没有网络时钟不同步问题">1. 主机没有网络，时钟不同步问题</h3>

<h4 id="11-开启ntpd服务">1.1 开启ntpd服务</h4>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl status ntpd
systemctl start ntpd
</code></pre></div></div>

<h4 id="12-修改etcntpconf配置文件">1.2 修改/etc/ntp.conf配置文件</h4>

<p>修改在要对齐的主机上执行（授权 10.24.14.0-10.24.14.255 网段上的所有机器可以从这台机器上查询和同步时间），对下面这行取消注释</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># restrict 10.24.14.0 mask 255.255.255.0 nomodify notrap</span>
</code></pre></div></div>

<p>对下面这几行添加注释</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server 0.centos.pool.ntp.org iburst
server 1.centos.pool.ntp.org iburst
server 2.centos.pool.ntp.org iburst
server 3.centos.pool.ntp.org iburst
</code></pre></div></div>

<p>添加以下行，当该节点无网络连接，使用本地时间作为时间服务器为其他节点提供时间同步</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server 127.127.1.0 fudge 127.127.1.0 stratum 10
</code></pre></div></div>

<h4 id="13-重启ntpd服务并设置开机自启">1.3 重启ntpd服务并设置开机自启</h4>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl restart ntpd
systemctl <span class="nb">enable </span>ntpd
</code></pre></div></div>

<h4 id="14-对其他机器的操作">1.4 对其他机器的操作</h4>

<p>关闭所有节点机器上的ntpd服务与自启动</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl stop ntpd
<span class="nb">sudo </span>systemctl disable ntpd
</code></pre></div></div>

<p>所有节点机器设置定时任务1 分钟与时间服务器同步一次</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>crontab <span class="nt">-e</span>
<span class="k">*</span>/1 <span class="k">*</span> <span class="k">*</span> <span class="k">*</span> <span class="k">*</span>  <span class="nt">-b</span> /usr/sbin/ntpdate 主机ip
<span class="nb">sudo date</span>
</code></pre></div></div>

<p>手动同步相关命令</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>ntpdate  10.11.6.135
<span class="nb">sudo </span>ntpdate <span class="nt">-s</span> 10.11.6.135
<span class="nb">sudo </span>clockdiff 10.11.6.33
</code></pre></div></div>

<h3 id="2-磁盘格式化时出现正在使用繁忙的问题">2. 磁盘格式化时出现正在使用，繁忙的问题</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>dmsetup status
<span class="nb">sudo </span>dmsetup remove_all
</code></pre></div></div>]]></content><author><name>俞融</name></author><category term="benchmark" /><category term="HTAP" /><summary type="html"><![CDATA[简介]]></summary></entry><entry><title type="html">面向HTAP数据库的基准评测工具研究进展</title><link href="https://dbhammer.github.io/2022/08/02/%E9%9D%A2%E5%90%91HTAP%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E5%9F%BA%E5%87%86%E8%AF%84%E6%B5%8B%E5%B7%A5%E5%85%B7%E7%A0%94%E7%A9%B6%E8%BF%9B%E5%B1%95.html" rel="alternate" type="text/html" title="面向HTAP数据库的基准评测工具研究进展" /><published>2022-08-02T00:00:00+00:00</published><updated>2026-03-31T13:16:28+00:00</updated><id>https://dbhammer.github.io/2022/08/02/%E9%9D%A2%E5%90%91HTAP%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E5%9F%BA%E5%87%86%E8%AF%84%E6%B5%8B%E5%B7%A5%E5%85%B7%E7%A0%94%E7%A9%B6%E8%BF%9B%E5%B1%95</id><content type="html" xml:base="https://dbhammer.github.io/2022/08/02/%E9%9D%A2%E5%90%91HTAP%E6%95%B0%E6%8D%AE%E5%BA%93%E7%9A%84%E5%9F%BA%E5%87%86%E8%AF%84%E6%B5%8B%E5%B7%A5%E5%85%B7%E7%A0%94%E7%A9%B6%E8%BF%9B%E5%B1%95.html"><![CDATA[<p>随着在线实时分析需求的增长，HTAP(Hybrid Transaction and Analitical Process)数据库随之出现，其能在同一个系统内实现 OLTP 负载和 OLAP 负载的高效处理，提供了对新鲜数据的分析能力。近年来，工业界和学术界提出了多种 HTAP 数据库架构，因此如何评测各种新型的 HTAP 数据库引起了学界和业界的广泛关注。</p>

<p>本篇内容主要探讨面向HTAP数据库的基准评测工具，以及研究进展。OceanBase 作为从 OLTP 数据库系统扩展而来的分布式 HTAP 数据库系统，它提供了两种资源隔离方案：OLAP 负载占比低时，在主副本上执行分析任务，以获得实时的数据；OLAP 占比高时，在只读副本上执行分析任务，以实现显式物理隔离，故而 OceanBase 在隔离性和数据库性能上有机会做出比较好的权衡，即可能具有较好的 HTAP 负载支持能力。后续我们也将陆续发布 OceanBase 对 HTAP 负载的支持能力测试报告。</p>

<h2 id="引言">引言</h2>

<p>HTAP 数据库实现的难点在于 TP/AP 的资源隔离和数据同步。因此，除了评测 TP 负载面对高并发负载的性能和 AP 负载面对复杂查询的性能外，现有的 HTAP benchmark 在评估性能的设计上更加关注以下两个问题：</p>

<p><strong>混合负载生成：</strong>生成 TP 和 AP 负载，并且控制 AP 与 TP 负载之间数据访问的交叉。</p>

<p><strong>负载指标：</strong>量化评测混合负载运行时的隔离性，即相互之间的干扰程度。</p>

<p>目前主流的 HTAP 评测基准（工具）有 CH-benCHmark(2011)[1]、HTAPBench(2017)[2]、OLxPBench(2022)[3]、HATtrick(2022)[4]。下面本文将对这 4 个工作从表模式和负载生成、测试方法、控制方法、测试指标等几个方面进行分析和总结。</p>

<h3 id="一ch-benchmark">一、CH-benCHmark</h3>

<p>它于 2011 年被提出，是第一个官方提出的混合负载评测基准，基于标准 OLTP 和 OLAP 基准完成定义。
<img src="/auto-image/picrepo/a8d96710-23c6-47ca-b526-6671be3c02c9.png" alt="图1 CH-benCHmark的TP负载和AP负载运行模式[1]" style="width: 100%;" /></p>

<p class="center">图1 CH-benCHmark的TP负载和AP负载运行模式[1]</p>
<p><strong>表模式和负载：</strong>把 TPC-C 和 TPC-H 表模式进行简单缝合。事务性负载使用 TPC-C 的 5 类负载，分析性负载使用 TPC-H 的 22 个查询。但是这种方式存在 AP 的扫描和 TP 的修改在数据访问空间上的不一致，较小的访问交叉使遇到读写冲突的概率较低，不同类型负载在处理上存在的资源干扰较低。</p>

<p><strong>测试方法：</strong>分别运行 TPC-C 指定比例的事务和改装后类似 TPC-H 的 22 个查询，运行模式见图 1。在测试过程中，通过指定 OLAP 流的数量、OLTP 不同事务的初始占比和客户端数量，分别测量该负载模式下的 TP 和 AP 能力。同时，为了比较两类负载之间的相互干扰情况，测试需要至少进行三组，无 TP 流纯 AP 流负载、无 AP 流纯 TP 流负载和指定数量 TP 流和 AP 流负载，通过控制变量的方式人工地对测试结果进行隔离性、干扰性的分析。</p>

<p><strong>测试指标：</strong>首次采用 
<img src="/auto-image/picrepo/18146342-e343-4bb1-a210-344896859a3f.png" alt="图片" /> 和 
<img src="/auto-image/picrepo/ae0be21a-129d-4fd1-b3ef-6bc16b51cb9b.png" alt="图片" />。虽然指标很客观，但不适用于数据库间的横向比较，适用于个体数据库的性能展示。</p>

<h3 id="二htapbench">二、HTAPBench</h3>

<p>HTAPBench[2] 在 2017 年首次提出以 TP 吞吐量为前提的评测流程。</p>

<p><strong>表模式和负载：</strong>与 CH-benCHmark 所使用的保持一致。</p>

<p><strong>测试方法：</strong>HTAPBench 通过指定应用可以容忍的 OLTP 目标吞吐下限范围，运行足够多的 TP 线程保证满足吞吐的初始下限，在执行过程中再根据 TP 吞吐的实时反馈来确定是否添加 OLAP 流，由此测得保证 TP 吞吐量下的最大 OLAP 能力，运行模式见图 2。这种测量方式包含了对于 TP/AP 之间相互干扰的考虑，只需要执行一次，较简便。</p>

<p><strong>测试指标：</strong>HTAPBench 使用 
<img src="/auto-image/picrepo/1531445c-1fed-440a-b39f-253c188e448f.png" alt="图片" /> 进行单个 worker 性能之间的比较。</p>

<p><strong>分布控制方法：</strong>HTAPBench 提出如何控制分析任务复杂度和查询访问模式这两个问题，主要目的是使得 AP 任务对 TP 生成数据的访问得到控制。同时提出使用密度估计的方法来确定当前数据库的数据分布，使之能够根据当前数据库状态动态确定分析查询。
<img src="/auto-image/picrepo/cee27f8c-2fb2-4285-adbc-366965b24f41.png" alt="图2 HTAPBench运行模式示意图[2]" style="width: 100%;" /></p>

<p class="center">图2 HTAPBench运行模式示意图[2]</p>
<h3 id="三olxpbench">三、OLxPBench</h3>

<p>OLxPBench[3] 是中科院计算所研发的关于 HTAP 数据库基准的评测工具（工具架构见图 3），他们对 HTAP 数据库评测的任务进行分析，得出负载应当满足三个特征，即实时查询、语义一致性和面向特定领域。语义一致性需要 TP 修改的数据都被 AP 访问，实时查询包含实时查询和批查询，要能够模拟用户的行为和客户决策的需求。论文指出 CH-benCHmark 和 HTAPBench 两种基准采用的对原有基准简单缝合的方式以及 TPC-H 查询未能真实展现 TP/AP 之间的干扰是要解决的问题。</p>

<p><strong>表模式和负载：</strong>OLxPBench 设计了面向通用场景(Subenchmark)、金融场景(Finbenchmark)和电信场景(Tabenchmark)的三种负载，包含对于实时查询、语义一致性的查询逻辑设计。Subenchmark 作为通用的负载，参考了 TPC-C 基准表模式的生成，使用 5 个事务 +9 个分析查询 +5 个混合事务；Fibenchmark参考 SmallBank 基准表模式的生成，使用 6 个事务 +4 个分析查询 +6 个混合事务；Tabenchmark 参考 TATP 基准表模式的生成，使用 7 个事务 +5 个分析查询 +6 个混合事务。</p>

<p><strong>测试方法：</strong>与 HTAPBench 相同。</p>

<p><strong>测试指标：</strong>结合了 HTAPBench 和 CH-benCHmark，使用 
<img src="/auto-image/picrepo/a59f8aec-fa23-4afb-b726-327e04983854.png" alt="图片" /> 和
<img src="/auto-image/picrepo/2e1a3fec-1b53-4829-8834-ed4d683cfa5f.png" alt="图片" /> 进行结果的呈现。
<img src="/auto-image/picrepo/21f75943-0a26-426c-bb96-1d7949a6508e.png" alt="图3 OLxPBench架构[3]" style="width: 100%;" /></p>

<p class="center">图3 OLxPBench架构[3]</p>
<h3 id="四hattrick">四、HATtrick</h3>

<p>HATtrick[4] 是威斯康辛大学在 2022 年提出的针对 HTAP 数据库的基准，它提出不同任务之间的隔离性和控制新鲜数据的访问是 HTAP 数据库实现中面临的主要挑战。</p>

<p><strong>表模式和负载：</strong>HATtrick 是从 SSB 表模式中扩展而来，新增了历史记录表、新鲜度记录表以及部分字段；共有两类负载，事务型负载受 TPC-C 启发使用自建的下单事务、付款事务和订单计数事务，分析型负载使用 13 个调整后的 SSB 查询。</p>

<p><strong>测试方式：</strong>给定 TP/AP 客户端数量，同时执行事务和 SSB 的 13 个查询，查询按批的形式连续不断执行，批内查询顺序随机。</p>

<p><strong>测试指标：</strong>针对隔离性和新鲜度提出了两个新的评价指标。首先，提出使用吞吐边界(throughput frontier)的概念，通过二维可视化的方式进行隔离性评测如图4所示。在栅格图中，随着客户端数量变化，线越平行坐标轴隔离性越好；而在综合图中，随扩展系数变化的图像中，吞吐边界线位于比例线之上越接近边界线隔离性能越好，越接近比例线表明事务负载和分析负载之间的代价权衡越多，低于比例线越接近坐标轴表示事务负载和分析负载之间的干扰程度越高，资源竞争越激烈。其次，对新鲜度给出了度量函数（查询发起版本与第一个不可见的 TP 版本之间的时间差）<img src="/auto-image/picrepo/3d82fe33-e761-44c4-bd8c-eabb2849ef27.png" alt="图片" />。
<img src="/auto-image/picrepo/01d02d37-b166-43ab-94fb-8ccda474f6d4.png" alt="图4 各曲线示意图[4]" style="width: 100%;" /></p>

<p class="center">图4 各曲线示意图[4]</p>
<p>根据调研，除了早期的 CH-benCHmark，最近的三款 Benchmark 在评测时明确要求保证 OLTP 的吞吐能力。HTAP 数据库系统上 OLTP 和 OLAP 访问“同一份数据”，而事务处理能力大概率受到同步的影响（新鲜度），如何做好资源共享与资源隔离的权衡[5]是一个难点问题。正如杨传辉在<a href="http://mp.weixin.qq.com/s?__biz=MzU0ODg0OTIyNw==&amp;mid=2247494143&amp;idx=1&amp;sn=26158d95218b74e4b39050036e3e607d&amp;chksm=fbba7adbcccdf3cdaa56a48f5e9a2ddc6ddb01de8d66525b7e30f9882d470856bfd76424f6ce&amp;scene=21#wechat_redirect">《真正的HTAP对用户和开发者意味着什么？》</a>所说，真正的 HTAP 数据库系统要求先有高性能的 OLTP，然后在 OLTP 产生的新鲜数据上支持实时分析 [5]。</p>

<p>通过对现有的典型 HTAP 数据库评测基准的分析，发现已有基准评测工具在表模式和负载生成、测试方法、分布控制方法、测试指标等方面均各有特色，旨在服务于 HTAP 特性的评测。具体来说，HTAPBench考虑了对计算代价的控制，OLxPBench 考虑了实时查询的使用，HATtrick 考虑了新鲜度指标，值得我们参考和学习。</p>

<h2 id="参考文献">参考文献</h2>

<p>[1] Cole R, Funke F, Giakoumakis L, et al. The mixed workload CH-benCHmark[C]//Proceedings of the Fourth International Workshop on Testing Database Systems. 2011: 1-6.</p>

<p>[2] Coelho F, Paulo J, Vilaça R, et al. Htapbench: Hybrid transactional and analytical processing benchmark[C]//Proceedings of the 8th ACM/SPEC on International Conference on Performance Engineering. 2017: 293-304.</p>

<p>[3] Kang G, Wang L, Gao W, et al. OLxPBench: Real-time, Semantically Consistent, and Domain-specific are Essential in Benchmarking, Designing, and Implementing HTAP Systems[J]. arXiv preprint arXiv:2203.16095, 2022.</p>

<p>[4] Milkai E, Chronis Y, Gaffney K P, et al. How Good is My HTAP System?[C]//Proceedings of the 2022 International Conference on Management of Data. 2022: 1810-1824.</p>

<p>[5]<a href="http://mp.weixin.qq.com/s?__biz=MzU0ODg0OTIyNw==&amp;mid=2247494143&amp;idx=1&amp;sn=26158d95218b74e4b39050036e3e607d&amp;chksm=fbba7adbcccdf3cdaa56a48f5e9a2ddc6ddb01de8d66525b7e30f9882d470856bfd76424f6ce&amp;scene=21#wechat_redirect">杨传辉，“真正的HTAP对用户和开发者意味着什么？“</a></p>

<p>[6]弱一致性读，https://open.oceanbase.com/docs/observer-cn/V3.1.4/10000000000449449</p>]]></content><author><name>俞融</name></author><category term="benchmark" /><category term="HTAP" /><summary type="html"><![CDATA[随着在线实时分析需求的增长，HTAP(Hybrid Transaction and Analitical Process)数据库随之出现，其能在同一个系统内实现 OLTP 负载和 OLAP 负载的高效处理，提供了对新鲜数据的分析能力。近年来，工业界和学术界提出了多种 HTAP 数据库架构，因此如何评测各种新型的 HTAP 数据库引起了学界和业界的广泛关注。]]></summary></entry><entry><title type="html">OceanBase性能调优</title><link href="https://dbhammer.github.io/2022/05/26/OceanBase%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98.html" rel="alternate" type="text/html" title="OceanBase性能调优" /><published>2022-05-26T00:00:00+00:00</published><updated>2026-03-31T13:16:28+00:00</updated><id>https://dbhammer.github.io/2022/05/26/OceanBase%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98</id><content type="html" xml:base="https://dbhammer.github.io/2022/05/26/OceanBase%E6%80%A7%E8%83%BD%E8%B0%83%E4%BC%98.html"><![CDATA[<p>本文介绍调试OceanBase代码的方法，以及Nested-Loop-Join性能优化的思路</p>

<h2 id="编译部署oceanbase">编译部署OceanBase</h2>

<ul>
  <li>
    <p>从源代码编译OceanBase</p>

    <p>想要在OceanBase源代码上进行调试，优化，第一件要做的事就是从源代码编译OceanBase。这里推荐克隆<a href="https://github.com/oceanbase/oceanbase/tree/oceanbase_competition">OceanBase数据库大赛的比赛分支</a>，克隆完成之后按照<a href="https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.0/get-the-oceanbase-database-by-using-source-code">官方教程</a>从源码构建OceanBase数据库，其中debug版本可以打断点调试，release版本可以用来测试性能。</p>
  </li>
  <li>
    <p>安装Oceanbase部署工具OBD</p>

    <p>接下来参照<a href="https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.0/use-obd-to-obtain-the-oceanbase-database">官方文档</a>安装OceanBase的部署工具OBD，安装完成之后进入OceanBase源码的编译目录（如build_release），在ocenbase-ce v3.1.0的基础上创建tag为obcompetition的OBD镜像。</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  obd mirror create <span class="nt">-n</span> oceanbase-ce <span class="nt">-V</span> 3.1.0 <span class="nt">-p</span> ./usr/local <span class="nt">-t</span> obcompetition
</code></pre></div>    </div>
  </li>
  <li>
    <p>部署数据库</p>

    <p>创建完镜像之后，可以通过配置文件部署数据库，官方有一些配置文件的<a href="https://github.com/oceanbase/obdeploy/tree/master/example">示例</a>，本文使用的配置文件ob.yaml如下：</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  oceanbase-ce:
    <span class="c"># tag设置为刚才创建镜像obcompetition的tag</span>
  	tag: obcompetition
    servers:
    - name: <span class="nb">test
      </span>ip: 127.0.0.1
    
    global:
  		<span class="c"># home_path需要修改成自己想要部署的目录</span>
      home_path: <span class="k">*****</span>
      devname: lo
      mysql_port: 2881
      rpc_port: 2882
      zone: zone1
      cluster_id: 1
      datafile_size: 10G
      appname: obcompetition
    
    <span class="nb">test</span>:
      syslog_level: INFO
      enable_syslog_recycle: <span class="nb">true
      </span>enable_syslog_wf: <span class="nb">true
      </span>max_syslog_file_count: 4
      memory_limit: 12G
      system_memory: 6G
      cpu_count: 16
</code></pre></div>    </div>

    <p>部署数据库前确定目录home_path为空，之后使用autodeploy自动部署名称为obcompetition的数据库。</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  obd cluster deploy obcompetition <span class="nt">-c</span> ob.yaml
</code></pre></div>    </div>

    <p>启动数据库，创建测试用租户test，并且将除sys租户以外的资源全部给test租户。</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  obd cluster start obcompetition
  obd cluster tenant create obcompetition <span class="nt">--tenant-name</span> <span class="nb">test</span>
</code></pre></div>    </div>

    <p>创建完租户之后就可以通过mysql客户端，连接OceanBase的test租户或者sys租户。</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  mysql <span class="nt">--host</span> 127.0.0.1 <span class="nt">--port</span> 2881 <span class="nt">-uroot</span>@test
  mysql <span class="nt">--host</span> 127.0.0.1 <span class="nt">--port</span> 2881 <span class="nt">-uroot</span>@sys
</code></pre></div>    </div>
  </li>
  <li>
    <p>修改代码后重新部署数据库</p>

    <p>在源代码上进行修改之后，首先需要重新编译代码，然后用编译完的内容替换正在运行的observer。首先查看正在运行的observer所在位置，即bin/observer所在的位置，然后ls -l看出这个observer是一个软连接，想要替换它只需要将软连接连接到刚编译出来的observer二进制文件。</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  ps <span class="nt">-ef</span> | <span class="nb">grep </span>observer
  <span class="nb">ls</span> <span class="nt">-l</span> <span class="k">***</span>/bin/observer
</code></pre></div>    </div>

    <p>最后重新启动obcompetition</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  obd cluster restart obcompetition
</code></pre></div>    </div>
  </li>
</ul>

<h2 id="代码调试工具">代码调试工具</h2>

<hr />

<ul>
  <li>
    <p>vscode远程调试</p>

    <p>在阅读，修改OceanBase源码的时候，需要调试代码，不过OceanBase需要的配置比较高，一般部署在服务器上，这时候使用vscode进行远程调试就比较优雅。</p>

    <p>首先在vscode装一下Remote - SSH插件，打开服务器上的OceanBase源代码目录，然后再Debug界面创建一个新的launch.json文件。</p>

    <p><img src="/auto-image/picrepo/4d564b4d-4dfe-44bf-869b-9b0c6dddf45a.png" alt="创建新的launch.json" /></p>

    <p>创建新的launch.json</p>

    <p>将launch.json替换为下面的配置，”configurations”→”program”需要替换为OceanBase配置文件里对应内容。</p>

    <div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="w">  </span><span class="p">{</span><span class="w">
      </span><span class="nl">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="p">{</span><span class="w">
              </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(gdb) Attach"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cppdbg"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"attach"</span><span class="p">,</span><span class="w">
  						</span><span class="err">//</span><span class="w"> </span><span class="err">program需要替换为/home_path/bin/observer</span><span class="w">
  						</span><span class="err">//</span><span class="w"> </span><span class="err">其中home_path是OceanBase配置文件里的对应内容</span><span class="w">
              </span><span class="nl">"program"</span><span class="p">:</span><span class="w"> </span><span class="s2">"****"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"processId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${input:FindPID}"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"MIMode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gdb"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"sudo"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
              </span><span class="nl">"miDebuggerPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gdb"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"setupCommands"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
                  </span><span class="p">{</span><span class="w">
                      </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Enable pretty-printing for gdb"</span><span class="p">,</span><span class="w">
                      </span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"-enable-pretty-printing"</span><span class="p">,</span><span class="w">
                      </span><span class="nl">"ignoreFailures"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                  </span><span class="p">}</span><span class="w">
              </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">如果调试的时候提示找不到source，还需要自己加上对应的目录映射</span><span class="w">
              </span><span class="nl">"sourceFileMap"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                  </span><span class="nl">"./build_debug/src/observer/./src/observer/omt"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                      </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/observer/omt"</span><span class="w">
                  </span><span class="p">},</span><span class="w">
                  </span><span class="nl">"./build_debug/src/sql/parser/./src/sql/parser"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                      </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/sql/parser"</span><span class="p">,</span><span class="w">
                      </span><span class="nl">"useForBreakpoints"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                  </span><span class="p">},</span><span class="w">
                  </span><span class="nl">"./build_debug/src/sql/./src/sql"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                      </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/sql"</span><span class="p">,</span><span class="w">
                      </span><span class="nl">"useForBreakpoints"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                  </span><span class="p">},</span><span class="w">
                  </span><span class="nl">"./build_debug/src/sql/engine/join/./src/sql/engine/join"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                      </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/sql/engine/join"</span><span class="p">,</span><span class="w">
                      </span><span class="nl">"useForBreakpoints"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                  </span><span class="p">},</span><span class="w">
                  </span><span class="nl">"./build_debug/src/storage/./src/storage"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                      </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/storage"</span><span class="p">,</span><span class="w">
                      </span><span class="nl">"useForBreakpoints"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                  </span><span class="p">},</span><span class="w">
                  </span><span class="nl">"./build_debug/src/rootserver/./src/rootserver"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                      </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/rootserver"</span><span class="p">,</span><span class="w">
                      </span><span class="nl">"useForBreakpoints"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                  </span><span class="p">},</span><span class="w">
                  </span><span class="nl">"./build_debug/src/share/./src/share"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                      </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/share"</span><span class="p">,</span><span class="w">
                      </span><span class="nl">"useForBreakpoints"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                  </span><span class="p">}</span><span class="w">
              </span><span class="p">}</span><span class="w">
          </span><span class="p">}</span><span class="w">
      </span><span class="p">],</span><span class="w">
      </span><span class="nl">"inputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
          </span><span class="p">{</span><span class="w">
              </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FindPID"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"command"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shellCommand.execute"</span><span class="p">,</span><span class="w">
              </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                  </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ps -aux | grep /bin/observer | awk '{print $2}' | head -1"</span><span class="p">,</span><span class="w">
                  </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Select your observer PID"</span><span class="p">,</span><span class="w">
                  </span><span class="nl">"useFirstResult"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
              </span><span class="p">}</span><span class="w">
          </span><span class="p">}</span><span class="w">
      </span><span class="p">]</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span></code></pre></div>    </div>

    <p>然后在服务器上装一个Tasks Shell Input插件，来通过脚本动态获取observer的进程id。</p>

    <p><img src="/auto-image/picrepo/7452acdd-b1e5-46da-b020-6c23b1ac1f16.png" alt="安装Tasks Shell Input插件" /></p>

    <p>安装Tasks Shell Input插件</p>

    <p>这样子在启动observer以后就能成功gdb attach了。</p>

    <p><img src="/auto-image/picrepo/95f5bac3-abc6-4c7c-8340-b5a866b80b2b.png" alt="成功gdb attach" /></p>

    <p>成功gdb attach</p>
  </li>
  <li>
    <p>打日志调试</p>

    <p>vscode调试还是存在一些问题的，比如打断点的位置可能有很多系统进程都会访问（尤其是存储层的代码），mysql客户端输入sql以后，catch住的进程不一定是执行sql的工作线程，函数的调用栈可能不是你想要的，这时候可以通过打日志的方式进行调试。</p>

    <p>OceanBase的日志类型定义在deps/oblib/src/lib/oblog/ob_log_module.h里面，日志目录在/home_path/log，日志内容的格式：</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="o">[</span><span class="nb">time</span><span class="o">]</span>log_level[module_name]function_name<span class="o">(</span>filename:file_no<span class="o">)[</span>thread_id]
  <span class="o">[</span>Ytrace_id0_trace_id1][log<span class="o">=</span>last_log_print_time]log_data
    
  <span class="c">#time 日志记录时间</span>
  <span class="c">#log_level 日志级别</span>
  <span class="c">#module_name 模块名</span>
  <span class="c">#filename:file_no 文件名:行号</span>
  <span class="c">#thread_id 线程id</span>
</code></pre></div>    </div>
  </li>
</ul>

<p>此外，官方也有讲<a href="https://github.com/oceanbase/oceanbase/wiki/how_to_debug">一些调试手段</a>。</p>

<h2 id="性能测试工具sysbench">性能测试工具SysBench</h2>

<hr />

<p>OceanBase数据库大赛使用SysBench进行性能测试，首先在测试机（客户端）上<a href="https://github.com/akopytov/sysbench">安装sysbench</a>。</p>

<ul>
  <li>
    <p>subplan.lua</p>

    <p>性能测试使用的是sysbench的subplan.lua脚本，该脚本在sysbench安装目录内，脚本里的schema为两张表t1和t2。</p>

    <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">t1</span><span class="p">(</span>
  	<span class="n">c1</span> <span class="nb">int</span> <span class="k">primary</span> <span class="k">key</span><span class="p">,</span> 
  	<span class="n">c2</span> <span class="nb">int</span><span class="p">,</span> 
  	<span class="n">c3</span> <span class="nb">int</span><span class="p">,</span> 
  	<span class="n">v1</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v2</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v3</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v4</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v5</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v6</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v7</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v8</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v9</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">)</span>
  <span class="p">);</span>
    
  <span class="k">CREATE</span> <span class="k">TABLE</span> <span class="n">t2</span><span class="p">(</span>
  	<span class="n">c1</span> <span class="nb">int</span> <span class="k">primary</span> <span class="k">key</span><span class="p">,</span> 
  	<span class="n">c2</span> <span class="nb">int</span><span class="p">,</span> 
  	<span class="n">c3</span> <span class="nb">int</span><span class="p">,</span> 
  	<span class="n">v1</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v2</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v3</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v4</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v5</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v6</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v7</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v8</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">),</span> 
  	<span class="n">v9</span> <span class="nb">CHAR</span><span class="p">(</span><span class="mi">60</span><span class="p">)</span>
  <span class="p">)</span>
</code></pre></div>    </div>

    <p>t1，t2表建完后插入数据。</p>

    <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">t1</span> <span class="p">(</span><span class="n">c1</span><span class="p">,</span> <span class="n">c2</span><span class="p">,</span> <span class="n">c3</span><span class="p">,</span> <span class="n">v1</span><span class="p">,</span> <span class="n">v2</span><span class="p">,</span> <span class="n">v3</span><span class="p">,</span> <span class="n">v4</span><span class="p">,</span> <span class="n">v5</span><span class="p">,</span> <span class="n">v6</span><span class="p">,</span> <span class="n">v7</span><span class="p">,</span> <span class="n">v8</span><span class="p">,</span> <span class="n">v9</span><span class="p">)</span> <span class="k">VALUES</span><span class="p">(...);</span>
  <span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">t2</span> <span class="p">(</span><span class="n">c1</span><span class="p">,</span> <span class="n">c2</span><span class="p">,</span> <span class="n">c3</span><span class="p">,</span> <span class="n">v1</span><span class="p">,</span> <span class="n">v2</span><span class="p">,</span> <span class="n">v3</span><span class="p">,</span> <span class="n">v4</span><span class="p">,</span> <span class="n">v5</span><span class="p">,</span> <span class="n">v6</span><span class="p">,</span> <span class="n">v7</span><span class="p">,</span> <span class="n">v8</span><span class="p">,</span> <span class="n">v9</span><span class="p">)</span> <span class="k">VALUES</span><span class="p">(...);</span>
</code></pre></div>    </div>

    <p>插入数据后在t2表建索引，由于两个索引键都非主键，这两个索引都是二级索引，在查内表时会有一个回表操作，也就是根据索引键查询主键对应的行数据。</p>

    <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">create</span> <span class="k">index</span> <span class="n">t2_i1</span> <span class="k">on</span> <span class="n">t2</span><span class="p">(</span><span class="n">c2</span><span class="p">)</span> <span class="k">local</span><span class="p">;</span>
  <span class="k">create</span> <span class="k">index</span> <span class="n">t2_i2</span> <span class="k">on</span> <span class="n">t2</span><span class="p">(</span><span class="n">c3</span><span class="p">)</span> <span class="k">local</span><span class="p">;</span>
</code></pre></div>    </div>

    <p>Select操作限定外表为200个元素的范围查询，通过Hint强制使用Nested-Loop-Join（Index Nested-Loop-Join），并且在c2，c3列进行等值连接。</p>

    <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="k">select</span> <span class="cm">/*+ordered use_nl(A,B)*/</span> <span class="o">*</span> 
  <span class="k">from</span> <span class="n">t1</span> <span class="n">A</span><span class="p">,</span> <span class="n">t2</span> <span class="n">B</span> 
  <span class="k">where</span> <span class="n">A</span><span class="p">.</span><span class="n">c1</span> <span class="o">&gt;=</span> <span class="o">?</span> <span class="k">and</span> <span class="n">A</span><span class="p">.</span><span class="n">c1</span> <span class="o">&lt;</span> <span class="o">?</span> <span class="k">and</span> <span class="n">A</span><span class="p">.</span><span class="n">c2</span> <span class="o">=</span> <span class="n">B</span><span class="p">.</span><span class="n">c2</span> <span class="k">and</span> <span class="n">A</span><span class="p">.</span><span class="n">c3</span> <span class="o">=</span> <span class="n">B</span><span class="p">.</span><span class="n">c3</span>
</code></pre></div>    </div>

    <p>explain看一下OceanBase的查询执行计划，A表是一个全表的scan，查出200行数据作为内表，在一次查询内，对于内表的每一行数据，B表作为外表可以通过t2_i2索引快速定位到相对应的匹配的数据。</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="o">===============================================</span>
  |ID|OPERATOR        |NAME    |EST. ROWS|COST  |
  <span class="nt">-----------------------------------------------</span>
  |0 |NESTED-LOOP JOIN|        |193      |138185|
  |1 | TABLE SCAN     |A       |200      |188   |
  |2 | TABLE SCAN     |B<span class="o">(</span>t2_i2<span class="o">)</span>|1        |690   |
  <span class="o">===============================================</span>
    
  Outputs &amp; filters: 
  <span class="nt">-------------------------------------</span>
  0 - output<span class="o">([</span>A.c1], <span class="o">[</span>A.c2], <span class="o">[</span>A.c3], <span class="o">[</span>A.v1], <span class="o">[</span>A.v2], <span class="o">[</span>A.v3], <span class="o">[</span>A.v4], <span class="o">[</span>A.v5], <span class="o">[</span>A.v6], <span class="o">[</span>A.v7], <span class="o">[</span>A.v8], <span class="o">[</span>A.v9], <span class="o">[</span>B.c1], <span class="o">[</span>B.c2], <span class="o">[</span>B.c3], <span class="o">[</span>B.v1], <span class="o">[</span>B.v2], 
  		<span class="o">[</span>B.v3], <span class="o">[</span>B.v4], <span class="o">[</span>B.v5], <span class="o">[</span>B.v6], <span class="o">[</span>B.v7], <span class="o">[</span>B.v8], <span class="o">[</span>B.v9]<span class="o">)</span>, filter<span class="o">(</span>nil<span class="o">)</span>,
      conds<span class="o">(</span>nil<span class="o">)</span>, nl_params_<span class="o">([</span>A.c2], <span class="o">[</span>A.c3]<span class="o">)</span>, <span class="nv">batch_join</span><span class="o">=</span><span class="nb">false
    
  </span>1 - output<span class="o">([</span>A.c1], <span class="o">[</span>A.c2], <span class="o">[</span>A.c3], <span class="o">[</span>A.v1], <span class="o">[</span>A.v2], <span class="o">[</span>A.v3], <span class="o">[</span>A.v4], <span class="o">[</span>A.v5], <span class="o">[</span>A.v6], <span class="o">[</span>A.v7], <span class="o">[</span>A.v8], <span class="o">[</span>A.v9]<span class="o">)</span>, filter<span class="o">(</span>nil<span class="o">)</span>,
  	  access<span class="o">([</span>A.c1], <span class="o">[</span>A.c2], <span class="o">[</span>A.c3], <span class="o">[</span>A.v1], <span class="o">[</span>A.v2], <span class="o">[</span>A.v3], <span class="o">[</span>A.v4], <span class="o">[</span>A.v5], <span class="o">[</span>A.v6], <span class="o">[</span>A.v7], <span class="o">[</span>A.v8], <span class="o">[</span>A.v9]<span class="o">)</span>, partitions<span class="o">(</span>p0<span class="o">)</span>,
  	  <span class="nv">is_index_back</span><span class="o">=</span><span class="nb">false</span>,
  	  range_key<span class="o">([</span>A.c1]<span class="o">)</span>, range[200 <span class="p">;</span> 400<span class="o">)</span>,
  	  range_cond<span class="o">([</span>A.c1 <span class="o">&gt;=</span> 200], <span class="o">[</span>A.c1 &lt; 400]<span class="o">)</span>
    
  2 - output<span class="o">([</span>B.c2], <span class="o">[</span>B.c3], <span class="o">[</span>B.c1], <span class="o">[</span>B.v1], <span class="o">[</span>B.v2], <span class="o">[</span>B.v3], <span class="o">[</span>B.v4], <span class="o">[</span>B.v5], <span class="o">[</span>B.v6], <span class="o">[</span>B.v7], <span class="o">[</span>B.v8], <span class="o">[</span>B.v9]<span class="o">)</span>, filter<span class="o">([</span>? <span class="o">=</span> B.c2]<span class="o">)</span>,
  		access<span class="o">([</span>B.c2], <span class="o">[</span>B.c3], <span class="o">[</span>B.c1], <span class="o">[</span>B.v1], <span class="o">[</span>B.v2], <span class="o">[</span>B.v3], <span class="o">[</span>B.v4], <span class="o">[</span>B.v5], <span class="o">[</span>B.v6], <span class="o">[</span>B.v7], <span class="o">[</span>B.v8], <span class="o">[</span>B.v9]<span class="o">)</span>, partitions<span class="o">(</span>p0<span class="o">)</span>,
  		<span class="nv">is_index_back</span><span class="o">=</span><span class="nb">true</span>, filter_before_indexback[false],
  		range_key<span class="o">([</span>B.c3], <span class="o">[</span>B.c1]<span class="o">)</span>, range<span class="o">(</span>MIN <span class="p">;</span> MAX<span class="o">)</span>,
  		range_cond<span class="o">([</span>? <span class="o">=</span> B.c3]<span class="o">)</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>测试脚本</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nv">USER</span><span class="o">=</span>root@test
  <span class="nv">DB</span><span class="o">=</span><span class="nb">test
  </span><span class="nv">HOST</span><span class="o">=</span>127.0.0.1
  <span class="nv">PORT</span><span class="o">=</span>2881
  <span class="nv">THREADS</span><span class="o">=</span>128
  <span class="nv">TABLE_SIZE</span><span class="o">=</span>100000
  <span class="nv">TABLES</span><span class="o">=</span>3
  <span class="nv">TIME</span><span class="o">=</span>300
  <span class="nv">REPORT_INTERVAL</span><span class="o">=</span>10
    
  sysbench <span class="nt">--db-ps-mode</span><span class="o">=</span>disable <span class="nt">--mysql-host</span><span class="o">=</span><span class="nv">$HOST</span> <span class="nt">--mysql-port</span><span class="o">=</span><span class="nv">$PORT</span> <span class="se">\</span>
           <span class="nt">--rand-type</span><span class="o">=</span>uniform <span class="nt">--rand-seed</span><span class="o">=</span>26765 <span class="se">\</span>
           <span class="nt">--mysql-user</span><span class="o">=</span><span class="nv">$USER</span> <span class="nt">--mysql-db</span><span class="o">=</span><span class="nv">$DB</span> <span class="se">\</span>
           <span class="nt">--threads</span><span class="o">=</span><span class="nv">$THREADS</span> <span class="se">\</span>
           <span class="nt">--tables</span><span class="o">=</span><span class="nv">$TABLES</span> <span class="nt">--table_size</span><span class="o">=</span><span class="nv">$TABLE_SIZE</span> <span class="se">\</span>
           <span class="nt">--time</span><span class="o">=</span><span class="nv">$TIME</span> <span class="nt">--report-interval</span><span class="o">=</span><span class="nv">$REPORT_INTERVAL</span> <span class="se">\</span>
           subplan cleanup
    
  sysbench <span class="nt">--db-ps-mode</span><span class="o">=</span>disable <span class="nt">--mysql-host</span><span class="o">=</span><span class="nv">$HOST</span> <span class="nt">--mysql-port</span><span class="o">=</span><span class="nv">$PORT</span> <span class="se">\</span>
           <span class="nt">--rand-type</span><span class="o">=</span>uniform <span class="nt">--rand-seed</span><span class="o">=</span>26765 <span class="se">\</span>
           <span class="nt">--mysql-user</span><span class="o">=</span><span class="nv">$USER</span> <span class="nt">--mysql-db</span><span class="o">=</span><span class="nv">$DB</span> <span class="se">\</span>
           <span class="nt">--threads</span><span class="o">=</span><span class="nv">$THREADS</span> <span class="se">\</span>
           <span class="nt">--tables</span><span class="o">=</span><span class="nv">$TABLES</span> <span class="nt">--table_size</span><span class="o">=</span><span class="nv">$TABLE_SIZE</span> <span class="se">\</span>
           <span class="nt">--time</span><span class="o">=</span><span class="nv">$TIME</span> <span class="nt">--report-interval</span><span class="o">=</span><span class="nv">$REPORT_INTERVAL</span> <span class="se">\</span>
           subplan prepare
    
  sysbench <span class="nt">--db-ps-mode</span><span class="o">=</span>disable <span class="nt">--mysql-host</span><span class="o">=</span><span class="nv">$HOST</span> <span class="nt">--mysql-port</span><span class="o">=</span><span class="nv">$PORT</span> <span class="se">\</span>
           <span class="nt">--rand-type</span><span class="o">=</span>uniform <span class="nt">--rand-seed</span><span class="o">=</span>26765 <span class="se">\</span>
           <span class="nt">--mysql-user</span><span class="o">=</span><span class="nv">$USER</span> <span class="nt">--mysql-db</span><span class="o">=</span><span class="nv">$DB</span> <span class="se">\</span>
           <span class="nt">--threads</span><span class="o">=</span><span class="nv">$THREADS</span> <span class="se">\</span>
           <span class="nt">--tables</span><span class="o">=</span><span class="nv">$TABLES</span> <span class="nt">--table_size</span><span class="o">=</span><span class="nv">$TABLE_SIZE</span> <span class="se">\</span>
           <span class="nt">--time</span><span class="o">=</span><span class="nv">$TIME</span> <span class="nt">--report-interval</span><span class="o">=</span><span class="nv">$REPORT_INTERVAL</span> <span class="se">\</span>
           subplan run
</code></pre></div>    </div>
  </li>
</ul>

<h2 id="性能调优">性能调优</h2>

<hr />

<ul>
  <li>
    <p>iotop</p>

    <p>首先看一下sysbench测试过程中，observer的磁盘IO情况，这里选用iotop来从系统/proc目录下读取进程的IO信息进行汇总。</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nb">sudo </span>iotop <span class="nt">-o</span>
</code></pre></div>    </div>

    <p><img src="/auto-image/picrepo/2a9eed10-7bec-4c0f-b727-ee6629a1e4ab.png" alt="Untitled" /></p>

    <p><img src="/auto-image/picrepo/0d7ad7c8-d6b3-4dc4-8316-abe9c4e94818.png" alt="Untitled" /></p>

    <p><img src="/auto-image/picrepo/5d02b676-e41f-4629-99cc-820acdd1fd6b.png" alt="sysbench过程中observer的磁盘IO情况" /></p>

    <p>sysbench过程中observer的磁盘IO情况</p>

    <p>可以看出在sysbench测试一段时间后，只有一些异步日志落盘和事务redo日志会产生写IO，读请求的内容不多，应该被cache在内存里了。再加上官方给的优化建议也是从内存优化入手，我们可以把重心放在内存优化上。</p>
  </li>
  <li>
    <p>perf</p>

    <p>perf是一个轻量级的profiling工具。perf top可以实时打印采样函数，显示出花费大部分CPU时间的函数。</p>

    <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="nb">sudo </span>perf top <span class="nt">-p</span> observer_pid
</code></pre></div>    </div>

    <p><img src="/auto-image/picrepo/1bd53e75-5d13-4fb0-918e-87d1772927fd.png" alt="perf top查看热点函数" /></p>

    <p>perf top查看热点函数</p>

    <p>perf top返回的界面还可以交互，通过annotate跳进函数，还可以看到每个指令的耗时占比。不过返回的都是反汇编的结果，难以将其与源代码联系起来。</p>

    <p><img src="/auto-image/picrepo/6f23f286-d8cf-400a-974e-34c32623c488.png" alt="annotate查看函数每条指令的执行时间占比" /></p>

    <p>annotate查看函数每条指令的执行时间占比</p>

    <p>perf stat还能看程序的branch-misses情况。</p>

    <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  sudo perf stat -p observer_pid
</code></pre></div>    </div>

    <p><img src="/auto-image/picrepo/f3bd086f-7b88-425a-814b-871ae1a1a027.png" alt="Untitled" /></p>
  </li>
  <li>
    <p>FlameGraph</p>

    <p>perf工具的采样结果需要在终端一个个点开函数才能看到调用栈的信息，比较难以对代码的执行流程有一个宏观上的认识，<a href="https://github.com/brendangregg/FlameGraph">FlameGraph</a>能够帮助我们可视化perf采样的结果。</p>

    <p><img src="/auto-image/picrepo/eac60029-cc3f-4a36-a32d-0a9b661fbe3c.png" alt="NestedLoopJoin算子的三个主要部分" /></p>

    <p>NestedLoopJoin算子的三个主要部分</p>

    <p>跑sysbench的同时跑一下火焰图，可以看到在NLJ的负载模式下，OceanBase的NestedLoopJoin物理算子执行流程主要包含三个部分（三个蓝色箭头指示），中间部分是对左表的扫描，右边部分是根据左表的每一行，先通过B.c3列（其实就是A.c3列的值）查询索引t2_i2，获取到rowkey后再查询t2，左边部分是左表每一行匹配完，与右表完成Join之后，会重置右表的扫描状态。</p>
  </li>
</ul>

<h2 id="一些优化点">一些优化点</h2>

<ul>
  <li>
    <p>优化右表回表逻辑</p>

    <p>右表会从索引表一次拿batch rowkeys，然后根据rowkeys数组通过ObMultipleGetMerge查询主表，从火焰图可以看出，这一块占了很大的比重。事实上，如果从索引表只拿到一个rowkey，可以使用ObSingleMerge查询主表，效率更高。</p>

    <p><img src="/auto-image/picrepo/446dc749-0f4b-43b2-8289-ed1f10740d2f.png" alt="原始代码的右表查索引表和回表过程" /></p>

    <p>原始代码的右表查索引表和回表过程</p>

    <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  diff --git a/src/storage/ob_index_merge.cpp b/src/storage/ob_index_merge.cpp
  index e6386773..82c59ba4 100644
  --- a/src/storage/ob_index_merge.cpp
  +++ b/src/storage/ob_index_merge.cpp
  @@ -30,7 +30,9 @@ ObIndexMerge::ObIndexMerge()
         rowkeys_(),
         rowkey_allocator_(ObModIds::OB_SSTABLE_GET_SCAN),
         rowkey_range_idx_(),
  -      index_range_array_cursor_(0)
  +      index_range_array_cursor_(0),
  +      table_iter_single_(),
  +      is_single_(0)
   {}
     
   ObIndexMerge::~ObIndexMerge()
  @@ -49,12 +51,20 @@ void ObIndexMerge::reset()
     rowkey_allocator_.reset();
     rowkey_range_idx_.reset();
     index_range_array_cursor_ = 0;
  +  if (is_single_) {
  +    table_iter_single_.reset();
  +    is_single_ = 0;
  +  }
   }
     
   void ObIndexMerge::reuse()
   {
     table_iter_.reuse();
     index_range_array_cursor_ = 0;
  +  // if (is_single_) {
  +  //   table_iter_single_.reuse();
  +  //   is_single_ = 0;
  +  // }
   }
     
   int ObIndexMerge::open(ObQueryRowIterator&amp; index_iter)
  @@ -71,11 +81,14 @@ int ObIndexMerge::init(const ObTableAccessParam&amp; param, const ObTableAccessParam
     int ret = OB_SUCCESS;
     if (OB_FAIL(table_iter_.init(param, context, get_table_param))) {
       STORAGE_LOG(WARN, "Fail to init table iter, ", K(ret));
  +  } else if (OB_FAIL(table_iter_single_.init(param, context, get_table_param))) {
  +    STORAGE_LOG(WARN, "Fail to init table single iter, ", K(ret));
     } else {
       index_param_ = &amp;index_param;
       access_ctx_ = &amp;context;
       rowkey_cnt_ = param.iter_param_.rowkey_cnt_;
     }
  +  is_single_ = 0;
     return ret;
   }
     
  @@ -107,7 +120,6 @@ int ObIndexMerge::get_next_row(ObStoreRow*&amp; row)
             ObExtStoreRowkey dest_key;
             rowkeys_.reuse();
             rowkey_allocator_.reuse();
  -          table_iter_.reuse();
             access_ctx_-&gt;allocator_-&gt;reuse();
             for (int64_t i = 0; OB_SUCC(ret) &amp;&amp; i &lt; MAX_NUM_PER_BATCH; ++i) {
               if (OB_FAIL(index_iter_-&gt;get_next_row(index_row))) {
  @@ -139,10 +151,21 @@ int ObIndexMerge::get_next_row(ObStoreRow*&amp; row)
             }
     
             if (OB_SUCC(ret)) {
  -            if (OB_FAIL(table_iter_.open(rowkeys_))) {
  -              STORAGE_LOG(WARN, "fail to open iterator", K(ret));
  +            if (1 == rowkeys_.count()) {
  +              table_iter_single_.reuse();
  +              is_single_ = 1;
  +              if (OB_FAIL(table_iter_single_.open(rowkeys_[0]))) {
  +                STORAGE_LOG(WARN, "fail to open iterator", K(ret));
  +              } else {
  +                main_iter_ = &amp;table_iter_single_;
  +              }
               } else {
  -              main_iter_ = &amp;table_iter_;
  +              table_iter_.reuse();
  +              if (OB_FAIL(table_iter_.open(rowkeys_))) {
  +                STORAGE_LOG(WARN, "fail to open iterator", K(ret));
  +              } else {
  +                main_iter_ = &amp;table_iter_;
  +              }
               }
             }
           }
  diff --git a/src/storage/ob_index_merge.h b/src/storage/ob_index_merge.h
  index e7a6cde5..cdeb0da1 100644
  --- a/src/storage/ob_index_merge.h
  +++ b/src/storage/ob_index_merge.h
  @@ -19,6 +19,7 @@
   #include "storage/ob_multiple_get_merge.h"
   #include "storage/ob_query_iterator_util.h"
   #include "storage/blocksstable/ob_block_sstable_struct.h"
  +#include "storage/ob_single_merge.h"
     
   namespace oceanbase {
   namespace storage {
  @@ -50,6 +51,8 @@ class ObIndexMerge : public ObQueryRowIterator {
     common::ObArenaAllocator rowkey_allocator_;
     ObArray&lt;int64_t&gt; rowkey_range_idx_;
     int64_t index_range_array_cursor_;
  +  ObSingleMerge table_iter_single_;
  +  int is_single_;
     
   private:
     DISALLOW_COPY_AND_ASSIGN(ObIndexMerge);
  diff --git a/src/storage/ob_single_merge.cpp b/src/storage/ob_single_merge.cpp
  index 42a34425..3c76aa0f 100644
  --- a/src/storage/ob_single_merge.cpp
  +++ b/src/storage/ob_single_merge.cpp
  @@ -141,9 +141,10 @@ int ObSingleMerge::inner_get_next_row(ObStoreRow&amp; row)
       int64_t end_table_idx = 0;
       int64_t row_cache_snapshot_version = 0;
       const ObIArray&lt;ObITable*&gt;&amp; tables = tables_handle_.get_tables();
  -    const bool enable_fuse_row_cache = is_x86() &amp;&amp; access_ctx_-&gt;use_fuse_row_cache_ &amp;&amp;
  -                                       access_param_-&gt;iter_param_.enable_fuse_row_cache() &amp;&amp;
  -                                       access_ctx_-&gt;fuse_row_cache_hit_rate_ &gt; 6;
  +    // const bool enable_fuse_row_cache = is_x86() &amp;&amp; access_ctx_-&gt;use_fuse_row_cache_ &amp;&amp;
  +    //                                    access_param_-&gt;iter_param_.enable_fuse_row_cache() &amp;&amp;
  +    //                                    access_ctx_-&gt;fuse_row_cache_hit_rate_ &gt; 6;
  +    const bool enable_fuse_row_cache = false;
       access_ctx_-&gt;query_flag_.set_not_use_row_cache();
       const int64_t table_cnt = tables.count();
       ObITable* table = NULL;
</code></pre></div>    </div>

    <p><img src="/auto-image/picrepo/353362bb-1b37-411c-8fcf-b510231d8427.png" alt="rowkey为1时使用SingleMerge回表" /></p>

    <p>rowkey为1时使用SingleMerge回表</p>
  </li>
  <li>
    <p>rescan过程中尽量少进行对象析构</p>

    <p>从火焰图中可以看出，reuse_row_iters会析构掉很多对象，优化思路是保证这些被析构的对象在整个查询过程中始终内存有效，并且每次rescan时重置一些状态。</p>

    <p><img src="/auto-image/picrepo/b5f00979-65b3-4d3b-b771-067faf285e27.png" alt="reuse row iters的内容" /></p>

    <p>reuse row iters的内容</p>

    <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  diff --git a/src/storage/memtable/ob_memtable.cpp b/src/storage/memtable/ob_memtable.cpp
  index 3a4d28f7..ba7d295d 100644
  --- a/src/storage/memtable/ob_memtable.cpp
  +++ b/src/storage/memtable/ob_memtable.cpp
  @@ -1033,7 +1033,7 @@ int ObMemtable::get(const storage::ObTableIterParam&amp; param, storage::ObTableAcce
       TRANS_LOG(WARN, "invalid argument, ", K(ret), K(param), K(context));
     } else if (OB_FAIL(context.store_ctx_-&gt;mem_ctx_-&gt;get_trans_status())) {
       TRANS_LOG(WARN, "trans already end", K(ret));
  -  } else if (NULL == (get_iter_buffer = context.allocator_-&gt;alloc(sizeof(ObMemtableGetIterator))) ||
  +  } else if (NULL == (get_iter_buffer = context.stmt_allocator_-&gt;alloc(sizeof(ObMemtableGetIterator))) ||
                NULL == (get_iter_ptr = new (get_iter_buffer) ObMemtableGetIterator())) {
       TRANS_LOG(WARN, "construct ObMemtableGetIterator fail");
       ret = OB_ALLOCATE_MEMORY_FAILED;
  @@ -1082,7 +1082,7 @@ int ObMemtable::scan(const storage::ObTableIterParam&amp; param, storage::ObTableAcc
     } else {
       if (param.is_multi_version_minor_merge_) {
         if (GCONF._enable_sparse_row) {
  -        if (NULL == (scan_iter_buffer = context.allocator_-&gt;alloc(sizeof(ObMemtableMultiVersionScanSparseIterator))) ||
  +        if (NULL == (scan_iter_buffer = context.stmt_allocator_-&gt;alloc(sizeof(ObMemtableMultiVersionScanSparseIterator))) ||
               NULL == (scan_iter_ptr = new (scan_iter_buffer) ObMemtableMultiVersionScanSparseIterator())) {
             TRANS_LOG(WARN,
                 "construct ObMemtableMultiVersionScanSparseIterator fail",
  @@ -1099,7 +1099,7 @@ int ObMemtable::scan(const storage::ObTableIterParam&amp; param, storage::ObTableAcc
             TRANS_LOG(WARN, "scan iter init fail", "ret", ret, K(real_range), K(param), K(context));
           }
         } else {
  -        if (NULL == (scan_iter_buffer = context.allocator_-&gt;alloc(sizeof(ObMemtableMultiVersionScanIterator))) ||
  +        if (NULL == (scan_iter_buffer = context.stmt_allocator_-&gt;alloc(sizeof(ObMemtableMultiVersionScanIterator))) ||
               NULL == (scan_iter_ptr = new (scan_iter_buffer) ObMemtableMultiVersionScanIterator())) {
             TRANS_LOG(WARN,
                 "construct ObMemtableScanIterator fail",
  @@ -1117,7 +1117,7 @@ int ObMemtable::scan(const storage::ObTableIterParam&amp; param, storage::ObTableAcc
           }
         }
       } else {
  -      if (NULL == (scan_iter_buffer = context.allocator_-&gt;alloc(sizeof(ObMemtableScanIterator))) ||
  +      if (NULL == (scan_iter_buffer = context.stmt_allocator_-&gt;alloc(sizeof(ObMemtableScanIterator))) ||
             NULL == (scan_iter_ptr = new (scan_iter_buffer) ObMemtableScanIterator())) {
           TRANS_LOG(WARN,
               "construct ObMemtableScanIterator fail",
  @@ -1162,7 +1162,7 @@ int ObMemtable::multi_get(const storage::ObTableIterParam&amp; param, storage::ObTab
       TRANS_LOG(WARN, "invalid argument, ", K(ret), K(param), K(context), K(rowkeys));
     } else if (OB_FAIL(context.store_ctx_-&gt;mem_ctx_-&gt;get_trans_status())) {
       TRANS_LOG(WARN, "trans already end", K(ret));
  -  } else if (NULL == (mget_iter_buffer = context.allocator_-&gt;alloc(sizeof(ObMemtableMGetIterator))) ||
  +  } else if (NULL == (mget_iter_buffer = context.stmt_allocator_-&gt;alloc(sizeof(ObMemtableMGetIterator))) ||
                NULL == (mget_iter_ptr = new (mget_iter_buffer) ObMemtableMGetIterator())) {
       TRANS_LOG(WARN,
           "construct ObMemtableMGetIterator fail",
  @@ -1212,7 +1212,7 @@ int ObMemtable::multi_scan(const storage::ObTableIterParam&amp; param, storage::ObTa
       TRANS_LOG(WARN, "invalid argument, ", K(ret), K(param), K(context), K(ranges));
     } else if (OB_FAIL(context.store_ctx_-&gt;mem_ctx_-&gt;get_trans_status())) {
       TRANS_LOG(WARN, "trans already end", K(ret));
  -  } else if (NULL == (mscan_iter_buffer = context.allocator_-&gt;alloc(sizeof(ObMemtableMScanIterator))) ||
  +  } else if (NULL == (mscan_iter_buffer = context.stmt_allocator_-&gt;alloc(sizeof(ObMemtableMScanIterator))) ||
                NULL == (mscan_iter_ptr = new (mscan_iter_buffer) ObMemtableMScanIterator())) {
       TRANS_LOG(WARN,
           "construct ObMemtableMScanIterator fail",
  diff --git a/src/storage/ob_i_store.h b/src/storage/ob_i_store.h
  index e13283f7..69971590 100644
  --- a/src/storage/ob_i_store.h
  +++ b/src/storage/ob_i_store.h
  @@ -833,6 +833,8 @@ public:
     }
     virtual void reuse()
     {}
  +  virtual void reset()
  +  {}
     virtual bool is_base_sstable_iter() const
     {
       return false;
  diff --git a/src/storage/ob_multiple_get_merge.cpp b/src/storage/ob_multiple_get_merge.cpp
  index ebfd26a7..c5686543 100644
  --- a/src/storage/ob_multiple_get_merge.cpp
  +++ b/src/storage/ob_multiple_get_merge.cpp
  @@ -82,7 +82,7 @@ void ObMultipleGetMerge::reset_with_fuse_row_cache()
       handles_ = nullptr;
     }
     prefetch_cnt_ = 0;
  -  reuse_iter_array();
  +  reset_iter_array();
   }
     
   void ObMultipleGetMerge::reset()
  diff --git a/src/storage/ob_multiple_merge.cpp b/src/storage/ob_multiple_merge.cpp
  index be8de75f..f5c92405 100644
  --- a/src/storage/ob_multiple_merge.cpp
  +++ b/src/storage/ob_multiple_merge.cpp
  @@ -505,6 +505,10 @@ void ObMultipleMerge::reset()
       if (NULL != (iter = iters_.at(i))) {
         iter-&gt;~ObStoreRowIterator();
       }
  +    if (OB_NOT_NULL(access_ctx_-&gt;stmt_allocator_)) {
  +      access_ctx_-&gt;stmt_allocator_-&gt;free(iter);
  +    }
  +    iter = NULL;
     }
     padding_allocator_.reset();
     iters_.reset();
  @@ -541,17 +545,31 @@ void ObMultipleMerge::reuse()
     read_memtable_only_ = false;
   }
     
  -void ObMultipleMerge::reuse_iter_array()
  +void ObMultipleMerge::reset_iter_array()
   {
     ObStoreRowIterator* iter = NULL;
     for (int64_t i = 0; i &lt; iters_.count(); ++i) {
       if (NULL != (iter = iters_.at(i))) {
         iter-&gt;~ObStoreRowIterator();
       }
  +    if (OB_NOT_NULL(access_ctx_-&gt;stmt_allocator_)) {
  +      access_ctx_-&gt;stmt_allocator_-&gt;free(iter);
  +    }
  +    iter = NULL;
     }
     iters_.reuse();
   }
     
  +void ObMultipleMerge::reuse_iter_array()
  +{
  +  ObStoreRowIterator* iter = NULL;
  +  for (int64_t i = 0; i &lt; iters_.count(); ++i) {
  +    if (NULL != (iter = iters_.at(i))) {
  +      iter-&gt;reuse();
  +    }
  +  }
  +}
  +
   int ObMultipleMerge::open()
   {
     int ret = OB_SUCCESS;
  @@ -946,7 +964,7 @@ int ObMultipleMerge::refresh_table_on_demand()
     } else if (need_refresh) {
       if (OB_FAIL(save_curr_rowkey())) {
         STORAGE_LOG(WARN, "fail to save current rowkey", K(ret));
  -    } else if (FALSE_IT(reuse_iter_array())) {
  +    } else if (FALSE_IT(reset_iter_array())) {
       } else if (OB_FAIL(prepare_read_tables())) {
         STORAGE_LOG(WARN, "fail to prepare read tables", K(ret));
       } else if (OB_FAIL(reset_tables())) {
  diff --git a/src/storage/ob_multiple_merge.h b/src/storage/ob_multiple_merge.h
  index ed227202..a560172d 100644
  --- a/src/storage/ob_multiple_merge.h
  +++ b/src/storage/ob_multiple_merge.h
  @@ -80,6 +80,7 @@ protected:
     const ObTableIterParam* get_actual_iter_param(const ObITable* table) const;
     int project_row(const ObStoreRow&amp; unprojected_row, const common::ObIArray&lt;int32_t&gt;* projector,
         const int64_t range_idx_delta, ObStoreRow&amp; projected_row);
  +  void reset_iter_array();
     void reuse_iter_array();
     virtual int skip_to_range(const int64_t range_idx);
     
  diff --git a/src/storage/ob_sstable.cpp b/src/storage/ob_sstable.cpp
  index 13a3f0fa..c0713bbc 100644
  --- a/src/storage/ob_sstable.cpp
  +++ b/src/storage/ob_sstable.cpp
  @@ -1105,14 +1105,14 @@ int ObSSTable::get(const storage::ObTableIterParam&amp; param, storage::ObTableAcces
       ObISSTableRowIterator* row_getter = NULL;
       if (is_multi_version_minor_sstable() &amp;&amp; (context.is_multi_version_read(get_upper_trans_version()) ||
                                                   contain_uncommitted_row() || !meta_.has_compact_row_)) {
  -      if (NULL == (buf = context.allocator_-&gt;alloc(sizeof(ObSSTableMultiVersionRowGetter)))) {
  +      if (NULL == (buf = context.stmt_allocator_-&gt;alloc(sizeof(ObSSTableMultiVersionRowGetter)))) {
           ret = OB_ALLOCATE_MEMORY_FAILED;
           STORAGE_LOG(WARN, "Fail to allocate memory, ", K(ret));
         } else {
           row_getter = new (buf) ObSSTableMultiVersionRowGetter();
         }
       } else {
  -      if (NULL == (buf = context.allocator_-&gt;alloc(sizeof(ObSSTableRowGetter)))) {
  +      if (NULL == (buf = context.stmt_allocator_-&gt;alloc(sizeof(ObSSTableRowGetter)))) {
           ret = OB_ALLOCATE_MEMORY_FAILED;
           STORAGE_LOG(WARN, "Fail to allocate memory, ", K(ret));
         } else {
  @@ -1163,14 +1163,14 @@ int ObSSTable::multi_get(const ObTableIterParam&amp; param, ObTableAccessContext&amp; co
         ObISSTableRowIterator* row_getter = NULL;
         if (is_multi_version_minor_sstable() &amp;&amp; (context.is_multi_version_read(get_upper_trans_version()) ||
                                                     contain_uncommitted_row() || !meta_.has_compact_row_)) {
  -        if (NULL == (buf = context.allocator_-&gt;alloc(sizeof(ObSSTableMultiVersionRowMultiGetter)))) {
  +        if (NULL == (buf = context.stmt_allocator_-&gt;alloc(sizeof(ObSSTableMultiVersionRowMultiGetter)))) {
             ret = OB_ALLOCATE_MEMORY_FAILED;
             STORAGE_LOG(WARN, "Fail to allocate memory, ", K(ret));
           } else {
             row_getter = new (buf) ObSSTableMultiVersionRowMultiGetter();
           }
         } else {
  -        if (NULL == (buf = context.allocator_-&gt;alloc(sizeof(ObSSTableRowMultiGetter)))) {
  +        if (NULL == (buf = context.stmt_allocator_-&gt;alloc(sizeof(ObSSTableRowMultiGetter)))) {
             ret = OB_ALLOCATE_MEMORY_FAILED;
             STORAGE_LOG(WARN, "Fail to allocate memory, ", K(ret));
           } else {
  @@ -1269,21 +1269,21 @@ int ObSSTable::scan(const ObTableIterParam&amp; param, ObTableAccessContext&amp; context
       void* buf = NULL;
       ObISSTableRowIterator* row_scanner = NULL;
       if (context.query_flag_.is_whole_macro_scan()) {
  -      if (NULL == (buf = context.allocator_-&gt;alloc(sizeof(ObSSTableRowWholeScanner)))) {
  +      if (NULL == (buf = context.stmt_allocator_-&gt;alloc(sizeof(ObSSTableRowWholeScanner)))) {
           ret = OB_ALLOCATE_MEMORY_FAILED;
           STORAGE_LOG(WARN, "Fail to allocate memory, ", K(ret));
         } else {
           row_scanner = new (buf) ObSSTableRowWholeScanner();
         }
       } else if (is_multi_version_minor_sstable()) {
  -      if (NULL == (buf = context.allocator_-&gt;alloc(sizeof(ObSSTableMultiVersionRowScanner)))) {
  +      if (NULL == (buf = context.stmt_allocator_-&gt;alloc(sizeof(ObSSTableMultiVersionRowScanner)))) {
           ret = OB_ALLOCATE_MEMORY_FAILED;
           STORAGE_LOG(WARN, "Fail to allocate memory, ", K(ret));
         } else {
           row_scanner = new (buf) ObSSTableMultiVersionRowScanner();
         }
       } else {
  -      if (NULL == (buf = context.allocator_-&gt;alloc(sizeof(ObSSTableRowScanner)))) {
  +      if (NULL == (buf = context.stmt_allocator_-&gt;alloc(sizeof(ObSSTableRowScanner)))) {
           ret = OB_ALLOCATE_MEMORY_FAILED;
           STORAGE_LOG(WARN, "Fail to allocate memory, ", K(ret));
         } else {
  @@ -1435,14 +1435,14 @@ int ObSSTable::multi_scan(const ObTableIterParam&amp; param, ObTableAccessContext&amp; c
       void* buf = NULL;
       ObISSTableRowIterator* row_scanner = NULL;
       if (is_multi_version_minor_sstable()) {
  -      if (NULL == (buf = context.allocator_-&gt;alloc(sizeof(ObSSTableMultiVersionRowMultiScanner)))) {
  +      if (NULL == (buf = context.stmt_allocator_-&gt;alloc(sizeof(ObSSTableMultiVersionRowMultiScanner)))) {
           ret = OB_ALLOCATE_MEMORY_FAILED;
           STORAGE_LOG(WARN, "Fail to allocate memory, ", K(ret));
         } else {
           row_scanner = new (buf) ObSSTableMultiVersionRowMultiScanner();
         }
       } else {
  -      if (NULL == (buf = context.allocator_-&gt;alloc(sizeof(ObSSTableRowMultiScanner)))) {
  +      if (NULL == (buf = context.stmt_allocator_-&gt;alloc(sizeof(ObSSTableRowMultiScanner)))) {
           ret = OB_ALLOCATE_MEMORY_FAILED;
           STORAGE_LOG(WARN, "Fail to allocate memory, ", K(ret));
         } else {
  diff --git a/src/storage/ob_sstable_row_iterator.cpp b/src/storage/ob_sstable_row_iterator.cpp
  index 27c89147..cc0d2dd5 100644
  --- a/src/storage/ob_sstable_row_iterator.cpp
  +++ b/src/storage/ob_sstable_row_iterator.cpp
  @@ -1539,7 +1539,7 @@ int ObSSTableRowIterator::alloc_micro_getter()
     int ret = OB_SUCCESS;
     void* buf = NULL;
     if (NULL == micro_getter_) {
  -    if (NULL == (buf = access_ctx_-&gt;allocator_-&gt;alloc(sizeof(ObMicroBlockRowGetter)))) {
  +    if (NULL == (buf = access_ctx_-&gt;stmt_allocator_-&gt;alloc(sizeof(ObMicroBlockRowGetter)))) {
         ret = OB_ALLOCATE_MEMORY_FAILED;
         STORAGE_LOG(WARN, "Fail to allocate memory, ", K(ret));
       } else {
  @@ -1572,14 +1572,14 @@ int ObSSTableRowIterator::open_cur_micro_block(ObSSTableReadHandle&amp; read_handle,
     if (NULL == micro_scanner_) {
       // alloc scanner
       if (!sstable_-&gt;is_multi_version_minor_sstable()) {
  -      if (NULL == (buf = access_ctx_-&gt;allocator_-&gt;alloc(sizeof(ObMicroBlockRowScanner)))) {
  +      if (NULL == (buf = access_ctx_-&gt;stmt_allocator_-&gt;alloc(sizeof(ObMicroBlockRowScanner)))) {
           ret = OB_ALLOCATE_MEMORY_FAILED;
           STORAGE_LOG(WARN, "Fail to allocate memory for micro block scanner, ", K(ret));
         } else {
           micro_scanner_ = new (buf) ObMicroBlockRowScanner();
         }
       } else {
  -      if (NULL == (buf = access_ctx_-&gt;allocator_-&gt;alloc(sizeof(ObMultiVersionMicroBlockRowScanner)))) {
  +      if (NULL == (buf = access_ctx_-&gt;stmt_allocator_-&gt;alloc(sizeof(ObMultiVersionMicroBlockRowScanner)))) {
           ret = OB_ALLOCATE_MEMORY_FAILED;
           STORAGE_LOG(WARN, "Fail to allocate memory for micro block scanner, ", K(ret));
         } else {
    
  diff --git a/src/storage/memtable/ob_memtable.cpp b/src/storage/memtable/ob_memtable.cpp
  index ba7d295d..d1a02dc1 100644
  --- a/src/storage/memtable/ob_memtable.cpp
  +++ b/src/storage/memtable/ob_memtable.cpp
  @@ -1048,6 +1048,7 @@ int ObMemtable::get(const storage::ObTableIterParam&amp; param, storage::ObTableAcce
     if (OB_FAIL(ret)) {
       if (NULL != get_iter_ptr) {
         get_iter_ptr-&gt;~ObMemtableGetIterator();
  +      context.stmt_allocator_-&gt;free(get_iter_ptr);
         get_iter_ptr = NULL;
       }
       TRANS_LOG(WARN, "get fail", K(ret), K_(key), K(param.table_id_));
  @@ -1139,6 +1140,7 @@ int ObMemtable::scan(const storage::ObTableIterParam&amp; param, storage::ObTableAcc
       } else {
         if (NULL != scan_iter_ptr) {
           scan_iter_ptr-&gt;~ObIMemtableScanIterator();
  +        context.stmt_allocator_-&gt;free(scan_iter_ptr);
           scan_iter_ptr = NULL;
         }
         TRANS_LOG(
  @@ -1182,6 +1184,7 @@ int ObMemtable::multi_get(const storage::ObTableIterParam&amp; param, storage::ObTab
     if (OB_FAIL(ret)) {
       if (NULL != mget_iter_ptr) {
         mget_iter_ptr-&gt;~ObMemtableMGetIterator();
  +      context.stmt_allocator_-&gt;free(mget_iter_ptr);
         mget_iter_ptr = NULL;
       }
       TRANS_LOG(WARN,
  @@ -1233,6 +1236,7 @@ int ObMemtable::multi_scan(const storage::ObTableIterParam&amp; param, storage::ObTa
     if (OB_FAIL(ret)) {
       if (NULL != mscan_iter_ptr) {
         mscan_iter_ptr-&gt;~ObMemtableMScanIterator();
  +      context.stmt_allocator_-&gt;free(mscan_iter_ptr);
         mscan_iter_ptr = NULL;
       }
       TRANS_LOG(WARN,
  diff --git a/src/storage/ob_multiple_merge.cpp b/src/storage/ob_multiple_merge.cpp
  index f5c92405..6426010c 100644
  --- a/src/storage/ob_multiple_merge.cpp
  +++ b/src/storage/ob_multiple_merge.cpp
  @@ -993,7 +993,7 @@ int ObMultipleMerge::release_table_ref()
       STORAGE_LOG(WARN, "fail to check need refresh table", K(ret));
     } else if (need_refresh) {
       tables_handle_.reset();
  -    reuse_iter_array();
  +    reset_iter_array();
       is_tables_reset_ = true;
       STORAGE_LOG(INFO, "table need to be released", "table_id", access_param_-&gt;iter_param_.table_id_,
           K(*access_param_), K(curr_scan_index_));
  diff --git a/src/storage/ob_sstable_multi_version_row_iterator.cpp b/src/storage/ob_sstable_multi_version_row_iterator.cpp
  index 95b94b11..e295ccee 100644
  --- a/src/storage/ob_sstable_multi_version_row_iterator.cpp
  +++ b/src/storage/ob_sstable_multi_version_row_iterator.cpp
  @@ -57,10 +57,13 @@ void ObSSTableMultiVersionRowIterator::reset()
     
   void ObSSTableMultiVersionRowIterator::reuse()
   {
  -  ObISSTableRowIterator::reuse();
  +  ObISSTableRowIterator::reset();
  +  // ObISSTableRowIterator::reuse();
     query_range_ = NULL;
     if (NULL != iter_) {
  -    iter_-&gt;reuse();
  +    // iter_-&gt;reuse();
  +    iter_-&gt;~ObSSTableRowIterator();
  +    iter_ = NULL;
     }
     out_cols_cnt_ = 0;
     range_idx_ = 0;
  @@ -123,7 +126,7 @@ int ObSSTableMultiVersionRowGetter::inner_open(
       if (OB_FAIL(ObVersionStoreRangeConversionHelper::store_rowkey_to_multi_version_range(
               *rowkey_, access_ctx.trans_version_range_, *access_ctx.allocator_, multi_version_range_))) {
         LOG_WARN("convert to multi version range failed", K(ret), K(*rowkey_));
  -    } else if (OB_FAIL(new_iterator&lt;ObSSTableRowScanner&gt;(*access_ctx.allocator_))) {
  +    } else if (OB_FAIL(new_iterator&lt;ObSSTableRowScanner&gt;(*access_ctx.stmt_allocator_))) {
         LOG_WARN("failed to new iterator", K(ret));
       } else if (OB_FAIL(iter_-&gt;init(iter_param, access_ctx, table, &amp;multi_version_range_))) {
         LOG_WARN("failed to open scanner", K(ret));
  @@ -213,7 +216,7 @@ int ObSSTableMultiVersionRowScanner::inner_open(
       if (OB_FAIL(ObVersionStoreRangeConversionHelper::range_to_multi_version_range(
               *range_, access_ctx.trans_version_range_, *access_ctx.allocator_, multi_version_range_))) {
         LOG_WARN("convert to multi version range failed", K(ret), K(*range_));
  -    } else if (OB_FAIL(new_iterator&lt;ObSSTableRowScanner&gt;(*access_ctx.allocator_))) {
  +    } else if (OB_FAIL(new_iterator&lt;ObSSTableRowScanner&gt;(*access_ctx.stmt_allocator_))) {
         LOG_WARN("failed to new iterator", K(ret));
       } else if (OB_FAIL(iter_-&gt;init(iter_param, access_ctx, table, &amp;multi_version_range_))) {
         LOG_WARN("failed to open scanner", K(ret));
  @@ -306,7 +309,7 @@ int ObSSTableMultiVersionRowMultiGetter::inner_open(
           }
         }
         if (OB_FAIL(ret)) {
  -      } else if (OB_FAIL(new_iterator&lt;ObSSTableRowMultiScanner&gt;(*access_ctx.allocator_))) {
  +      } else if (OB_FAIL(new_iterator&lt;ObSSTableRowMultiScanner&gt;(*access_ctx.stmt_allocator_))) {
           LOG_WARN("failed to new iterator", K(ret));
         } else if (OB_FAIL(iter_-&gt;init(iter_param, access_ctx, table, &amp;multi_version_ranges_))) {
           LOG_WARN("failed to open multi scanner", K(ret));
  @@ -431,7 +434,7 @@ int ObSSTableMultiVersionRowMultiScanner::inner_open(
         }
     
         if (OB_FAIL(ret)) {
  -      } else if (OB_FAIL(new_iterator&lt;ObSSTableRowMultiScanner&gt;(*access_ctx.allocator_))) {
  +      } else if (OB_FAIL(new_iterator&lt;ObSSTableRowMultiScanner&gt;(*access_ctx.stmt_allocator_))) {
           LOG_WARN("failed to new iterator", K(ret));
         } else if (OB_FAIL(iter_-&gt;init(iter_param, access_ctx, table, &amp;multi_version_ranges_))) {
           LOG_WARN("failed to open scanner", K(ret));
    
  diff --git a/src/storage/ob_sstable_row_iterator.cpp b/src/storage/ob_sstable_row_iterator.cpp
  index cc0d2dd5..acc43774 100644
  --- a/src/storage/ob_sstable_row_iterator.cpp
  +++ b/src/storage/ob_sstable_row_iterator.cpp
  @@ -469,13 +469,13 @@ int ObSSTableRowIterator::inner_open(
       STORAGE_LOG(WARN, "Unexpected error, ", K(ret), K_(read_handle_cnt), K_(micro_handle_cnt));
     } else if (OB_FAIL(init_handle_mgr(iter_param, access_ctx, query_range))) {
       STORAGE_LOG(WARN, "fail to init handle mgr", K(ret), K(iter_param), K(access_ctx));
  -  } else if (OB_FAIL(read_handles_.reserve(*access_ctx.allocator_, read_handle_cnt_))) {
  +  } else if (OB_FAIL(read_handles_.reserve(*access_ctx.stmt_allocator_, read_handle_cnt_))) {
       STORAGE_LOG(WARN, "failed to reserve read handles", K(ret), K_(read_handle_cnt));
  -  } else if (OB_FAIL(micro_handles_.reserve(*access_ctx.allocator_, micro_handle_cnt_))) {
  +  } else if (OB_FAIL(micro_handles_.reserve(*access_ctx.stmt_allocator_, micro_handle_cnt_))) {
       STORAGE_LOG(WARN, "failed to reserve micro handles", K(ret), K_(micro_handle_cnt));
  -  } else if (OB_FAIL(sstable_micro_infos_.reserve(*access_ctx.allocator_, micro_handle_cnt_))) {
  +  } else if (OB_FAIL(sstable_micro_infos_.reserve(*access_ctx.stmt_allocator_, micro_handle_cnt_))) {
       STORAGE_LOG(WARN, "failed to reserve sstable micro infos", K(ret), K_(micro_handle_cnt));
  -  } else if (OB_FAIL(sorted_sstable_micro_infos_.reserve(*access_ctx.allocator_, micro_handle_cnt_))) {
  +  } else if (OB_FAIL(sorted_sstable_micro_infos_.reserve(*access_ctx.stmt_allocator_, micro_handle_cnt_))) {
       STORAGE_LOG(WARN, "failed to reserve sorted sstable micro infos", K(ret), K_(micro_handle_cnt));
     } else {
       sstable_ = static_cast&lt;ObSSTable*&gt;(table);
    
  diff --git a/src/storage/ob_multiple_multi_scan_merge.cpp b/src/storage/ob_multiple_multi_scan_merge.cpp
  index a7b2a571..55bdabd8 100644
  --- a/src/storage/ob_multiple_multi_scan_merge.cpp
  +++ b/src/storage/ob_multiple_multi_scan_merge.cpp
  @@ -228,7 +228,7 @@ int ObMultipleMultiScanMerge::construct_iters()
             iter-&gt;~ObStoreRowIterator();
             STORAGE_LOG(WARN, "Fail to push iter to iterator array, ", K(ret), K(i));
           }
  -      } else if (OB_ISNULL(iters_.at(tables.count() - 1 - i))) {
  +      } else if (OB_ISNULL(iter = iters_.at(tables.count() - 1 - i))) {
           ret = OB_ERR_UNEXPECTED;
           STORAGE_LOG(WARN, "Unexpected null iter", K(ret), "idx", tables.count() - 1 - i, K_(iters));
         } else if (OB_FAIL(iter-&gt;init(*iter_param, *access_ctx_, table, ranges_))) {
    
  diff --git a/src/storage/blocksstable/ob_micro_block_row_scanner.cpp b/src/storage/blocksstable/ob_micro_block_row_scanner.cpp
  index d6fd2648..cdc0297f 100644
  --- a/src/storage/blocksstable/ob_micro_block_row_scanner.cpp
  +++ b/src/storage/blocksstable/ob_micro_block_row_scanner.cpp
  @@ -445,7 +445,7 @@ int ObMicroBlockRowScanner::init(const ObTableIterParam&amp; param, ObTableAccessCon
       STORAGE_LOG(WARN, "fail to get projector", K(ret));
     } else if (OB_FAIL(param_-&gt;get_column_map(false /*is get*/, column_id_map))) {
       STORAGE_LOG(WARN, "fail to get column id map", K(ret));
  -  } else if (OB_FAIL(column_map_.init(*context_-&gt;allocator_,
  +  } else if (OB_FAIL(column_map_.init(*context_-&gt;stmt_allocator_,
                    param_-&gt;schema_version_,
                    param_-&gt;rowkey_cnt_,
                    0, /*store count*/
  @@ -573,7 +573,7 @@ int ObMultiVersionMicroBlockRowScanner::init(
       STORAGE_LOG(WARN, "fail to get projector", K(ret));
     } else if (OB_FAIL(param_-&gt;get_column_map(context.use_fuse_row_cache_, column_id_map))) {
       STORAGE_LOG(WARN, "fail to get column id map", K(ret));
  -  } else if (OB_FAIL(column_map_.init(*context_-&gt;allocator_,
  +  } else if (OB_FAIL(column_map_.init(*context_-&gt;stmt_allocator_,
                    param_-&gt;schema_version_,
                    param_-&gt;rowkey_cnt_,
                    0, /*store count*/
  @@ -1358,7 +1358,7 @@ int ObMultiVersionMicroBlockMinorMergeRowScanner::init(
       // minor merge should contain 2
       if (OB_FAIL(build_minor_merge_out_cols(*param_, out_cols, expect_multi_version_col_cnt))) {
         STORAGE_LOG(WARN, "fail to build minor merge out columns", K(ret));
  -    } else if (OB_FAIL(column_map_.init(*context_-&gt;allocator_,
  +    } else if (OB_FAIL(column_map_.init(*context_-&gt;stmt_allocator_,
                      param_-&gt;schema_version_,
                      param_-&gt;rowkey_cnt_,
                      0, /*store count*/
  diff --git a/src/storage/memtable/ob_memtable.cpp b/src/storage/memtable/ob_memtable.cpp
  index d1a02dc1..94050470 100644
  --- a/src/storage/memtable/ob_memtable.cpp
  +++ b/src/storage/memtable/ob_memtable.cpp
  @@ -927,7 +927,7 @@ int ObMemtable::get(const storage::ObTableIterParam&amp; param, storage::ObTableAcce
       const ColumnMap* param_column_map = nullptr;
       if (nullptr == row.row_val_.cells_) {
         if (nullptr ==
  -          (row.row_val_.cells_ = static_cast&lt;ObObj*&gt;(context.allocator_-&gt;alloc(sizeof(ObObj) * out_cols-&gt;count())))) {
  +          (row.row_val_.cells_ = static_cast&lt;ObObj*&gt;(context.stmt_allocator_-&gt;alloc(sizeof(ObObj) * out_cols-&gt;count())))) {
           ret = OB_ALLOCATE_MEMORY_FAILED;
           TRANS_LOG(WARN, "Fail to allocate memory, ", K(ret));
         } else {
  @@ -940,11 +940,11 @@ int ObMemtable::get(const storage::ObTableIterParam&amp; param, storage::ObTableAcce
         TRANS_LOG(WARN, "fail to get column map", K(ret));
       } else if (NULL == param_column_map) {
         void* buf = NULL;
  -      if (NULL == (buf = context.allocator_-&gt;alloc(sizeof(ColumnMap)))) {
  +      if (NULL == (buf = context.stmt_allocator_-&gt;alloc(sizeof(ColumnMap)))) {
           ret = OB_ALLOCATE_MEMORY_FAILED;
           TRANS_LOG(WARN, "Fail to allocate memory, ", K(ret));
         } else {
  -        local_map = new (buf) ColumnMap(*context.allocator_);
  +        local_map = new (buf) ColumnMap(*context.stmt_allocator_);
           if (OB_FAIL(local_map-&gt;init(*out_cols))) {
             TRANS_LOG(WARN, "Fail to build column map, ", K(ret));
           }
</code></pre></div>    </div>
  </li>
  <li>
    <p>复用handle mgr</p>

    <p>ObSSTableRowIterator中使用了block_handle_mgr_和block_index_handle_mgr_来缓存访问到的block_handle和block_index_handle，可以识别出rescan场景并且保持mgr一直有效。</p>

    <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  diff --git a/src/storage/ob_i_store.h b/src/storage/ob_i_store.h
  index 69971590..eb5274c9 100644
  --- a/src/storage/ob_i_store.h
  +++ b/src/storage/ob_i_store.h
  @@ -785,7 +785,7 @@ public:
     
   class ObStoreRowIterator : public ObIStoreRowIterator {
   public:
  -  ObStoreRowIterator() : type_(0)
  +  ObStoreRowIterator() : type_(0), is_rescan_(false)
     {}
     virtual ~ObStoreRowIterator()
     {}
  @@ -855,8 +855,14 @@ public:
     }
     VIRTUAL_TO_STRING_KV(K_(type));
     
  +  virtual void set_rescan_true()
  +  {
  +     is_rescan_ = true;
  +  }
  +
   protected:
     int type_;
  +  int is_rescan_;
     
   private:
     DISALLOW_COPY_AND_ASSIGN(ObStoreRowIterator);
  diff --git a/src/storage/ob_multiple_scan_merge.cpp b/src/storage/ob_multiple_scan_merge.cpp
  index 958c335e..130b53e9 100644
  --- a/src/storage/ob_multiple_scan_merge.cpp
  +++ b/src/storage/ob_multiple_scan_merge.cpp
  @@ -160,6 +160,9 @@ int ObMultipleScanMerge::construct_iters()
           }
           STORAGE_LOG(DEBUG, "[PUSHDOWN]", K_(consumer), K(iter-&gt;is_base_sstable_iter()));
           STORAGE_LOG(DEBUG, "add iter for consumer", KPC(table), KPC(access_param_));
  +        if (is_rescan()) {
  +          iter-&gt;set_rescan_true();
  +        }
         }
       }
     
  diff --git a/src/storage/ob_sstable_row_iterator.cpp b/src/storage/ob_sstable_row_iterator.cpp
  index a09c3e30..0fe498ae 100644
  --- a/src/storage/ob_sstable_row_iterator.cpp
  +++ b/src/storage/ob_sstable_row_iterator.cpp
  @@ -218,6 +218,7 @@ void ObISSTableRowIterator::reset()
     batch_rows_ = NULL;
     batch_row_count_ = 0;
     batch_row_pos_ = 0;
  +  is_rescan_ = false;
   }
     
   void ObISSTableRowIterator::reuse()
  @@ -428,7 +429,8 @@ ObSSTableRowIterator::ObSSTableRowIterator()
         io_micro_infos_(),
         micro_info_iter_(),
         prefetch_handle_depth_(DEFAULT_PREFETCH_HANDLE_DEPTH),
  -      prefetch_micro_depth_(DEFAULT_PREFETCH_MICRO_DEPTH)
  +      prefetch_micro_depth_(DEFAULT_PREFETCH_MICRO_DEPTH),
  +      hdr_flag_(0)
   {}
     
   ObSSTableRowIterator::~ObSSTableRowIterator()
  @@ -640,6 +642,7 @@ void ObSSTableRowIterator::reset()
     storage_file_ = nullptr;
     prefetch_handle_depth_ = DEFAULT_PREFETCH_HANDLE_DEPTH;
     prefetch_micro_depth_ = DEFAULT_PREFETCH_MICRO_DEPTH;
  +  hdr_flag_ = 0;
   }
     
   void ObSSTableRowIterator::reuse()
  @@ -666,8 +669,6 @@ void ObSSTableRowIterator::reuse()
     cur_range_idx_ = -1;
     io_micro_infos_.reuse();
     micro_info_iter_.reuse();
  -  block_index_handle_mgr_.reset();
  -  block_handle_mgr_.reset();
     table_store_stat_.reuse();
     skip_ctx_.reset();
     storage_file_ = nullptr;
  @@ -1683,6 +1684,29 @@ int ObSSTableRowIterator::init_handle_mgr(
       const ObTableIterParam&amp; iter_param, ObTableAccessContext&amp; access_ctx, const void* query_range)
   {
     int ret = OB_SUCCESS;
  +  if (is_rescan_) {
  +    if (hdr_flag_ == 0) {
  +      block_index_handle_mgr_.reset();
  +      block_handle_mgr_.reset();
  +      if (OB_FAIL(block_handle_mgr_.init(true, true, *access_ctx.stmt_allocator_))) {
  +        STORAGE_LOG(WARN, "failed to init block handle mgr", K(ret), K(true), K(true)); 
  +      } else if (OB_FAIL(block_index_handle_mgr_.init(true, true, *access_ctx.stmt_allocator_))) {
  +        STORAGE_LOG(WARN, "failed to init block index handle mgr", K(ret), K(true), K(true));
  +      }
  +      hdr_flag_ = 1;
  +    }
  +    return ret;
  +  } else {
  +    bool is_multi = false;
  +    bool is_ordered = false;
  +    if (!block_handle_mgr_.is_inited() &amp;&amp; OB_FAIL(block_handle_mgr_.init(false, true, *access_ctx.stmt_allocator_))) {
  +      STORAGE_LOG(WARN, "failed to init block handle mgr", K(ret), K(is_multi), K(is_ordered)); 
  +    } else if (!block_index_handle_mgr_.is_inited() &amp;&amp; OB_FAIL(block_index_handle_mgr_.init(false, is_ordered, *access_ctx.stmt_allocator_))) {
  +      STORAGE_LOG(WARN, "failed to init block index handle mgr", K(ret), K(is_multi), K(is_ordered));
  +    }
  +    return ret;
  +  }
  +  // never execute
     int64_t range_count = 0;
     bool is_multi = false;
     bool is_ordered = false;
  @@ -1703,9 +1727,9 @@ int ObSSTableRowIterator::init_handle_mgr(
               range_count &gt;= USE_HANDLE_CACHE_RANGE_COUNT_THRESHOLD);
     }
     if (OB_SUCC(ret)) {
  -    if (!block_handle_mgr_.is_inited() &amp;&amp; OB_FAIL(block_handle_mgr_.init(false, true, *access_ctx.allocator_))) {
  -      STORAGE_LOG(WARN, "failed to init block handle mgr", K(ret), K(is_multi), K(is_ordered));
  -    } else if (!block_index_handle_mgr_.is_inited() &amp;&amp; OB_FAIL(block_index_handle_mgr_.init(false, is_ordered, *access_ctx.allocator_))) {
  +    if (!block_handle_mgr_.is_inited() &amp;&amp; OB_FAIL(block_handle_mgr_.init(false, true, *access_ctx.stmt_allocator_))) {
  +      STORAGE_LOG(WARN, "failed to init block handle mgr", K(ret), K(is_multi), K(is_ordered)); 
  +    } else if (!block_index_handle_mgr_.is_inited() &amp;&amp; OB_FAIL(block_index_handle_mgr_.init(false, is_ordered, *access_ctx.stmt_allocator_))) {
         STORAGE_LOG(WARN, "failed to init block index handle mgr", K(ret), K(is_multi), K(is_ordered));
       }
     }
  diff --git a/src/storage/ob_sstable_row_iterator.h b/src/storage/ob_sstable_row_iterator.h
  index ebbbfc17..c6223425 100644
  --- a/src/storage/ob_sstable_row_iterator.h
  +++ b/src/storage/ob_sstable_row_iterator.h
  @@ -426,6 +426,7 @@ private:
     ObSSTableMicroBlockInfoIterator micro_info_iter_;
     int64_t prefetch_handle_depth_;
     int64_t prefetch_micro_depth_;
  +  int hdr_flag_;
   };
     
   }  // namespace storage
    
  diff --git a/src/storage/ob_micro_block_handle_mgr.cpp b/src/storage/ob_micro_block_handle_mgr.cpp
  index 028a2018..bb7f5e00 100644
  --- a/src/storage/ob_micro_block_handle_mgr.cpp
  +++ b/src/storage/ob_micro_block_handle_mgr.cpp
  @@ -45,6 +45,13 @@ void ObMicroBlockDataHandle::reset()
     io_handle_.reset();
   }
     
  +void ObMicroBlockDataHandle::reuse()
  +{
  +  block_index_ = -1;
  +  cache_handle_.reset();
  +  io_handle_.reset();
  +}
  +
   int ObMicroBlockDataHandle::get_block_data(
       ObMacroBlockReader&amp; block_reader, ObStorageFile* storage_file, ObMicroBlockData&amp; block_data)
   {
  @@ -104,7 +111,6 @@ int ObMicroBlockHandleMgr::get_micro_block_handle(const uint64_t table_id,
   {
     int ret = OB_SUCCESS;
     bool found = false;
  -  micro_block_handle.reset();
     if (IS_NOT_INIT) {
       ret = OB_NOT_INIT;
       STORAGE_LOG(WARN, "block handle mgr is not inited", K(ret));
  @@ -128,6 +134,7 @@ int ObMicroBlockHandleMgr::get_micro_block_handle(const uint64_t table_id,
       }
     }
     if (!found) {
  +    micro_block_handle.reuse();
       if (OB_FAIL(ObStorageCacheSuite::get_instance().get_block_cache().get_cache_block(
               table_id, block_ctx.get_macro_block_id(), file_id, offset, size, micro_block_handle.cache_handle_))) {
         if (OB_ENTRY_NOT_EXIST != ret) {
  diff --git a/src/storage/ob_micro_block_handle_mgr.h b/src/storage/ob_micro_block_handle_mgr.h
  index 37f6d005..1ff90688 100644
  --- a/src/storage/ob_micro_block_handle_mgr.h
  +++ b/src/storage/ob_micro_block_handle_mgr.h
  @@ -30,6 +30,7 @@ struct ObMicroBlockDataHandle {
     ObMicroBlockDataHandle();
     virtual ~ObMicroBlockDataHandle();
     void reset();
  +  void reuse();
     int get_block_data(blocksstable::ObMacroBlockReader&amp; block_reader, blocksstable::ObStorageFile* storage_file,
         blocksstable::ObMicroBlockData&amp; block_data);
     TO_STRING_KV(
  diff --git a/src/storage/ob_micro_block_index_handle_mgr.cpp b/src/storage/ob_micro_block_index_handle_mgr.cpp
  index 83beb4e0..4e938a81 100644
  --- a/src/storage/ob_micro_block_index_handle_mgr.cpp
  +++ b/src/storage/ob_micro_block_index_handle_mgr.cpp
  @@ -37,6 +37,13 @@ void ObMicroBlockIndexHandle::reset()
     io_handle_.reset();
   }
     
  +void ObMicroBlockIndexHandle::reuse()
  +{
  +  block_index_mgr_ = NULL;
  +  cache_handle_.reset();
  +  io_handle_.reuse();
  +}
  +
   int ObMicroBlockIndexHandle::search_blocks(const ObStoreRange&amp; range, const bool is_left_border,
       const bool is_right_border, ObIArray&lt;ObMicroBlockInfo&gt;&amp; infos, const ObIArray&lt;ObRowkeyObjComparer*&gt;* cmp_funcs)
   {
  @@ -107,7 +114,6 @@ int ObMicroBlockIndexHandleMgr::get_block_index_handle(const uint64_t table_id,
   {
     int ret = OB_SUCCESS;
     bool found = false;
  -  block_idx_handle.reset();
     if (IS_NOT_INIT) {
       ret = OB_NOT_INIT;
       STORAGE_LOG(WARN, "index handle mgr is not inited", K(ret));
  @@ -127,6 +133,7 @@ int ObMicroBlockIndexHandleMgr::get_block_index_handle(const uint64_t table_id,
       }
     }
     if (!found) {
  +    block_idx_handle.reuse();
       if (OB_FAIL(ObStorageCacheSuite::get_instance().get_micro_index_cache().get_cache_block_index(
               table_id, block_ctx.get_macro_block_id(), file_id, block_idx_handle.cache_handle_))) {
         if (OB_ENTRY_NOT_EXIST != ret) {
  diff --git a/src/storage/ob_micro_block_index_handle_mgr.h b/src/storage/ob_micro_block_index_handle_mgr.h
  index 2aea9dcf..89a19ac0 100644
  --- a/src/storage/ob_micro_block_index_handle_mgr.h
  +++ b/src/storage/ob_micro_block_index_handle_mgr.h
  @@ -23,6 +23,7 @@ struct ObMicroBlockIndexHandle {
     ObMicroBlockIndexHandle();
     virtual ~ObMicroBlockIndexHandle();
     void reset();
  +  void reuse();
     int search_blocks(const common::ObStoreRange&amp; range, const bool is_left_border, const bool is_right_border,
         common::ObIArray&lt;blocksstable::ObMicroBlockInfo&gt;&amp; infos,
         const common::ObIArray&lt;ObRowkeyObjComparer*&gt;* cmp_funcs = nullptr);
</code></pre></div>    </div>
  </li>
  <li>
    <p>减少冗余的代码 &amp; 逻辑优化（部分内容）</p>

    <p>prefetch数据预取逻辑冗余。</p>

    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="kt">int</span> <span class="n">ObSSTableRowIterator</span><span class="o">::</span><span class="n">inner_open</span><span class="p">(</span>
      <span class="k">const</span> <span class="n">ObTableIterParam</span><span class="o">&amp;</span> <span class="n">iter_param</span><span class="p">,</span> <span class="n">ObTableAccessContext</span><span class="o">&amp;</span> <span class="n">access_ctx</span><span class="p">,</span> <span class="n">ObITable</span><span class="o">*</span> <span class="n">table</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span><span class="o">*</span> <span class="n">query_range</span><span class="p">)</span>
  <span class="p">{</span>
  	<span class="p">...</span>
  	<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">prefetch</span><span class="p">()))</span> <span class="p">{</span>
  	  <span class="n">STORAGE_LOG</span><span class="p">(</span><span class="n">WARN</span><span class="p">,</span> <span class="s">"Fail to prefetch data, "</span><span class="p">,</span> <span class="n">K</span><span class="p">(</span><span class="n">ret</span><span class="p">));</span>
  	<span class="p">}</span>
    <span class="p">...</span>
  <span class="p">}</span>
    
  <span class="kt">int</span> <span class="n">ObMultipleGetMerge</span><span class="o">::</span><span class="n">construct_sstable_iter</span><span class="p">()</span>
  <span class="p">{</span>
  	<span class="k">for</span> <span class="p">(</span><span class="kt">int64_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">prefetch_cnt</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">prefetch</span><span class="p">()))</span> <span class="p">{</span>
        <span class="n">STORAGE_LOG</span><span class="p">(</span><span class="n">WARN</span><span class="p">,</span> <span class="s">"fail to prefetch"</span><span class="p">,</span> <span class="n">K</span><span class="p">(</span><span class="n">ret</span><span class="p">));</span>
      <span class="p">}</span>
    <span class="p">}</span>
  	<span class="p">...</span>
  <span class="p">}</span>
</code></pre></div>    </div>

    <p>索引回表时去掉多余的reuse。</p>

    <div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="kt">void</span> <span class="n">ObTableScanStoreRowIterator</span><span class="o">::</span><span class="n">reuse_row_iters</span><span class="p">()</span>
  <span class="p">{</span>
    <span class="p">...</span>
  	<span class="k">if</span> <span class="p">(</span><span class="nb">NULL</span> <span class="o">!=</span> <span class="n">index_merge_</span><span class="p">)</span> <span class="p">{</span>
  		<span class="n">index_merge_</span><span class="o">-&gt;</span><span class="n">reuse</span><span class="p">();</span> <span class="c1">// 每次 rescan 都会进⾏ reuse</span>
  	<span class="p">}</span>
  	<span class="p">...</span>
  <span class="p">}</span>
    
  <span class="kt">void</span> <span class="n">ObIndexMerge</span><span class="o">::</span><span class="n">reuse</span><span class="p">()</span>
  <span class="p">{</span>
  <span class="c1">// table_iter_.reuse(); </span>
  <span class="n">index_range_array_cursor_</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="p">}</span>
  <span class="kt">int</span> <span class="n">ObIndexMerge</span><span class="o">::</span><span class="n">get_next_row</span><span class="p">(</span><span class="n">ObStoreRow</span><span class="o">*&amp;</span> <span class="n">row</span><span class="p">)</span> <span class="p">{</span>
   <span class="p">......</span>
  <span class="n">table_iter_</span><span class="p">.</span><span class="n">reuse</span><span class="p">();</span> <span class="c1">// 在这⾥ reuse</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">table_iter_</span><span class="p">.</span><span class="n">open</span><span class="p">(</span><span class="n">rowkeys_</span><span class="p">)))</span> <span class="p">{</span>
</code></pre></div>    </div>

    <p>优化refresh table on demand逻辑</p>

    <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  diff --git a/src/storage/ob_multiple_merge.cpp b/src/storage/ob_multiple_merge.cpp
  index 9aa5cb01..be8de75f 100644
  --- a/src/storage/ob_multiple_merge.cpp
  +++ b/src/storage/ob_multiple_merge.cpp
  @@ -922,7 +922,7 @@ int ObMultipleMerge::prepare_read_tables()
       }
     
       if (OB_SUCC(ret)) {
  -      relocate_cnt_ = access_ctx_-&gt;store_ctx_-&gt;mem_ctx_-&gt;get_relocate_cnt();
  +//      relocate_cnt_ = access_ctx_-&gt;store_ctx_-&gt;mem_ctx_-&gt;get_relocate_cnt();
         if (OB_UNLIKELY(nullptr != row_filter_)) {
           const ObPartitionKey&amp; pkey = partition_store.get_partition_key();
           row_filter_ = tables_handle_.has_split_source_table(pkey) ? row_filter_ : NULL;
  @@ -987,24 +987,20 @@ int ObMultipleMerge::release_table_ref()
   int ObMultipleMerge::check_need_refresh_table(bool &amp;need_refresh)
   {
     int ret = OB_SUCCESS;
  -  if (OB_UNLIKELY(!inited_)) {
  -    ret = OB_NOT_INIT;
  -    STORAGE_LOG(WARN, "ObMultipleMerge has not been inited", K(ret));
  +  if (NULL != access_ctx_-&gt;store_ctx_-&gt;mem_ctx_) {
  +    temp = relocate_cnt_;
  +    relocate_cnt_ = access_ctx_-&gt;store_ctx_-&gt;mem_ctx_-&gt;get_relocate_cnt();
  +    need_refresh = relocate_cnt_ &gt; temp;
     } else {
  -    const bool relocated = NULL == access_ctx_-&gt;store_ctx_-&gt;mem_ctx_
  -                               ? false
  -                               : access_ctx_-&gt;store_ctx_-&gt;mem_ctx_-&gt;get_relocate_cnt() &gt; relocate_cnt_;
  -    const bool memtable_retired = tables_handle_.check_store_expire();
  -    const int64_t relocate_cnt = access_ctx_-&gt;store_ctx_-&gt;mem_ctx_-&gt;get_relocate_cnt();
  -    need_refresh = relocated || memtable_retired;
  +    need_refresh = tables_handle_.check_store_expire();
  +  }
   #ifdef ERRSIM
  -    ret = E(EventTable::EN_FORCE_REFRESH_TABLE) ret;
  -    if (OB_FAIL(ret)) {
  -      ret = OB_SUCCESS;
  -      need_refresh = true;
  -    }
  -#endif
  +  ret = E(EventTable::EN_FORCE_REFRESH_TABLE) ret;
  +  if (OB_FAIL(ret)) {
  +    ret = OB_SUCCESS;
  +    need_refresh = true;
     }
  +#endif
     return ret;
   }
     
  diff --git a/src/storage/ob_multiple_merge.h b/src/storage/ob_multiple_merge.h
  index 12f8cdc2..ed227202 100644
  --- a/src/storage/ob_multiple_merge.h
  +++ b/src/storage/ob_multiple_merge.h
  @@ -164,6 +164,7 @@ class ObMultipleMerge : public ObQueryRowIterator {
     int64_t range_idx_delta_;
     ObGetTableParam get_table_param_;
     int64_t relocate_cnt_;
  +  int64_t temp;
     ObTableStoreStat table_stat_;
     bool skip_refresh_table_;
     bool read_memtable_only_;
</code></pre></div>    </div>
  </li>
</ul>

<h2 id="正确性验证">正确性验证</h2>

<hr />

<p>修改代码的正确性是通过mysqltest运行测试样例来评定，OceanBase代码量庞大，逻辑复杂，自己做的修改难免会出现一些段错误之类的问题，这时候可以开vscode debug，在运行测试用例出错时就会catch住段错误的位置，方便找到问题的根源。</p>

<p>比如这个iter没有初始化的bug就是这样找出来的，改了代码以后会走到ObMultipleMultiScanMerge，mysqltest的测试样例正好测出了这个bug。</p>

<p><img src="/auto-image/picrepo/24980b22-3ff2-4a3a-822e-95d2080a17da.png" alt="Untitled" /></p>]]></content><author><name>张惠东</name></author><category term="oceanbase" /><summary type="html"><![CDATA[本文介绍调试OceanBase代码的方法，以及Nested-Loop-Join性能优化的思路]]></summary></entry><entry><title type="html">Oceanbase中LSM-Tree的分层设计及其优缺点</title><link href="https://dbhammer.github.io/2022/05/25/Oceanbase%E4%B8%ADLSM-Tree%E7%9A%84%E5%88%86%E5%B1%82%E8%AE%BE%E8%AE%A1%E5%8F%8A%E5%85%B6%E4%BC%98%E7%BC%BA%E7%82%B9.html" rel="alternate" type="text/html" title="Oceanbase中LSM-Tree的分层设计及其优缺点" /><published>2022-05-25T00:00:00+00:00</published><updated>2026-03-31T13:16:28+00:00</updated><id>https://dbhammer.github.io/2022/05/25/Oceanbase%E4%B8%ADLSM-Tree%E7%9A%84%E5%88%86%E5%B1%82%E8%AE%BE%E8%AE%A1%E5%8F%8A%E5%85%B6%E4%BC%98%E7%BC%BA%E7%82%B9</id><content type="html" xml:base="https://dbhammer.github.io/2022/05/25/Oceanbase%E4%B8%ADLSM-Tree%E7%9A%84%E5%88%86%E5%B1%82%E8%AE%BE%E8%AE%A1%E5%8F%8A%E5%85%B6%E4%BC%98%E7%BC%BA%E7%82%B9.html"><![CDATA[<p>LSM Tree技术出现的一个最主要的原因就是磁盘的随机写速度要远远低于顺序写的速度，而数据库要面临很多写密集型的场景，所以很多数据库产品就把LSM Tree的思想引入到了数据库领域。LSM Tree ，顾名思义，就是The Log-Structured Merge-Tree 的缩写。从这个名称里面可以看到几个关键的信息：</p>

<p>第一：Log-Structred，通过日志的方式来组织的</p>

<p>第二：Merge，可以合并的</p>

<p>第三：Tree，一种树形结构</p>

<p>实际上它并不是一棵树，也不是一种具体的数据结构，它实际上是一种数据保存和更新的思想。简单的说，就是将数据按照key来进行排序（在数据库中就是表的主键），之后形成一棵一棵小的树形结构，或者不是树形结构，是一张小表也可以，这些数据通常被称为基线数据；之后把每次数据的改变（也就是log）都记录下来，也按照主键进行排序，之后定期的把log中对数据的改变合并（merge）到基线数据当中。下面的图形描述了LSM Tree的基本结构。</p>

<p><img src="/auto-image/picrepo/93e5d2c4-ee44-4ed1-b762-0427df96f70b.png" width="100%" height="100%" style="float: left" /></p>

<p>图中的C0代表了缓存在内存中的数据，当内存中的数据达到了一定的阈值后，就会把数据内存中的数据排序后保存到磁盘当中，这就形成了磁盘中C1级别的增量数据（这些数据也是按照主键排序的），这个过程通常被称为转储。当C1级别的数据也达到一定阈值的时候，就会触发另外的一次合并（合并的过程可以认为是一种归并排序的过程），形成C2级别的数据，以此类推，如果这个逐级合并的结构定义了k层的话，那么最后的第k层数据就是最后的基线数据，这个过程通常被称为合并。</p>

<p><strong>用一句话来简单描述的话，LSM Tree就是一个基于归并排序的数据存储思想</strong>。从上面的结构中不难看出，LSM Tree对写密集型的应用是非常友好的，因为绝大部分的写操作都是顺序的。但是对很多读操作是要损失一些性能的，因为数据在磁盘上可能存在多个版本，所以通常情况下，使用了LSM Tree的存储引擎都会选择把很多个版本的数据存在内存中，根据查询的需要，构建出满足要求的数据版本。</p>

<p><img src="/auto-image/picrepo/4442d802-dd7e-4443-bf43-0ca3a78cf513.png" width="100%" height="100%" style="float: left" /></p>

<p>OceanBase 数据库的存储引擎基于 LSM Tree 架构，将数据分为<strong>静态基线数据</strong>（放在 SSTable 中）和<strong>动态增量数据</strong>（放在 MemTable 中）两部分，其中 SSTable 是只读的，一旦生成就不再被修改，存储于磁盘；MemTable 支持读写，存储于内存。数据库 DML 操作插入、更新、删除等首先写入 MemTable，等到 MemTable 达到一定大小时转储到磁盘成为 SSTable。在进行查询时，需要分别对 SSTable 和 MemTable 进行查询，并将查询结果进行归并，返回给 SQL 层归并后的查询结果。同时在内存实现了 Block Cache 和 Row cache，来避免对基线数据的随机读。</p>

<p>当内存的增量数据达到一定规模的时候，会触发增量数据和基线数据的合并，把增量数据落盘。同时每天晚上的空闲时刻，系统也会自动每日合并。</p>

<p>OceanBase 本质上是一个基线加增量的存储引擎，跟关系数据库差别很大，同时也借鉴了部分传统关系数据库存储引擎的优点。</p>

<p>传统数据库把数据分成很多页面，OceanBase 数据库也借鉴了传统数据库的思想，把数据分成很多 2MB 为单位的宏块。合并时采用增量合并的方式，OceanBase 数据库的合并代价相比 LevelDB 和 RocksDB 都会低很多。另外，OceanBase 数据库通过轮转合并的机制把正常服务和合并时间错开，使得合并操作对正常用户请求完全没有干扰。</p>

<p>由于 OceanBase 数据库采用基线加增量的设计，一部分数据在基线，一部分在增量，原理上每次查询都是既要读基线，也要读增量。为此，OceanBase 数据库做了很多的优化，尤其是针对单行的优化。OceanBase 数据库内部除了对数据块进行缓存之外，也会对行进行缓存，行缓存会极大加速对单行的查询性能。对于不存在行的“空查”，OceanBase 会构建布隆过滤器，并对布隆过滤器进行缓存。OLTP 业务大部分操作为小查询，通过小查询优化，OceanBase 数据库避免了传统数据库解析整个数据块的开销，达到了接近内存数据库的性能。另外，由于基线是只读数据，而且内部采用连续存储的方式，OceanBase 数据库可以采用比较激进的压缩算法，既能做到高压缩比，又不影响查询性能，大大降低了成本。</p>]]></content><author><name>徐金凯</name></author><category term="oceanbase" /><summary type="html"><![CDATA[LSM Tree技术出现的一个最主要的原因就是磁盘的随机写速度要远远低于顺序写的速度，而数据库要面临很多写密集型的场景，所以很多数据库产品就把LSM Tree的思想引入到了数据库领域。LSM Tree ，顾名思义，就是The Log-Structured Merge-Tree 的缩写。从这个名称里面可以看到几个关键的信息：]]></summary></entry><entry><title type="html">OceanBase内核初探</title><link href="https://dbhammer.github.io/2022/05/11/OceanBase%E5%86%85%E6%A0%B8%E5%88%9D%E6%8E%A2.html" rel="alternate" type="text/html" title="OceanBase内核初探" /><published>2022-05-11T00:00:00+00:00</published><updated>2026-03-31T13:16:28+00:00</updated><id>https://dbhammer.github.io/2022/05/11/OceanBase%E5%86%85%E6%A0%B8%E5%88%9D%E6%8E%A2</id><content type="html" xml:base="https://dbhammer.github.io/2022/05/11/OceanBase%E5%86%85%E6%A0%B8%E5%88%9D%E6%8E%A2.html"><![CDATA[<blockquote>
  <p>💡 <strong>作者：华东师范大学 数据科学与工程学院 DBHammer项目组 东亚男儿团队</strong></p>
</blockquote>

<p>本文主体面向对OceanBase数据库源码以及系统性能优化感兴趣的初学者供以技术交流，笔者来自<a href="https://dbhammer.github.io/">华东师范大学数据科学与工程学院DBHammer项目组</a>。</p>

<p>本文主体分为三个部分：如何快速对OceanBase进行调试；系统性能优化利器-火焰图的简要介绍；面向赛题Nested Loop Join的应用场景，如何进行性能优化。</p>

<h2 id="oceanbase快速debug">OceanBase快速Debug</h2>

<h3 id="引言">引言</h3>

<p>本文以在VSCode编辑器上OceanBase 3.1版本为例，进行Debug的教学，本地OB搭建的教程可以参考<a href="https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.0/get-the-oceanbase-database-by-using-source-code">使用源码构建 OceanBase 数据库</a> 和 <a href="https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.0/deploy-the-oceanbase-database-by-using-obd">使用OBD部署OceanBase</a>。需要注意的是OBD目前只有rpm包，在Ubuntu环境下的具体安装方法可见<a href="https://linuxize.com/post/install-rpm-packages-on-ubuntu/">Install RPM packages on Ubuntu | Linuxize</a>。</p>

<h3 id="步骤一创建租户">步骤一：创建租户</h3>

<p>在安装部署好OB后，接下来我们需要创建一个租户。当OB集群创建完成时，只有一个默认的sys租户，而sys租户仅用于集群管理，并不能支持测试服务，因此我们需要手动创建新的租户用于测试。</p>

<p>OBD提供了方便的创建租户的命令。 在OB比赛的简单场景中，我们仅创建一个租户：</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">obd</span> <span class="k">cluster</span> <span class="n">tenant</span> <span class="k">create</span> <span class="n">obadvanced</span> <span class="c1">--tenant-name mysql</span>
</code></pre></div></div>

<p>这个命令创建了一个名为mysql 的租户，并为它分配了剩下的所有系统资源，没有设置密码。</p>

<p>接着我们使用mysql租户连接数据库，加上-c以确保之后输入的<a href="https://help.aliyun.com/apsara/enterprise/v_3_13_0_20201215/oceanbase/enterprise-user-guide/hint-overview.html">sql hint</a>生效：</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">mysql</span> <span class="o">-</span><span class="n">uroot</span><span class="o">@</span><span class="n">mysql</span> <span class="o">-</span><span class="n">h127</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="o">-</span><span class="n">P</span> <span class="mi">2881</span> <span class="o">-</span><span class="k">c</span>
</code></pre></div></div>

<p>输入指令后即可看到数据库连接界面：</p>

<p><img src="/auto-image/picrepo/eb7643e3-1f60-451d-9dd7-ed255596ec32.png" alt="image-20220502160801229" /></p>

<h3 id="步骤二配置launchjson">步骤二：配置launch.json</h3>

<p>假定大家现在已经搭建了一款本地的OB且创建好了相应的租户，那么接下来我们需要在.vscode目录下创建launch.json文件以配置具体的gdb调试环境。</p>

<p>我们采取的是gdb attach <pid>的方式进行debug，而每次OB启动时其进程号并不固定，所以我们配置了tasks-shell-input插件（可以在vscode扩展中下载），以grep的方式进行server端进程号的选取和自动化填充，这为调试带来了极大的便利。</pid></p>

<p>以下我们给出一个示例文件：</p>

<p>（其中出现的一些诸如ob-advanced这种创建OB时自定义的名称或者文件目录位置都需要再自行调整，sourceFileMap也可能需要根据需求手动增加mapping，更多的json配置项语义可见<a href="https://code.visualstudio.com/docs/cpp/launch-json-reference">官网</a>）</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
    </span><span class="nl">"configurations"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"(gdb) Attach"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"cppdbg"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"request"</span><span class="p">:</span><span class="w"> </span><span class="s2">"attach"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"program"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/data/ob-advanced/bin/observer"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"processId"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${input:FindPID}"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"MIMode"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gdb"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"sudo"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
            </span><span class="nl">"miDebuggerPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"gdb"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"setupCommands"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
                </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Enable pretty-printing for gdb"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"text"</span><span class="p">:</span><span class="w"> </span><span class="s2">"-enable-pretty-printing"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"ignoreFailures"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">],</span><span class="w">
            </span><span class="nl">"sourceFileMap"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"./build_debug/src/observer/./src/observer/omt"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/observer/omt"</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"./build_debug/src/sql/parser/./src/sql/parser"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/sql/parser"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"useForBreakpoints"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"./build_debug/src/sql/./src/sql"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/sql"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"useForBreakpoints"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"./build_debug/src/storage/./src/storage"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/storage"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"useForBreakpoints"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                </span><span class="p">},</span><span class="w">
                </span><span class="nl">"./build_debug/src/sql/engine/join/./src/sql/engine/join"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                    </span><span class="nl">"editorPath"</span><span class="p">:</span><span class="w"> </span><span class="s2">"${workspaceFolder}/src/sql/engine/join"</span><span class="p">,</span><span class="w">
                    </span><span class="nl">"useForBreakpoints"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
                </span><span class="p">}</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">],</span><span class="w">
    </span><span class="nl">"inputs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
        </span><span class="p">{</span><span class="w">
            </span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"FindPID"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"command"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"shellCommand.execute"</span><span class="p">,</span><span class="w">
            </span><span class="nl">"args"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
                </span><span class="nl">"command"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ps -aux | grep /bin/observer | grep -v </span><span class="se">\\</span><span class="s2">"</span><span class="err">grep\\</span><span class="s2">" | awk '{print $2}'"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Select your observer PID"</span><span class="p">,</span><span class="w">
                </span><span class="nl">"useFirstResult"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
            </span><span class="p">}</span><span class="w">
        </span><span class="p">}</span><span class="w">
    </span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<blockquote>
  <p>💡 如果遇到下面的问题 <code class="language-plaintext highlighter-rouge">Authentication is needed to run </code>/usr/bin/gdb’ as the super user` 可以输入指令<strong>echo 0| sudo tee /proc/sys/kernel/yama/ptrace_scope</strong>调整权限解决</p>
</blockquote>

<p>最终点击左上角的调试按钮，等待一段时间后，即可看到完整的调试界面，如下图所示：</p>

<p><img src="/auto-image/picrepo/25aa13c8-b25b-4170-a65c-6f8425fba916.png" alt="image-20220502160910433" /></p>

<h3 id="步骤三增加breakpoint和所需观测变量">步骤三：增加Breakpoint和所需观测变量</h3>

<p>接着我们通过obclient/mysql模式连接OB。</p>

<p>为了调试的方便，我们可能需要适当增大事务超时时间以避免调试内部因可能的超时原因提前终止而影响判断。</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">set</span> <span class="k">global</span> <span class="n">ob_trx_idle_timeout</span><span class="o">=</span><span class="mi">120000000</span><span class="p">;</span>
<span class="k">set</span> <span class="k">global</span> <span class="n">ob_trx_timeout</span><span class="o">=</span><span class="mi">36000000000</span><span class="p">;</span>
<span class="k">set</span> <span class="k">global</span> <span class="n">ob_query_timeout</span><span class="o">=</span><span class="mi">3600000000</span>
</code></pre></div></div>

<p>比如，我们想先观察一下当前的场景是否开启了batch操作，即可在ob_nested_loop_join_op.cpp：read_left_operate函数里打上断点（也可以右键编辑条件断点）。</p>

<p><img src="/auto-image/picrepo/dbf00cc3-ae93-4fcc-b467-392e618194d0.png" alt="image-20220502160922103" /></p>

<p>接着我们在命令行中输入负载样例：</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">select</span> <span class="cm">/*+ordered use_nl(A,B)*/</span> <span class="o">*</span> <span class="k">from</span> <span class="n">t1</span> <span class="n">A</span><span class="p">,</span> <span class="n">t2</span> <span class="n">B</span> <span class="k">where</span> <span class="n">A</span><span class="p">.</span><span class="n">c1</span> <span class="o">&gt;=</span> <span class="o">-</span><span class="mi">100</span> <span class="k">and</span> <span class="n">A</span><span class="p">.</span><span class="n">c1</span> <span class="o">&lt;</span> <span class="mi">200</span> <span class="k">and</span> <span class="n">A</span><span class="p">.</span><span class="n">c2</span> <span class="o">=</span> <span class="n">B</span><span class="p">.</span><span class="n">c2</span><span class="p">;</span>
</code></pre></div></div>

<p>等待片刻即可看到如下所示的debug界面：具体有四大信息栏值得关注：</p>

<ol>
  <li>工具栏：继续执行到下一个breakpoint、单步跳过、单步跳出、单步调试等基本调试操作；</li>
  <li>变量栏：在此可以展开变量，观察程序执行期间各变量具体的值信息；</li>
  <li>堆栈信息栏：在此可以观测到当前程序执行期间堆栈的上下文调用信息，便以快速把握代码的整体调用结构；</li>
  <li>监视栏：由于变量栏里的很多变量都是指针，我们对其进行观测时只能看到变量的地址信息。那么我们在监视栏一方面可以执行基本的类型转换，取出指针指向的具体值信息，另一方面也可以一直保留某些需要持续观测的变量。</li>
</ol>

<p><img src="/auto-image/picrepo/5f4a063e-70b7-4269-96cd-6f691a8d9a44.png" alt="image-20220502160930579" /></p>

<p>以上便是对OceanBase的debug调试方法的全部介绍。</p>

<p>除了通过debug从执行的细节上见微知著，我们还可以通过火焰图对程序的整体执行有一个宏观的把握，在此我们简单介绍一下火焰图的使用方法，希望对大家有所帮助。</p>

<h2 id="火焰图介绍">火焰图介绍</h2>

<h3 id="什么是火焰图">什么是火焰图</h3>

<p>火焰图由性能优化大师Brendan Gregg发明，以图像的形式形象地展示了程序执行时的调用堆栈信息，从底向上展示函数的执行比例，便于技术人员从中把握可能的性能瓶颈。因其颜色以红黄橙等暖色为主，像是跳动的火焰，故称Flame Graph，下图为OceanBase v3.1的整体火焰图。</p>

<p><img src="/auto-image/picrepo/6c9c15eb-d179-41fc-8904-5eeef2eb8e78.png" alt="image-20220502160938777" /></p>

<p>悬浮其上便能看到某个函数具体的执行比例：</p>

<p><img src="/auto-image/picrepo/6fda2122-99c1-4d04-b57b-ecc4726a54a0.png" alt="image-20220502160946520" /></p>

<p>关于火焰图相关的介绍文档和视频有很多，我们在此就不再赘述了，仅在下面作一个简要的概括，更详细的介绍可参见文章底部提供的链接。</p>

<p>火焰图主体有以下特征（这里以 on-cpu 火焰图为例）：</p>

<ul>
  <li>每一列代表一个调用栈，每一个格子代表一个函数（一个栈帧）</li>
  <li>Y轴展示了栈的深度，按照调用关系从下到上排列。最顶上的格子代表当执行采样收集时，当前正在占用 cpu 的函数。每个格子下面的格子即是它的父函数。</li>
  <li>X轴展示了火焰图将要采集的不同调用栈信息，从左到右以函数名称的字母序顺序排列，但需要注意的是，横向的排序并不代表时间的流逝，其本身排序是没有任何实义的。</li>
  <li>横轴格子的宽度代表其在采样中出现频率，其宽度与实际在堆栈中执行的时间长成正比，因此如果一个格子的宽度越大，说明它是瓶颈原因的可能性就越大。</li>
  <li>火焰图格子的颜色是随机的暖色调，其颜色深浅并无具体实义，只是单纯为了方便区分各个调用信息。</li>
</ul>

<p><img src="/auto-image/picrepo/bd4fa662-a0a6-4c22-86cb-27c2392d2995.png" alt="image-20220502160958727" /></p>

<p>以Brendan Gregg所给示意图来说：</p>

<ul>
  <li>顶端的格子显示函数g()占用CPU的时间最多；</li>
  <li>函数d()更宽，但其暴露的顶端边缘在CPU上运行得最少（相较于e、f来说），说明我们可能更需要关注其子函数的调用；</li>
  <li>b()和c()两个函数似乎并没有直接在CPU上采样，实际在CPU上执行的函数都是它们的子函数；</li>
  <li>g()下面的函数显示了它的祖先：g()被f()调用，而f()又被d()调用，以此类推；</li>
  <li>从视觉上比较函数b()和h()的宽度可以看出，b()的代码路径在CPU上的时间占用上是h()的4倍；</li>
  <li>在a()调用b()和h()的地方，可以看到代码路径中存在一个分叉，这可能是一个条件判断的结果（即如果有条件，就调用b()，否则就调用h()）或者是一个程序执行逻辑上的阶段分组（a()被分成两部分处理：b()和h()）。</li>
  <li>需要注意的是，OB内部存在协程，因此，一个格子暴露的边缘部分并不一定就是其运行的时间，这一点可能需要通过看汇编代码来确定。</li>
</ul>

<h3 id="如何获取火焰图">如何获取火焰图</h3>

<p>火焰图本身的制作是基于perf生成的data数据进行的，下面我们便进入工具的使用介绍：</p>

<ol>
  <li>获取Flame Graph工具：<code class="language-plaintext highlighter-rouge">git clone [&lt;https://github.com/brendangregg/FlameGraph.git&gt;](&lt;https://github.com/brendangregg/FlameGraph.git&gt;)</code></li>
  <li>执行perf record -F 99 -g -p 127 – sleep 20，在当前目录下生成采样数据perf.data.
    <ol>
      <li>意即基于某个指定pid，以99hz频率采样，持续10s</li>
      <li>更多的指令详见https://www.brendangregg.com/perf.html</li>
    </ol>
  </li>
  <li>执行<code class="language-plaintext highlighter-rouge">perf script -i perf.data &amp;&gt; perf.unfold</code> ，用perf script工具读取perf.data结果，并对perf.data进行解析，其输出格式如下：</li>
</ol>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">/</span><span class="n">read_left_operate</span>
        <span class="n">ffffffffb86dc289</span> <span class="n">finish_task_switch</span><span class="o">+</span><span class="mi">0</span><span class="n">x199</span> <span class="p">([</span><span class="n">kernel</span><span class="p">.</span><span class="n">kallsyms</span><span class="p">])</span>
        <span class="n">ffffffffb9200a46</span> <span class="n">__sched_text_start</span><span class="o">+</span><span class="mi">0</span><span class="n">x2f6</span> <span class="p">([</span><span class="n">kernel</span><span class="p">.</span><span class="n">kallsyms</span><span class="p">])</span>
        <span class="n">ffffffffb920109f</span> <span class="n">schedule</span><span class="o">+</span><span class="mi">0</span><span class="n">x4f</span> <span class="p">([</span><span class="n">kernel</span><span class="p">.</span><span class="n">kallsyms</span><span class="p">])</span>
        <span class="n">ffffffffb873bfd3</span> <span class="n">exit_to_user_mode_prepare</span><span class="o">+</span><span class="mi">0</span><span class="n">xf3</span> <span class="p">([</span><span class="n">kernel</span><span class="p">.</span><span class="n">kallsyms</span><span class="p">])</span>
        <span class="n">ffffffffb91f7be9</span> <span class="n">irqentry_exit_to_user_mode</span><span class="o">+</span><span class="mi">0</span><span class="n">x9</span> <span class="p">([</span><span class="n">kernel</span><span class="p">.</span><span class="n">kallsyms</span><span class="p">])</span>
        <span class="n">ffffffffb91f7c19</span> <span class="n">irqentry_exit</span><span class="o">+</span><span class="mi">0</span><span class="n">x19</span> <span class="p">([</span><span class="n">kernel</span><span class="p">.</span><span class="n">kallsyms</span><span class="p">])</span>
        <span class="n">ffffffffb91f6c7e</span> <span class="n">sysvec_reschedule_ipi</span><span class="o">+</span><span class="mi">0</span><span class="n">x7e</span> <span class="p">([</span><span class="n">kernel</span><span class="p">.</span><span class="n">kallsyms</span><span class="p">])</span>
        <span class="n">ffffffffb9400dc2</span> <span class="n">asm_sysvec_reschedule_ipi</span><span class="o">+</span><span class="mi">0</span><span class="n">x12</span> <span class="p">([</span><span class="n">kernel</span><span class="p">.</span><span class="n">kallsyms</span><span class="p">])</span>
                 <span class="mi">972</span><span class="n">e9a0</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">storage</span><span class="p">::</span><span class="n">ObQueryIteratorConsumer</span><span class="p">::</span><span class="n">set_consumer_num</span><span class="o">+</span><span class="mi">0</span><span class="n">x0</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">9617</span><span class="n">ac1</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">storage</span><span class="p">::</span><span class="n">ObMultipleScanMergeImpl</span><span class="p">::</span><span class="n">inner_get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">xb1</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">961793</span><span class="n">e</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">storage</span><span class="p">::</span><span class="n">ObMultipleScanMerge</span><span class="p">::</span><span class="n">inner_get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x2e</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">9609</span><span class="n">eff</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">storage</span><span class="p">::</span><span class="n">ObMultipleMerge</span><span class="p">::</span><span class="n">get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x44f</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">9</span><span class="n">aa2c49</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">storage</span><span class="p">::</span><span class="n">ObTableScanStoreRowIterator</span><span class="p">::</span><span class="n">get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x119</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">9</span><span class="n">aa3807</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">storage</span><span class="p">::</span><span class="n">ObTableScanRangeArrayRowIterator</span><span class="p">::</span><span class="n">get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x117</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">9</span><span class="n">aa4bb7</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">storage</span><span class="p">::</span><span class="n">ObTableScanIterator</span><span class="p">::</span><span class="n">get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x1d7</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">9</span><span class="n">b42a55</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">storage</span><span class="p">::</span><span class="n">ObTableScanIterator</span><span class="p">::</span><span class="n">get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x25</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">48266</span><span class="n">b7</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObTableScanOp</span><span class="p">::</span><span class="n">get_next_row_with_mode</span><span class="o">+</span><span class="mi">0</span><span class="n">x97</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">4826</span><span class="n">aa8</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObTableScanOp</span><span class="p">::</span><span class="n">inner_get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x3d8</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">56</span><span class="n">aa469</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObOperator</span><span class="p">::</span><span class="n">get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x189</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">38731</span><span class="n">eb</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObJoinOp</span><span class="p">::</span><span class="n">get_next_left_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x2b</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">38752</span><span class="n">d3</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObBasicNestedLoopJoinOp</span><span class="p">::</span><span class="n">get_next_left_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x1a3</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">3879</span><span class="n">c31</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObNestedLoopJoinOp</span><span class="p">::</span><span class="n">group_read_left_operate</span><span class="o">+</span><span class="mi">0</span><span class="n">xb71</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">3876</span><span class="n">cf9</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObNestedLoopJoinOp</span><span class="p">::</span><span class="n">read_left_operate</span><span class="o">+</span><span class="mi">0</span><span class="n">x39</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">38782</span><span class="n">a2</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObNestedLoopJoinOp</span><span class="p">::</span><span class="n">inner_get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x262</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">56</span><span class="n">aa469</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObOperator</span><span class="p">::</span><span class="n">get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x189</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">4</span><span class="n">cee252</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObExecuteResult</span><span class="p">::</span><span class="n">get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x152</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">4</span><span class="n">ced5c7</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObExecuteResult</span><span class="p">::</span><span class="n">get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x47</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">64084</span><span class="n">ee</span> <span class="n">oceanbase</span><span class="p">::</span><span class="k">sql</span><span class="p">::</span><span class="n">ObResultSet</span><span class="p">::</span><span class="n">get_next_row</span><span class="o">+</span><span class="mi">0</span><span class="n">x13e</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">a3c0b01</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">observer</span><span class="p">::</span><span class="n">ObSyncPlanDriver</span><span class="p">::</span><span class="n">response_query_result</span><span class="o">+</span><span class="mi">0</span><span class="n">x3d1</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">a3bf493</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">observer</span><span class="p">::</span><span class="n">ObSyncPlanDriver</span><span class="p">::</span><span class="n">response_result</span><span class="o">+</span><span class="mi">0</span><span class="n">x4b3</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">a3f5d09</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">observer</span><span class="p">::</span><span class="n">ObMPQuery</span><span class="p">::</span><span class="n">process_single_stmt</span><span class="o">+</span><span class="mi">0</span><span class="n">x3669</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">a3f135d</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">observer</span><span class="p">::</span><span class="n">ObMPQuery</span><span class="p">::</span><span class="n">process</span><span class="o">+</span><span class="mi">0</span><span class="n">x222d</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">b3d20bd</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">rpc</span><span class="p">::</span><span class="n">frame</span><span class="p">::</span><span class="n">ObReqProcessor</span><span class="p">::</span><span class="n">run</span><span class="o">+</span><span class="mi">0</span><span class="n">x3ed</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">a750cb0</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">omt</span><span class="p">::</span><span class="n">ObWorkerProcessor</span><span class="p">::</span><span class="n">process_one</span><span class="o">+</span><span class="mi">0</span><span class="n">x240</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">a72e41a</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">omt</span><span class="p">::</span><span class="n">ObWorkerProcessor</span><span class="p">::</span><span class="n">process</span><span class="o">+</span><span class="mi">0</span><span class="n">x8ea</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">a74ea86</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">omt</span><span class="p">::</span><span class="n">ObThWorker</span><span class="p">::</span><span class="n">process_request</span><span class="o">+</span><span class="mi">0</span><span class="n">x3e6</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">a72c33d</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">omt</span><span class="p">::</span><span class="n">ObThWorker</span><span class="p">::</span><span class="n">worker</span><span class="o">+</span><span class="mi">0</span><span class="n">x10cd</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">a72c80c</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">omt</span><span class="p">::</span><span class="n">ObThWorker</span><span class="p">::</span><span class="n">run</span><span class="o">+</span><span class="mi">0</span><span class="n">x3c</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">2</span><span class="n">af48ec</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">lib</span><span class="p">::</span><span class="n">CoKThreadTemp</span><span class="o">&lt;</span><span class="n">oceanbase</span><span class="p">::</span><span class="n">lib</span><span class="p">::</span><span class="n">CoUserThreadTemp</span><span class="o">&lt;</span><span class="n">oceanbase</span><span class="p">::</span><span class="n">lib</span><span class="p">::</span><span class="n">CoSetSched</span><span class="o">&gt;</span> <span class="o">&gt;</span><span class="p">::</span><span class="k">start</span><span class="p">()::{</span><span class="n">lambda</span><span class="p">()</span><span class="o">#</span><span class="mi">1</span><span class="p">}::</span><span class="k">operator</span><span class="p">()</span><span class="o">+</span><span class="mi">0</span><span class="n">x4c</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">2</span><span class="n">af477d</span> <span class="n">std</span><span class="p">::</span><span class="n">_Function_handler</span><span class="o">&lt;</span><span class="n">void</span> <span class="p">(),</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">lib</span><span class="p">::</span><span class="n">CoKThreadTemp</span><span class="o">&lt;</span><span class="n">oceanbase</span><span class="p">::</span><span class="n">lib</span><span class="p">::</span><span class="n">CoUserThreadTemp</span><span class="o">&lt;</span><span class="n">oceanbase</span><span class="p">::</span><span class="n">lib</span><span class="p">::</span><span class="n">CoSetSched</span><span class="o">&gt;</span> <span class="o">&gt;</span><span class="p">::</span><span class="k">start</span><span class="p">()::{</span><span class="n">lambda</span><span class="p">()</span><span class="o">#</span><span class="mi">1</span><span class="p">}</span><span class="o">&gt;</span><span class="p">::</span><span class="n">_M_invoke</span><span class="o">+</span><span class="mi">0</span><span class="n">x1d</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="mi">1</span><span class="n">e4f14e</span> <span class="n">std</span><span class="p">::</span><span class="k">function</span><span class="o">&lt;</span><span class="n">void</span> <span class="p">()</span><span class="o">&gt;</span><span class="p">::</span><span class="k">operator</span><span class="p">()</span><span class="o">+</span><span class="mi">0</span><span class="n">x3e</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">af877b5</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">lib</span><span class="p">::</span><span class="n">CoSetSched</span><span class="p">::</span><span class="n">Worker</span><span class="p">::</span><span class="n">run</span><span class="o">+</span><span class="mi">0</span><span class="n">x45</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">af86155</span> <span class="n">oceanbase</span><span class="p">::</span><span class="n">lib</span><span class="p">::</span><span class="n">CoRoutine</span><span class="p">::</span><span class="n">__start</span><span class="o">+</span><span class="mi">0</span><span class="n">x1b5</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
                 <span class="n">af7eeaf</span> <span class="n">finish</span><span class="o">+</span><span class="mi">0</span><span class="n">x0</span> <span class="p">(</span><span class="o">/</span><span class="k">data</span><span class="o">/</span><span class="n">ob</span><span class="o">-</span><span class="n">advanced</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span><span class="p">.</span><span class="n">another</span><span class="o">-</span><span class="k">commit</span><span class="o">-</span><span class="mi">915609</span><span class="n">a0</span><span class="o">-</span><span class="mi">25</span><span class="n">_20</span><span class="p">:</span><span class="mi">01</span><span class="p">:</span><span class="mi">00</span><span class="o">-</span><span class="n">debug</span><span class="p">)</span>
        <span class="n">ccccccccccccccf4</span> <span class="p">[</span><span class="k">unknown</span><span class="p">]</span> <span class="p">([</span><span class="k">unknown</span><span class="p">])</span>
</code></pre></div></div>

<ol>
  <li>执行<code class="language-plaintext highlighter-rouge">FlameGraph/stackcollapse-perf.pl perf.unfold &amp;&gt; perf.folded</code> ，接着将perf.unfold中的符号进行折叠，组织成火焰图所需的统一格式</li>
  <li>执行<code class="language-plaintext highlighter-rouge">FlameGraph/flamegraph.pl perf.folded &gt; perf.svg</code>，最后生成svg格式的火焰图</li>
</ol>

<p>以下是一个样例脚本供自动化生成OceanBase的Flame Graph，感谢复赛的lhcmaple队伍提供了这个脚本，我们稍作了一些改进。</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">#!/</span><span class="n">bin</span><span class="o">/</span><span class="n">bash</span>
<span class="o">#</span> <span class="k">no</span> <span class="k">parameter</span> <span class="k">is</span> <span class="n">needed</span><span class="p">,</span> <span class="n">just</span> <span class="n">run</span>
<span class="n">PID</span><span class="o">=</span><span class="err">$</span><span class="p">(</span><span class="n">ps</span> <span class="o">-</span><span class="n">aux</span> <span class="o">|</span> <span class="n">grep</span> <span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">observer</span> <span class="o">|</span> <span class="n">grep</span> <span class="o">-</span><span class="n">v</span> <span class="err">\</span><span class="nv">"grep</span><span class="se">\"</span><span class="nv"> | awk '{print $2}')
if [ ${#PID} -eq 0 ]
then
    echo "</span><span class="n">observer</span> <span class="k">is</span> <span class="k">not</span> <span class="n">running</span><span class="nv">"
    exit -1
fi
perf record -F 99 -g -p $PID -- sleep 20
perf script -i perf.data &amp;&gt; perf.unfold
FlameGraph/stackcollapse-perf.pl perf.unfold &amp;&gt; perf.folded
FlameGraph/flamegraph.pl perf.folded &gt; perf.svg
</span></code></pre></div></div>

<p>当然，尽管火焰图的可视化对于性能优化debug的确大有裨益，但原生的perf操作对数据的统计信息和操控粒度会更加丰富和深入，二者互相配合，才能相得益彰。</p>

<p>比如我们在步骤一统计完perf.data信息后，可以直接输入perf report查看命令行内的树状信息，如下图所示：</p>

<p><img src="/auto-image/picrepo/7ba1f235-7d27-4214-b0e3-730c7c8b90b1.png" alt="image-20220502161010627" /></p>

<p>我们输入/group_read_left_operate快速搜索定位到我们所需要的函数：</p>

<p><img src="/auto-image/picrepo/0ed6e010-94ea-484e-871a-a33aa1bd95a3.png" alt="image-20220502161019748" /></p>

<p>接着按下a，即可展开具体的堆栈信息，且以汇编的形式陈列，这便可以帮助我们确定某些优化是否真正起到了作用，比如循环展开（Loop Unrolling），可能我们需要通过汇编才能真正确定其是否优化到了我们预期的效果。</p>

<p><img src="/auto-image/picrepo/438fb9f6-ada8-4371-9da8-1819bf13965f.png" alt="image-20220502161026394" /></p>

<p>工欲善其事，必先利其器，有了如上如此方便快捷的调试方法和性能分析利器火焰图，接下来我们便需要开始着手思考赛题了。</p>

<h2 id="性能优化方向">性能优化方向</h2>

<p>本次赛题是在开源 OceanBase 基础之上，针对 Nested Loop Join（NLJ) 场景做性能优化。</p>

<p>测试所使用的查询语句为<code class="language-plaintext highlighter-rouge">select /*+ordered use_nl(A,B)*/ * from t1 A, t2 B where A.c1 &gt;= ? and A.c1 &lt; ? and A.c2 = B.c2 and A.c3 = B.c3;</code>。</p>

<p>我们explain这条SQL语句，可得到如下结果：</p>

<p>可以看到，查询语句中包含两个join条件和一个对t1表的范围过滤，在过滤后，左表t1的数据量会显著小于右表t2。同时，t1.c2=t2.c2这一join条件会使用到t2.c2这一索引。</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">explain</span> <span class="n">extended</span> <span class="n">select</span> <span class="cm">/*+ordered use_nl(A,B)*/</span> <span class="o">*</span> <span class="n">from</span> <span class="n">t1</span> <span class="n">A</span><span class="p">,</span> <span class="n">t2</span> <span class="n">B</span> <span class="n">where</span> <span class="n">A</span><span class="p">.</span><span class="n">c1</span> <span class="o">&gt;=</span> <span class="mi">56107</span> <span class="n">and</span> <span class="n">A</span><span class="p">.</span><span class="n">c1</span> <span class="o">&lt;</span> <span class="mi">56307</span> <span class="n">and</span> <span class="n">A</span><span class="p">.</span><span class="n">c2</span> <span class="o">=</span> <span class="n">B</span><span class="p">.</span><span class="n">c2</span> <span class="n">and</span> <span class="n">A</span><span class="p">.</span><span class="n">c3</span> <span class="o">=</span> <span class="n">B</span><span class="p">.</span><span class="n">c3</span><span class="p">;</span>
<span class="o">|</span> <span class="o">================================================</span>
<span class="o">|</span><span class="n">ID</span><span class="o">|</span><span class="n">OPERATOR</span>        <span class="o">|</span><span class="n">NAME</span>    <span class="o">|</span><span class="n">EST</span><span class="p">.</span> <span class="n">ROWS</span><span class="o">|</span><span class="n">COST</span>   <span class="o">|</span>
<span class="o">------------------------------------------------</span>
<span class="o">|</span><span class="mi">0</span> <span class="o">|</span><span class="n">NESTED</span><span class="o">-</span><span class="n">LOOP</span> <span class="n">JOIN</span><span class="o">|</span>        <span class="o">|</span><span class="mi">2718</span>     <span class="o">|</span><span class="mi">1306675</span><span class="o">|</span>
<span class="o">|</span><span class="mi">1</span> <span class="o">|</span> <span class="n">TABLE</span> <span class="n">SCAN</span>     <span class="o">|</span><span class="n">A</span>       <span class="o">|</span><span class="mi">200</span>      <span class="o">|</span><span class="mi">78</span>     <span class="o">|</span>
<span class="o">|</span><span class="mi">2</span> <span class="o">|</span> <span class="n">TABLE</span> <span class="n">SCAN</span>     <span class="o">|</span><span class="n">B</span><span class="p">(</span><span class="n">t2_i1</span><span class="p">)</span><span class="o">|</span><span class="mi">10</span>       <span class="o">|</span><span class="mi">6530</span>   <span class="o">|</span>
<span class="o">================================================</span>
<span class="o">----</span>
<span class="mi">2</span> <span class="o">-</span> <span class="n">is_index_back</span><span class="o">=</span><span class="nb">true</span>
<span class="o">----</span>
</code></pre></div></div>

<h3 id="背景">背景</h3>

<p>NLJ的基本原理是每次从左表获取一行，然后用这行数据和右表进行JOIN。</p>

<p>通常来说，最简单的想法就是直接把右表的全部数据扫描上来，再跟左表的这行数据进行JOIN的话，那么程序整体的复杂度就是：M(左表行数)*N(右表行数)。</p>

<p>但在大部分的实际场景，为了降低复杂度，右表获取数据可以选取其他两种方案：</p>

<ol>
  <li>JOIN条件本身是右表的rowkey(主键)，可以直接通过主键索引获取到右表行（假设是聚合索引）；</li>
  <li>右表上面有普通索引，JOIN条件可以命中索引，那么可以根据左表这行数据先去查右表的索引，获得到右表的rowkey(主键)，再利用主键去查找右表(这个过程叫做回表)，获取到完整的行；</li>
</ol>

<p>由于本次大赛的题目中右表上存在索引，因此可以应用方式2。同时，我们也可以从上述EXPLAIN执行计划印证这一点：左表是t1，右表是t2，扫描右表走了索引t2_i1回表。</p>

<h3 id="nlj整体执行流程">NLJ整体执行流程</h3>

<p>具体来说，该场景的具体实现可以分为3个部分：左表scan、rescan和右表scan回表。</p>

<p><strong>左表scan：</strong>在这个场景中就是对t1表，根据主键范围进行扫描，并逐行返回，该模块涉及到的函数调用关系为：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ObNestedLoopJoinOp</span><span class="p">.</span><span class="n">read_left_operate</span><span class="o">-&gt;</span><span class="n">ObJoinOp</span><span class="p">.</span><span class="n">get_next_left_row</span><span class="o">-&gt;</span><span class="n">ObOperator</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span>
<span class="n">ObTableScanOp</span><span class="p">.</span><span class="n">inner_get_next_row</span><span class="o">-&gt;</span><span class="n">ObTableScanIterator</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span><span class="n">ObTableScanRangeArrayRowIterator</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span>
<span class="c1">//往下为存储层</span>
<span class="n">ObTableScanStoreRowIterator</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span><span class="n">ObMultipleMerge</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span><span class="n">ObMultipleScanMerge</span><span class="p">.</span><span class="n">inner_get_next_row</span><span class="o">-&gt;</span><span class="p">...</span>
<span class="n">ObStoreRowIterator</span><span class="p">.</span><span class="n">get_next_row_ext</span><span class="o">-&gt;</span><span class="p">...</span>
</code></pre></div></div>

<p><strong>rescan：</strong>rescan发生在左表的上一行针对右表已经完成了JOIN的情况，这个时候OB并不会直接关闭右表的扫描，而是通过rescan重置右表的扫描状态，之后在左表扫描下一行时可以直接开始右表的扫描，而不用重新打开。具体来说，该模块涉及到的函数调用关系为：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ObNestedLoopJoinOp</span><span class="p">.</span><span class="n">read_left_func_going</span><span class="o">-&gt;</span>
<span class="n">ObTableScanOp</span><span class="p">.</span><span class="n">rescan</span><span class="o">-&gt;</span><span class="n">ObTableScanOp</span><span class="p">.</span><span class="n">rt_rescan</span><span class="o">-&gt;</span><span class="n">ObTableScanOp</span><span class="p">.</span><span class="n">rescan_after_adding_query_range</span><span class="o">-&gt;</span>
<span class="c1">//往下为存储层</span>
<span class="n">ObTableScanIterIterator</span><span class="p">.</span><span class="n">rescan</span><span class="o">-&gt;</span><span class="n">ObTableScanStoreRowIterator</span><span class="p">.</span><span class="n">rescan</span><span class="o">-&gt;</span>
<span class="p">...</span>
</code></pre></div></div>

<p><strong>右表scan回表：</strong>在这个场景中就是先通过B.c2列查询索引t2_i1，获取到rowkey后再查询t2的过程，该模块涉及到的函数调用关系为：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ObNestedLoopJoinOp</span><span class="p">.</span><span class="n">read_right_operate</span><span class="o">-&gt;</span><span class="n">ObJoinOp</span><span class="p">.</span><span class="n">get_next_right_row</span><span class="o">-&gt;</span><span class="n">ObOperator</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span>
<span class="n">ObTableScanOp</span><span class="p">.</span><span class="n">inner_get_next_row</span><span class="o">-&gt;</span><span class="n">ObTableScanIterator</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span><span class="n">ObTableScanRangeArrayRowIterator</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span>
<span class="c1">//往下为存储层</span>
<span class="n">ObTableScanStoreRowIterator</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span><span class="n">ObIndexMerge</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span>
<span class="p">...</span>
</code></pre></div></div>

<h3 id="存储层查询流程">存储层查询流程</h3>

<p>OB的存储是基于LSM-Tree实现的，具体内容可以参考OB开源官网文档 <a href="https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.0/lsm-tree-architecture">LSM Tree 架构</a>，以及同一个章节下的其它内容。</p>

<p>存储层查询是在多个memtable、sstable上迭代、归并的过程，按单行返回给上层。</p>

<p>OB目前的实现是为每一个memtable/sstable对应分配一个iterator，多个memtable/sstable对应的iterators维护在ObMultipleMerge类中，由Merge类完成从每个iterator获取1行数据，然后再进行归并的任务。</p>

<p>同时，OB的sstable内部是按照宏块-微块-行三层存储粒度组成，在iterator内部会先根据查询range定位到微块，然后通过微块对应的ObMicroBlockRowScanner打开读行。</p>

<p>总结来说，NLJ查询涉及的到存储层结构可以分为3层，multiple merge层，store row iter层，micro scanner/getter层，其关联关系如下所示：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">multiple</span> <span class="n">merge</span>
<span class="o">--</span> <span class="n">iters</span> <span class="err">维护多个</span><span class="n">memtable</span><span class="o">/</span><span class="n">sstable</span><span class="err">的</span><span class="n">iterator</span><span class="err">，查询可能涉及到多个</span><span class="n">memtable</span><span class="o">/</span><span class="n">sstable</span><span class="err">做归并</span>
<span class="n">store</span> <span class="n">row</span> <span class="n">iter</span>
<span class="o">--</span> <span class="err">单个</span><span class="n">memtable</span><span class="o">/</span><span class="n">sstable</span><span class="err">对应的迭代器</span>
<span class="n">micro</span> <span class="n">scanner</span><span class="o">/</span><span class="n">getter</span>
<span class="o">--</span> <span class="err">微块迭代器</span>
</code></pre></div></div>

<p>所以理论上对于同一个查询中，同一个memtable/sstable只需要1个iterator就可以了。尽管对NLJ rescan场景需要对右表进行多次遍历，但在理想情况下还是可以只用这1个iterator完成多次遍历，不过OB目前的版本并没有实现这一点。</p>

<p>同时，在sstable iterator内部实际读数据时，是通过预取(prefetch)的方式把IO和解析读到的行数据串联起来，从而避免不必要的IO，CPU也可以流水线执行，极大提升了效率。</p>

<p>而针对目前这个NLJ场景，从执行计划可以看出这个查询会对两张表进行table scan，对t1表使用普通的迭代，对t2表使用索引回表（OB的索引回表实现在ObIndexMerge中）。</p>

<p>其落实到查询层的调用链路和执行过程列举如下：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mf">1.</span> <span class="err">创建</span><span class="n">iterator</span><span class="err">并开始第</span><span class="mi">1</span><span class="err">次预取</span>
<span class="n">ObTableScanStoreRowIterator</span><span class="p">.</span><span class="n">open_iter</span><span class="o">-&gt;</span><span class="n">ObMultipleScan</span><span class="p">(</span><span class="n">get</span><span class="p">)</span><span class="n">Merge</span><span class="p">.</span><span class="n">construct_iters</span><span class="o">-&gt;</span><span class="n">ObSSTable</span><span class="p">(</span><span class="n">ObMemtable</span><span class="p">).</span><span class="n">scan</span><span class="p">(</span><span class="n">get</span><span class="p">)</span><span class="o">-&gt;</span> <span class="c1">//分配iterator</span>
<span class="n">ObISSTableRowIterator</span><span class="p">.</span><span class="n">init</span><span class="o">-&gt;</span><span class="n">ObSSTableRowIterator</span><span class="p">.</span><span class="n">inner_open</span><span class="o">-&gt;</span> <span class="c1">// 初始化iterator</span>
<span class="n">ObSSTableRowIterator</span><span class="p">.</span><span class="n">prefetch</span> <span class="c1">//预取数据</span>
<span class="mf">2.1</span><span class="p">.</span> <span class="err">持续预取并读数据</span><span class="p">(</span><span class="err">非回表</span><span class="p">)</span>
<span class="n">get_next_row</span><span class="o">-&gt;</span><span class="p">..</span>
<span class="mf">2.2</span><span class="p">.</span> <span class="err">回表读取数据</span>
<span class="n">ObIndexMerge</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span><span class="n">ObMultipleMerge</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span><span class="n">ObMultipleScanMerge</span><span class="p">.</span><span class="n">inner_get_next_row</span><span class="o">-&gt;</span><span class="p">...</span>
<span class="o">-&gt;</span><span class="n">ObMultipleMerge</span><span class="p">.</span><span class="n">get_next_row</span><span class="o">-&gt;</span><span class="n">ObMultipleGetMerge</span><span class="p">.</span><span class="n">inner_get_next_row</span><span class="o">-&gt;</span><span class="p">...</span>
</code></pre></div></div>

<h3 id="优化方向">优化方向</h3>

<p><img src="/auto-image/picrepo/6fa0542d-9417-4274-8dac-386420371303.png" alt="image-20220502161042457" /></p>

<p>不难从OB的NLJ实现中和火焰图的占比上看出rescan和右表scan回表对性能影响比较大，我们以rescan为例分析当前的实现和可以改进的方向。</p>

<p>首先针对rescan，它的作用是使右表多次的扫描可以尽量复用对象，在如下代码中可以看到rescan释放和保留(重置)了哪些对象：</p>

<p><img src="/auto-image/picrepo/fa53eaa5-587e-4b79-8ad8-69f548effc54.png" alt="image-20220502161050205" /></p>

<p><img src="/auto-image/picrepo/4acdae4b-aa5d-4fc7-92f9-13c4edffa858.png" alt="image-20220502161106412" /></p>

<ul>
  <li>rescan调用了reuse_row_iters，意图重用这个iter对象，但是在其内部实现，还是调用了~ObStoreRowIterator，经过继承关系（ObSSTableRowIterator→ObISSTableRowIterator→ObSSTableRowIterator）最终通过ObSSTableRowIterator析构掉了这个iter对象，并清空了iters数组，实际并没有起到复用的效果。</li>
  <li>而现在的实现实际上是在open_iter中调用了construct_iters，接着再调用scan函数进行iter的初始化和重新分配，其调用栈信息如下火焰图所示：</li>
</ul>

<p><img src="/auto-image/picrepo/8a897d27-6049-42c0-a176-690e3c344692.png" alt="image-20220502161117726" /></p>

<p>同时在scan函数中也会调用init进而通过inner_open函数进行实际的iter分配，最后设置row_iter的值。</p>

<p><img src="/auto-image/picrepo/bb357b7d-b7bc-4507-ae06-c204e5a120dc.png" alt="image-20220502161125114" /></p>

<p>所以对于多次扫描实际使用的都是sstable iter对象，这里直接的改进方向是，rescan中不要析构掉和清理iters数组，然后保持iters在整个查询(多次rescan)一直有效。</p>

<p>为了实现这个目标，我们的大致思路如下：</p>

<p>首先我们需要内存保持有效，在最开始分配iter的地方使用适当的资源分配器（allocator）保证整个查询期间内存都不会被释放；接着当遇到析构的时候不再直接释放变量，而是调用iter的reuse接口，但同时也需要保留某些必须的清理动作，最大化复用迭代器，以实现性能的提升。</p>

<p>以上即是本文的全部内容了，希望能为各位同仁提供一些帮助和思路上的启发，如有疑问也可联系我们进行交流。</p>

<h2 id="参考链接">参考链接</h2>

<ul>
  <li><a href="https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.0/get-the-oceanbase-database-by-using-source-code">使用源码构建 OceanBase 数据库</a></li>
  <li><a href="https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.0/deploy-the-oceanbase-database-by-using-obd">使用 OBD 部署 OceanBase 数据库</a></li>
  <li><a href="https://linuxize.com/post/install-rpm-packages-on-ubuntu/">Install RPM packages on Ubuntu</a></li>
  <li><a href="https://github.com/oceanbase/obdeploy/blob/master/README-CN.md#obd-cluster-tenant-create">OceanBase Deployer</a></li>
  <li><a href="https://help.aliyun.com/apsara/enterprise/v_3_13_0_20201215/oceanbase/enterprise-user-guide/hint-overview.html">Hint 概述</a></li>
  <li><a href="https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.0/lsm-tree-architecture">LSM Tree 架构</a></li>
  <li><a href="https://code.visualstudio.com/docs/cpp/launch-json-reference">Configuring C/C++ debugging</a></li>
  <li><a href="https://www.cnblogs.com/happyliu/p/6142929.html">perf + 火焰图分析程序性能</a></li>
  <li><a href="https://blog.eastonman.com/blog/2021/02/use-perf/">使用Perf进行程序热点分析</a></li>
  <li><a href="https://www.brendangregg.com/perf.html">perf Examples</a></li>
  <li><a href="https://www.brendangregg.com/flamegraphs.html">Flame Graphs</a></li>
  <li><a href="https://queue.acm.org/detail.cfm?id=2927301">The Flame Graph</a></li>
</ul>]]></content><author><name>胡梓锐</name></author><category term="oceanbase" /><summary type="html"><![CDATA[💡 作者：华东师范大学 数据科学与工程学院 DBHammer项目组 东亚男儿团队]]></summary></entry><entry><title type="html">从查询树的生成到查询执行 — 以Index Nested Loop Join为例</title><link href="https://dbhammer.github.io/2022/05/11/%E4%BB%8E%E6%9F%A5%E8%AF%A2%E6%A0%91%E7%9A%84%E7%94%9F%E6%88%90%E5%88%B0%E6%9F%A5%E8%AF%A2%E6%89%A7%E8%A1%8C-%E4%BB%A5Index-Nested-Loop-Join%E4%B8%BA%E4%BE%8B.html" rel="alternate" type="text/html" title="从查询树的生成到查询执行 — 以Index Nested Loop Join为例" /><published>2022-05-11T00:00:00+00:00</published><updated>2026-03-31T13:16:28+00:00</updated><id>https://dbhammer.github.io/2022/05/11/%E4%BB%8E%E6%9F%A5%E8%AF%A2%E6%A0%91%E7%9A%84%E7%94%9F%E6%88%90%E5%88%B0%E6%9F%A5%E8%AF%A2%E6%89%A7%E8%A1%8C%20%E2%80%94%20%E4%BB%A5Index%20Nested%20Loop%20Join%E4%B8%BA%E4%BE%8B</id><content type="html" xml:base="https://dbhammer.github.io/2022/05/11/%E4%BB%8E%E6%9F%A5%E8%AF%A2%E6%A0%91%E7%9A%84%E7%94%9F%E6%88%90%E5%88%B0%E6%9F%A5%E8%AF%A2%E6%89%A7%E8%A1%8C-%E4%BB%A5Index-Nested-Loop-Join%E4%B8%BA%E4%BE%8B.html"><![CDATA[<blockquote>
  <p>💡 <strong>作者：华东师范大学 数据科学与工程学院 DBHammer项目组 东亚男儿团队</strong></p>
</blockquote>

<p>本文主体面向对OceanBase数据库源码以及系统性能优化感兴趣的初学者供以技术交流，笔者来自<a href="https://dbhammer.github.io/">华东师范大学数据科学与工程学院DBHammer项目组</a>。</p>

<p>在OceanBase数据库大赛的复赛阶段，我们需要对OceanBase 3.1版本的Nested Loop Join（下称NLJ）这一功能进行优化。因此，在这里我们以NLJ为例，简要介绍我们对OB从查询树的生成到查询执行的一些认识。文章主要从OB的基础代码组织逻辑、查询树的生成和查询执行三个方面进行介绍。</p>

<h2 id="ob的代码组织逻辑">OB的代码组织逻辑</h2>

<p>在刚开始研读OB的代码时，OB简洁的代码风格让人印象深刻。一般而言，OB的函数返回值不是函数的处理结果，而是函数的执行状态。与之相对的，函数的处理结果会通过修改引用参数的方式向上传递。因此，通过分析每个函数的返回值，可以简单得知这个函数的执行流程。所以，在OB的代码中经常能看到类似的代码：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">func_1</span><span class="p">)){</span>
     <span class="n">logging</span>
 <span class="p">}</span> <span class="k">else</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">func_2</span><span class="p">)){</span>
     <span class="n">logging</span>
 <span class="p">}</span> <span class="k">else</span> <span class="p">(</span><span class="n">OB_SUCC</span><span class="p">(</span><span class="n">func_3</span><span class="p">)){</span>
     <span class="n">logging</span>
 <span class="p">}</span>
</code></pre></div></div>
<p>这样的代码实际上相当于顺序执行func_1、func_2、func_3，并在函数执行失败/成功的时候输出日志信息，不继续执行剩下的函数。为了后续叙述的简洁，对于类似的代码，我们将省略函数执行失败的异常处理。</p>

<h2 id="查询树的生成以index-nested-loop-join为例">查询树的生成：以Index Nested Loop Join为例</h2>

<p>在生成物理的查询树之前，OB会先生成逻辑的查询计划，然后根据这个逻辑的查询计划确定实际执行的物理计划。在NLJ中，最重要的就是join算子的生成，所以我们以join算子的生成为例介绍这一部分。</p>

<p>join算子的生成主要依赖于<code class="language-plaintext highlighter-rouge">ObStaticEngineCG::generate_join_spec(ObLogJoin&amp; op, ObJoinSpec&amp; spec)</code>函数。这一函数接收两个引用参数op和spec，op是join对应的逻辑计划，当有多个join条件时，op详细记录第一个join条件，并将其他的join条件存储在<code class="language-plaintext highlighter-rouge">other_join_conditions</code>里；spec是生成的物理执行计划。<code class="language-plaintext highlighter-rouge">generate_join_spec()</code>分为两个部分。在第一部分，它会处理首要的join条件，判断逻辑计划中首要的join条件的join类型，根据这个类型选择生成对应的物理计划。在第二部分，它遍历<code class="language-plaintext highlighter-rouge">other_join_conditions</code>，逐个调用接口函数生成物理执行计划。通过这种方式，OB能够自然地实现对逻辑计划的递归展开，从而构建出对应的物理执行树。我们对这一函数进行了整理和提取，抽象出其中的逻辑如下：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">int</span> <span class="n">ObStaticEngineCG</span><span class="o">::</span><span class="n">generate_join_spec</span><span class="p">(</span><span class="n">ObLogJoin</span><span class="o">&amp;</span> <span class="n">op</span><span class="p">,</span> <span class="n">ObJoinSpec</span><span class="o">&amp;</span> <span class="n">spec</span><span class="p">)</span>
 <span class="p">{</span>
   <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">OB_SUCCESS</span><span class="p">;</span>
   <span class="kt">bool</span> <span class="n">is_late_mat</span> <span class="o">=</span> <span class="p">(</span><span class="n">phy_plan_</span><span class="o">-&gt;</span><span class="n">get_is_late_materialized</span><span class="p">()</span> <span class="o">||</span> <span class="n">op</span><span class="p">.</span><span class="n">is_late_mat</span><span class="p">());</span>
   <span class="n">phy_plan_</span><span class="o">-&gt;</span><span class="n">set_is_late_materialized</span><span class="p">(</span><span class="n">is_late_mat</span><span class="p">);</span>

   <span class="n">spec</span><span class="p">.</span><span class="n">join_type_</span> <span class="o">=</span> <span class="n">op</span><span class="p">.</span><span class="n">get_join_type</span><span class="p">();</span>
   <span class="k">if</span> <span class="p">(</span><span class="n">MERGE_JOIN</span> <span class="o">==</span> <span class="n">op</span><span class="p">.</span><span class="n">get_join_algo</span><span class="p">())</span> <span class="p">{</span>
       <span class="c1">// 查询计划是merge join时的处理，暂略</span>
   <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">NESTED_LOOP_JOIN</span> <span class="o">==</span> <span class="n">op</span><span class="p">.</span><span class="n">get_join_algo</span><span class="p">())</span> <span class="p">{</span>  <span class="c1">// nested loop join</span>
     <span class="k">if</span> <span class="p">(</span><span class="mi">0</span> <span class="o">!=</span> <span class="n">op</span><span class="p">.</span><span class="n">get_equal_join_conditions</span><span class="p">().</span><span class="n">count</span><span class="p">())</span> <span class="p">{</span>
     <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
       <span class="n">ObBasicNestedLoopJoinSpec</span><span class="o">&amp;</span> <span class="n">nlj_spec</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">ObBasicNestedLoopJoinSpec</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">spec</span><span class="p">);</span>
       <span class="n">nlj_spec</span><span class="p">.</span><span class="n">enable_gi_partition_pruning_</span> <span class="o">=</span> <span class="n">op</span><span class="p">.</span><span class="n">is_enable_gi_partition_pruning</span><span class="p">();</span>
       <span class="k">const</span> <span class="n">ObIArray</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">pair</span><span class="o">&lt;</span><span class="kt">int64_t</span><span class="p">,</span> <span class="n">ObRawExpr</span><span class="o">*&gt;&gt;&amp;</span> <span class="n">nl_params</span> <span class="o">=</span> <span class="n">op</span><span class="p">.</span><span class="n">get_nl_params</span><span class="p">();</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">nlj_spec</span><span class="p">.</span><span class="n">enable_gi_partition_pruning_</span> <span class="o">&amp;&amp;</span>
           <span class="n">OB_FAIL</span><span class="p">(</span><span class="n">do_gi_partition_pruning</span><span class="p">(</span><span class="n">op</span><span class="p">,</span> <span class="n">nlj_spec</span><span class="p">)))</span> <span class="p">{</span> <span class="c1">// 如果可以的话，进行分区裁剪</span>
       <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">nlj_spec</span><span class="p">.</span><span class="n">init_param_count</span><span class="p">(</span><span class="n">nl_params</span><span class="p">.</span><span class="n">count</span><span class="p">())))</span> <span class="p">{</span> <span class="c1">// 初始化join的参数数量</span>
       <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
         <span class="n">ObIArray</span><span class="o">&lt;</span><span class="n">ObRawExpr</span><span class="o">*&gt;&amp;</span> <span class="n">exec_param_exprs</span> <span class="o">=</span> <span class="n">op</span><span class="p">.</span><span class="n">get_stmt</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">get_exec_param_ref_exprs</span><span class="p">();</span>
         <span class="n">ARRAY_FOREACH</span><span class="p">(</span><span class="n">nl_params</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span><span class="c1">// 遍历每个参数</span>
         <span class="p">{</span>
             <span class="c1">// 根据参数生成物理执行算子，具体内容略</span>
         <span class="p">}</span>
         <span class="k">if</span> <span class="p">(</span><span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">PHY_NESTED_LOOP_JOIN</span> <span class="o">==</span> <span class="n">spec</span><span class="p">.</span><span class="n">type_</span><span class="p">)</span> <span class="p">{</span>
           <span class="c1">// 判断能否使用batch nlj，如果可以就更新flag</span>
           <span class="kt">bool</span> <span class="n">use_batch_nlj</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
           <span class="n">ObNestedLoopJoinSpec</span><span class="o">&amp;</span> <span class="n">nlj</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">ObNestedLoopJoinSpec</span><span class="o">&amp;&gt;</span><span class="p">(</span><span class="n">spec</span><span class="p">);</span>
           <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">op</span><span class="p">.</span><span class="n">can_use_batch_nlj</span><span class="p">(</span><span class="n">use_batch_nlj</span><span class="p">)))</span> <span class="p">{</span><span class="c1">// 判断能不能用，当join条件有复数字段组成时不使用batch</span>
           <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">use_batch_nlj</span><span class="p">)</span> <span class="p">{</span>
             <span class="n">nlj</span><span class="p">.</span><span class="n">use_group_</span> <span class="o">=</span> <span class="n">use_batch_nlj</span><span class="p">;</span>
             <span class="k">if</span> <span class="p">(</span><span class="n">OB_ISNULL</span><span class="p">(</span><span class="n">nlj</span><span class="p">.</span><span class="n">get_right</span><span class="p">())</span> <span class="o">||</span> <span class="n">PHY_TABLE_SCAN</span> <span class="o">!=</span> <span class="n">nlj</span><span class="p">.</span><span class="n">get_right</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">type_</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 只有table scan的时候才会选择batch nlj</span>
             <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
               <span class="k">const</span> <span class="n">ObTableScanSpec</span><span class="o">*</span> <span class="n">right_tsc</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="n">ObTableScanSpec</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">nlj</span><span class="p">.</span><span class="n">get_right</span><span class="p">());</span>
               <span class="k">const_cast</span><span class="o">&lt;</span><span class="n">ObTableScanSpec</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">right_tsc</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">batch_scan_flag_</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
             <span class="p">}</span>
           <span class="p">}</span>
         <span class="p">}</span>
       <span class="p">}</span>
     <span class="p">}</span>
   <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">HASH_JOIN</span> <span class="o">==</span> <span class="n">op</span><span class="p">.</span><span class="n">get_join_algo</span><span class="p">())</span> <span class="p">{</span>
     <span class="c1">// 查询计划是hash join时的处理，暂略</span>
   <span class="p">}</span>
   <span class="k">const</span> <span class="n">common</span><span class="o">::</span><span class="n">ObIArray</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">pair</span><span class="o">&lt;</span><span class="kt">int64_t</span><span class="p">,</span> <span class="n">ObRawExpr</span><span class="o">*&gt;&gt;&amp;</span> <span class="n">exec_params</span> <span class="o">=</span> <span class="n">op</span><span class="p">.</span><span class="n">get_exec_params</span><span class="p">();</span>
 
   <span class="c1">// 2. add other join conditions</span>
   <span class="k">const</span> <span class="n">ObIArray</span><span class="o">&lt;</span><span class="n">ObRawExpr</span><span class="o">*&gt;&amp;</span> <span class="n">other_join_conds</span> <span class="o">=</span> <span class="n">op</span><span class="p">.</span><span class="n">get_other_join_conditions</span><span class="p">();</span>
   <span class="c1">// 初始化other condition</span>
   <span class="n">OZ</span><span class="p">(</span><span class="n">spec</span><span class="p">.</span><span class="n">other_join_conds_</span><span class="p">.</span><span class="n">init</span><span class="p">(</span><span class="n">other_join_conds</span><span class="p">.</span><span class="n">count</span><span class="p">()));</span>
 
   <span class="n">ARRAY_FOREACH</span><span class="p">(</span><span class="n">other_join_conds</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span> <span class="c1">// 遍历剩下的条件</span>
   <span class="p">{</span>
     <span class="c1">// 逐个生成剩下条件的物理算子</span>
   <span class="p">}</span>  <span class="c1">// end for</span>
 
   <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
 <span class="p">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">generate_join_spec()</code> 对于NLJ最为特别的处理就是判断能否使用batch NLJ，这也是我们所关注的第一个优化点。通过debug我们知道，由于比赛中的查询语句的join条件含有两个字段，这个函数不会选择使用batch NLJ。因此，我们通过扩展OB对batch NLJ的支持和修改判定条件，使得<code class="language-plaintext highlighter-rouge">generate_join_spec()</code>能将比赛中的查询转化为batch NLJ的物理执行计划。</p>

<h2 id="查询执行以index-nested-loop-join为例">查询执行：以Index Nested Loop Join为例</h2>

<p>当逻辑执行计划里的NLJ经过<code class="language-plaintext highlighter-rouge">generate_join_spec</code>转换为物理的执行算子之后，OB就会通过火山模型逐层执行物理算子，并一次向上层算子呈递一行数据。对于NLJ而言，这部分功能主要由<code class="language-plaintext highlighter-rouge">ObNestedLoopJoinOp::inner_get_next_row()</code>实现。这一函数并不需要返回值，是因为它会直接通过修改上下文信息的方式保存一次调用所获取的数据。</p>

<p>作为一个join算子，<code class="language-plaintext highlighter-rouge">ObNestedLoopJoinOp</code>很自然地含有两个子节点。对于比赛中的查询语句，我们可以很容易知道这两个子节点都是table scan算子。且不论左右节点的区别，每次调用join算子时需要从左右节点分别获取一行数据并连接。但在执行过程中可能出现很多种情况，比如右节点获取的数据与左节点获取的数据不匹配，遍历完右节点都无法与左节点当前的数据匹配等等。因此，为了更好地实现这部分逻辑，OB在函数中实现了一个小型的状态机，根据当前状态选择后续需要执行的函数。示意图如下：</p>

<pre><code class="language-flow">start=&gt;start: 开始inner_get_next_row
end=&gt;end: 返回结果
left_op=&gt;operation: read_left_operate()
left_going=&gt;operation: read_left_func_going()
left_end=&gt;operation: read_left_func_end()
right_op=&gt;operation: read_right_operate()
right_going=&gt;operation: read_right_func_going()
right_end=&gt;operation: read_right_func_end()
iter_end1=&gt;condition: 左节点存在下一行数据
iter_end2=&gt;condition: 右节点存在下一行数据
output_product=&gt;condition: 连接成功

start-&gt;left_op-&gt;iter_end1
iter_end1(yes)-&gt;left_going-&gt;right_op-&gt;iter_end2
iter_end1(no)-&gt;left_end-&gt;end
iter_end2(yes)-&gt;right_going-&gt;output_product
iter_end2(no)-&gt;right_end-&gt;output_product
output_product(yes)-&gt;end
output_product(no)-&gt;left_op
</code></pre>
<p>其代码抽象如下：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">ObNestedLoopJoinOp</span><span class="o">::</span><span class="n">inner_get_next_row</span><span class="p">()</span>
<span class="p">{</span>
  <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">OB_SUCCESS</span><span class="p">;</span>
  <span class="k">if</span> <span class="p">(</span><span class="n">OB_UNLIKELY</span><span class="p">(</span><span class="n">LEFT_SEMI_JOIN</span> <span class="o">==</span> <span class="n">MY_SPEC</span><span class="p">.</span><span class="n">join_type_</span> <span class="o">||</span> <span class="n">LEFT_ANTI_JOIN</span> <span class="o">==</span> <span class="n">MY_SPEC</span><span class="p">.</span><span class="n">join_type_</span><span class="p">))</span> <span class="p">{</span>
   	<span class="c1">// 处理半连接，具体内容略</span>
  <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
    <span class="n">state_operation_func_type</span> <span class="n">state_operation</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="n">state_function_func_type</span> <span class="n">state_function</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">func</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
    <span class="n">output_row_produced_</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
    <span class="k">while</span> <span class="p">(</span><span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">output_row_produced_</span><span class="p">)</span> <span class="p">{</span>
      <span class="n">state_operation</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">ObNestedLoopJoinOp</span><span class="o">::</span><span class="n">state_operation_func_</span><span class="p">[</span><span class="n">state_</span><span class="p">];</span><span class="c1">// state_取值为left right，表示当前执行左节点还是右节点的任务</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">OB_ITER_END</span> <span class="o">==</span> <span class="p">(</span><span class="n">ret</span> <span class="o">=</span> <span class="p">(</span><span class="k">this</span><span class="o">-&gt;*</span><span class="n">state_operation</span><span class="p">)()))</span> <span class="p">{</span>
        <span class="n">func</span> <span class="o">=</span> <span class="n">FT_ITER_END</span><span class="p">;</span><span class="c1">// func的取值为end、going，对应取流程图中end或是going后缀的函数</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="n">OB_SUCCESS</span><span class="p">;</span>
      <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">ret</span><span class="p">))</span> <span class="p">{</span>
      <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="n">func</span> <span class="o">=</span> <span class="n">FT_ITER_GOING</span><span class="p">;</span>
      <span class="p">}</span>
      <span class="k">if</span> <span class="p">(</span><span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">))</span> <span class="p">{</span>
        <span class="n">state_function</span> <span class="o">=</span> <span class="k">this</span><span class="o">-&gt;</span><span class="n">ObNestedLoopJoinOp</span><span class="o">::</span><span class="n">state_function_func_</span><span class="p">[</span><span class="n">state_</span><span class="p">][</span><span class="n">func</span><span class="p">];</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">((</span><span class="k">this</span><span class="o">-&gt;*</span><span class="n">state_function</span><span class="p">)())</span> <span class="o">&amp;&amp;</span> <span class="n">OB_ITER_END</span> <span class="o">!=</span> <span class="n">ret</span><span class="p">)</span> <span class="p">{</span>
        <span class="p">}</span>
      <span class="p">}</span>
    <span class="p">}</span>  <span class="c1">// while end</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>其中一共包含6个函数，分别是左右节点的<code class="language-plaintext highlighter-rouge">operate(),func_going(),func_end()</code>函数。其中，<code class="language-plaintext highlighter-rouge">operate()</code>函数负责从对应子节点获取一行数据；<code class="language-plaintext highlighter-rouge">func_going()</code>函数负责判断是否需要切换为另一个节点的函数，并做预处理，如<code class="language-plaintext highlighter-rouge">left_func_going()</code>会根据左节点获取的数据，准备扫描右节点所需要的参数（值得一提的是，因为实际上这个函数会深入底层更新右节点的迭代器，所以开销是很大的），<code class="language-plaintext highlighter-rouge">right_func_going()</code>负责判断是否连接成功；<code class="language-plaintext highlighter-rouge">func_end()</code>判断是否得到了需要的结果，并修改<code class="language-plaintext highlighter-rouge">inner_get_next_row()</code>的返回值。</p>

<p>在这6个函数中，<code class="language-plaintext highlighter-rouge">func_going()</code>和<code class="language-plaintext highlighter-rouge">func_end()</code>的函数逻辑都比较简单，我们不再在这里赘述。以下主要介绍左右节点的<code class="language-plaintext highlighter-rouge">operate()</code>函数，这也是我们优化batch_nlj所主要关心的函数。</p>

<h3 id="join的左子节点实现batch-or-not-batch">Join的左子节点实现：batch, or not batch</h3>

<p>根据在构建物理执行树时的判断，left_operate()存在两种执行逻辑，分别对应不使用batch NLJ和使用batch NLJ。当不使用batch nlj时，函数直接调用对应算子的next_row()方法。这一方法会不断向下获取一行数据，具体的调用栈如下：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">oceanbase</span><span class="o">::</span><span class="n">storage</span><span class="o">::</span><span class="n">ObmultipleScanMergeImpI</span><span class="o">::</span><span class="n">supply_consume</span><span class="p">()</span>
 <span class="n">oceanbase</span><span class="o">::</span><span class="n">storage</span><span class="o">::</span><span class="n">ObmultipleScanMergeImpI</span><span class="o">::</span><span class="n">inner_get_next_row</span><span class="p">()</span>
 <span class="n">oceanbase</span><span class="o">::</span><span class="n">storage</span><span class="o">::</span><span class="n">ObmultipleScanMerge</span><span class="o">::</span><span class="n">inner_get_next_row</span><span class="p">()</span>
 <span class="n">oceanbase</span><span class="o">::</span><span class="n">storage</span><span class="o">::</span><span class="n">ObmultipleMerge</span><span class="o">::</span><span class="n">get_next_row</span><span class="p">()</span>
 <span class="n">oceanbase</span><span class="o">::</span><span class="n">storage</span><span class="o">::</span><span class="n">ObTableScanStoreRowIterator</span><span class="o">::</span><span class="n">get_next_row</span><span class="p">()</span>
 <span class="n">oceanbase</span><span class="o">::</span><span class="n">storage</span><span class="o">::</span><span class="n">ObTableScanRangeArrayRowIterator</span><span class="o">::</span><span class="n">get_next_row</span><span class="p">()</span>
 <span class="n">oceanbase</span><span class="o">::</span><span class="n">storage</span><span class="o">::</span><span class="n">ObTableScanIterator</span><span class="o">::</span><span class="n">get_next_row</span><span class="p">()</span>
 <span class="n">oceanbase</span><span class="o">::</span><span class="n">sql</span><span class="o">::</span><span class="n">ObTableScanOp</span><span class="o">::</span><span class="n">get_next_row_with_mode</span><span class="p">()</span>
 <span class="n">oceanbase</span><span class="o">::</span><span class="n">sql</span><span class="o">::</span><span class="n">ObTableScanOp</span><span class="o">::</span><span class="n">inner_get_next_row</span><span class="p">()</span>
 <span class="n">oceanbase</span><span class="o">::</span><span class="n">sql</span><span class="o">::</span><span class="n">ObOperator</span><span class="o">::</span><span class="n">get_next_row</span><span class="p">()</span>
 <span class="n">oceanbase</span><span class="o">::</span><span class="n">sql</span><span class="o">::</span><span class="n">ObJoinOp</span><span class="o">::</span><span class="n">get_next_left_row</span><span class="p">()</span>
 <span class="n">oceanbase</span><span class="o">::</span><span class="n">sql</span><span class="o">::</span><span class="n">ObBasicNestedLoopJoin</span><span class="o">::</span><span class="n">get_next_left_row</span><span class="p">()</span>
</code></pre></div></div>
<p>通过检查代码，我们发现每层调用的逻辑都非常清晰，主要是调用下层接口以及异常处理，因此不再赘述。我们主要讨论使用batch NLJ时的执行逻辑。</p>

<p>当使用batch NLJ时，join算子会先取出左节点中的一批数据，然后再逐个与右节点进行匹配。这种方式可以更好地利用数据的局部性，提高对磁盘数据的访问效率。为了实现批量获取数据，同时又不改变程序其他部分的逻辑，使用batch NLJ时需要在第一次调用时连续调用多次算子的<code class="language-plaintext highlighter-rouge">next_row()</code>方法并存储对应的数据，在后续调用时直接从存储的数据中导出对应数据。其代码抽象如下：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">int</span> <span class="n">ObNestedLoopJoinOp</span><span class="o">::</span><span class="n">group_read_left_operate</span><span class="p">()</span>
 <span class="p">{</span>
   <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">OB_SUCCESS</span><span class="p">;</span>
   <span class="n">ObTableScanOp</span><span class="o">*</span> <span class="n">right_tsc</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">ObTableScanOp</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">right_</span><span class="p">);</span>
   <span class="k">if</span> <span class="p">(</span><span class="n">left_store_iter_</span><span class="p">.</span><span class="n">is_valid</span><span class="p">()</span> <span class="o">&amp;&amp;</span> <span class="n">left_store_iter_</span><span class="p">.</span><span class="n">has_next</span><span class="p">())</span> <span class="p">{</span>
     <span class="c1">// 当当前还有存储数据时，直接获取存储数据，具体内容略</span>
   <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// 没有存储数据时，批量获取数据</span>
     <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">right_tsc</span><span class="o">-&gt;</span><span class="n">group_rescan_init</span><span class="p">(</span><span class="n">MY_SPEC</span><span class="p">.</span><span class="n">batch_size_</span><span class="p">)))</span> <span class="p">{</span>
     <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">is_left_end_</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 判断左节点是否已经取完</span>
     <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">OB_ISNULL</span><span class="p">(</span><span class="n">mem_context_</span><span class="p">))</span> <span class="p">{</span>
           <span class="c1">// 初始化存储数据的结构，具体内容略</span>
       <span class="p">}</span>
 
       <span class="kt">bool</span> <span class="n">ignore_end</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
       <span class="k">if</span> <span class="p">(</span><span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">))</span> <span class="p">{</span>
           <span class="c1">//初始化或重置访问数据和它的迭代器left_store_和left_store_iter_，具体内容略</span>
         <span class="p">}</span>
         <span class="n">save_last_row_</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
         <span class="k">while</span> <span class="p">(</span><span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">is_full</span><span class="p">())</span> <span class="p">{</span>
             <span class="c1">// 批量获取数据</span>
           <span class="n">clear_evaluated_flag</span><span class="p">();</span> <span class="c1">// 清理访问参数</span>
           <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">get_next_left_row</span><span class="p">()))</span> <span class="p">{</span> <span class="c1">// 获取下一行</span>
             <span class="k">if</span> <span class="p">(</span><span class="n">OB_ITER_END</span> <span class="o">!=</span> <span class="n">ret</span><span class="p">)</span> <span class="p">{</span>
             <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
               <span class="n">is_left_end_</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
             <span class="p">}</span>
           <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">left_store_</span><span class="p">.</span><span class="n">add_row</span><span class="p">(</span><span class="n">left_</span><span class="o">-&gt;</span><span class="n">get_spec</span><span class="p">().</span><span class="n">output_</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">eval_ctx_</span><span class="p">)))</span> <span class="p">{</span><span class="c1">// 存储到存储到对应的结构left_store_中</span>
           <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">prepare_rescan_params</span><span class="p">(</span><span class="nb">true</span> <span class="cm">/*is_group*/</span><span class="p">)))</span> <span class="p">{</span>
           <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">deep_copy_dynamic_obj</span><span class="p">()))</span> <span class="p">{</span>
           <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">right_tsc</span><span class="o">-&gt;</span><span class="n">group_add_query_range</span><span class="p">()))</span> <span class="p">{</span> <span class="c1">// 存储对应的右节点访问参数</span>
           <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
             <span class="n">ignore_end</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
           <span class="p">}</span>
         <span class="p">}</span>
       <span class="p">}</span>
 
       <span class="k">if</span> <span class="p">(</span><span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="n">ignore_end</span> <span class="o">&amp;&amp;</span> <span class="n">OB_ITER_END</span> <span class="o">==</span> <span class="n">ret</span><span class="p">))</span> <span class="p">{</span>
         <span class="c1">// 更新迭代器left_store_iter_，并更新右节点的访问参数，具体内容略</span>
       <span class="p">}</span>
     <span class="p">}</span>
   <span class="p">}</span>
 
   <span class="k">if</span> <span class="p">(</span><span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// 从存储结构left_store_里拿一行数据，作为本次调用的返回结果</span>
     <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">left_store_iter_</span><span class="p">.</span><span class="n">get_next_row</span><span class="p">(</span><span class="n">left_</span><span class="o">-&gt;</span><span class="n">get_spec</span><span class="p">().</span><span class="n">output_</span><span class="p">,</span> <span class="n">eval_ctx_</span><span class="p">)))</span> <span class="p">{</span>
     <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
       <span class="n">left_row_joined_</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
     <span class="p">}</span>
   <span class="p">}</span>
   <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
 <span class="p">}</span>
</code></pre></div></div>
<p>可以注意到，在执行过程中，<code class="language-plaintext highlighter-rouge">group_read_left_operate()</code>会存储每一行数据对应的右节点访问参数。更具体来说，是这一行数据对应第一个join条件字段的值。因此，如果存在多于一个join字段，剩余的字段值不会被存储，这导致使用batch NLJ时无法正确根据join条件过滤结果，这也是OB原本只限制在join条件唯一时使用batch NLJ的原因。为了使用batch NLJ，我们对存储结构进行扩展，使它能存储剩余条件字段的值，从而保证对于比赛的查询语句也能有效且正确使用batch NLJ。</p>

<h3 id="join的右节点实现index-merge">J<strong>oin的右节点实现：index merge</strong></h3>

<p>与左节点稍有不同，右节点并非是一个普通的table scan，而是一个带有index的scan。因此，在这个算子扫描结果的时候，会首先在索引表中进行搜索和定位，然后基于定位的结果直接构造数据表中的迭代器，从而加速数据的获取。这一流程在代码上的体现便是这个算子同时维护了两张表的迭代器，分别是数据表的<code class="language-plaintext highlighter-rouge">main_iter_</code>和索引表的<code class="language-plaintext highlighter-rouge">index_iter_</code>。其代码抽象如下：</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kt">int</span> <span class="n">ObIndexMerge</span><span class="o">::</span><span class="n">get_next_row</span><span class="p">(</span><span class="n">ObStoreRow</span><span class="o">*&amp;</span> <span class="n">row</span><span class="p">)</span>
 <span class="p">{</span>
   <span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">OB_SUCCESS</span><span class="p">;</span>
   <span class="k">if</span> <span class="p">(</span><span class="n">OB_UNLIKELY</span><span class="p">(</span><span class="nb">NULL</span> <span class="o">==</span> <span class="n">index_iter_</span><span class="p">)</span> <span class="o">||</span> <span class="n">OB_UNLIKELY</span><span class="p">(</span><span class="nb">NULL</span> <span class="o">==</span> <span class="n">access_ctx_</span><span class="p">))</span> <span class="p">{</span> <span class="c1">// 没有初始化</span>
   <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">access_ctx_</span><span class="o">-&gt;</span><span class="n">is_end</span><span class="p">())</span> <span class="p">{</span> <span class="c1">// 已经扫描完了</span>
   <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
     <span class="k">while</span> <span class="p">(</span><span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">))</span> <span class="p">{</span>
       <span class="k">if</span> <span class="p">(</span><span class="nb">NULL</span> <span class="o">!=</span> <span class="n">main_iter_</span> <span class="o">&amp;&amp;</span> <span class="n">OB_SUCC</span><span class="p">(</span><span class="n">main_iter_</span><span class="o">-&gt;</span><span class="n">get_next_row</span><span class="p">(</span><span class="n">row</span><span class="p">)))</span> <span class="p">{</span> <span class="c1">// 数据表获取到一行数据，直接结束</span>
         <span class="k">break</span><span class="p">;</span>
       <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
         <span class="k">if</span> <span class="p">(</span><span class="n">OB_ITER_END</span> <span class="o">==</span> <span class="n">ret</span><span class="p">)</span> <span class="p">{</span>
           <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">access_ctx_</span><span class="o">-&gt;</span><span class="n">is_end</span><span class="p">())</span> <span class="p">{</span>
             <span class="n">ret</span> <span class="o">=</span> <span class="n">OB_SUCCESS</span><span class="p">;</span>
           <span class="p">}</span>
           <span class="n">main_iter_</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
         <span class="p">}</span>
 
         <span class="k">if</span> <span class="p">(</span><span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">))</span> <span class="p">{</span>
           <span class="c1">// batch get main table rowkeys from index table</span>
           <span class="c1">// 初始化键值参数等结构，具体内容略</span>
           <span class="k">for</span> <span class="p">(</span><span class="kt">int64_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">MAX_NUM_PER_BATCH</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
             <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">index_iter_</span><span class="o">-&gt;</span><span class="n">get_next_row</span><span class="p">(</span><span class="n">index_row</span><span class="p">)))</span> <span class="p">{</span> <span class="c1">// 索引表获取数据</span>
               <span class="k">if</span> <span class="p">(</span><span class="n">OB_ARRAY_BINDING_SWITCH_ITERATOR</span> <span class="o">==</span> <span class="n">ret</span><span class="p">)</span> <span class="p">{</span>
                 <span class="o">++</span><span class="n">index_range_array_cursor_</span><span class="p">;</span>
                 <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">index_iter_</span><span class="o">-&gt;</span><span class="n">switch_iterator</span><span class="p">(</span><span class="n">index_range_array_cursor_</span><span class="p">)))</span> <span class="p">{</span>
                 <span class="p">}</span>
               <span class="p">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">OB_ITER_END</span> <span class="o">!=</span> <span class="n">ret</span><span class="p">)</span> <span class="p">{</span>
               <span class="p">}</span>
             <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="c1">// 存储获取的数据</span>
               <span class="n">src_key</span><span class="p">.</span><span class="n">assign</span><span class="p">(</span><span class="n">index_row</span><span class="o">-&gt;</span><span class="n">row_val_</span><span class="p">.</span><span class="n">cells_</span><span class="p">,</span> <span class="n">rowkey_cnt_</span><span class="p">);</span>
               <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">src_key</span><span class="p">.</span><span class="n">deep_copy</span><span class="p">(</span><span class="n">dest_key</span><span class="p">.</span><span class="n">get_store_rowkey</span><span class="p">(),</span> <span class="n">rowkey_allocator_</span><span class="p">)))</span> <span class="p">{</span>
               <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                 <span class="n">dest_key</span><span class="p">.</span><span class="n">set_range_array_idx</span><span class="p">(</span><span class="n">index_row</span><span class="o">-&gt;</span><span class="n">range_array_idx_</span><span class="p">);</span>
                 <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">rowkeys_</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">dest_key</span><span class="p">)))</span> <span class="p">{</span>
                 <span class="p">}</span>
               <span class="p">}</span>
             <span class="p">}</span>
           <span class="p">}</span>
           <span class="k">if</span> <span class="p">(</span><span class="n">OB_SUCC</span><span class="p">(</span><span class="n">ret</span><span class="p">))</span> <span class="p">{</span>
             <span class="k">if</span> <span class="p">(</span><span class="n">OB_FAIL</span><span class="p">(</span><span class="n">table_iter_</span><span class="p">.</span><span class="n">open</span><span class="p">(</span><span class="n">rowkeys_</span><span class="p">)))</span> <span class="p">{</span> <span class="c1">// 根据索引表的数据初始化数据表的迭代器</span>
             <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
               <span class="n">main_iter_</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">table_iter_</span><span class="p">;</span>
             <span class="p">}</span>
           <span class="p">}</span>
         <span class="p">}</span>
       <span class="p">}</span>
     <span class="p">}</span>
   <span class="p">}</span>
   <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
 <span class="p">}</span>
</code></pre></div></div>
<h2 id="总结如何从代码结构上对ob的源码进行分析">总结：如何从代码结构上对OB的源码进行分析？</h2>

<p>或许很多人会像我们一样，在一开始看到源码的时候不知所措。毕竟，OB作为一个非常庞大的项目，很难快速找到入手的部分。根据我们的经验，在这种情况下，一般可以先使用perf来快速找到最耗时的执行部分，这部分代码往往与核心逻辑紧密相关，如我们上述展示的部分代码。在此基础上，结合gdb debug，可以获取到执行过程中的主要调用栈。往往我们会发现调用栈很深，但是其中大部分的代码都不会涉及到核心逻辑，不需要逐个深入地研究。因此，再通过OB的代码自注释和基本的数据库知识可以有效地定位到所想要修改的部分。</p>

<p>当定位到要修改的部分之后，如何具体地分析源码的逻辑呢？首先需要熟悉OB的代码风格，能将连续的条件嵌套重新翻译为顺序执行。然后通过代码的自注释去感受实现的功能，由于OB中大部分的类都带有很多层的嵌套，在一开始不断深挖某个细节很容易晕头转向，采用“不求甚解”的态度，先宏观上把握整个执行流程，对于代码的分析会更有利。在了解了整体的流程之后，再结合逐步调试去深入理解其中的执行流程。</p>

<p>希望上述的方法分享，能够帮助大家分析OB代码，为大家在开源社区中的贡献添砖加瓦。</p>]]></content><author><name>胡梓锐</name></author><category term="query" /><summary type="html"><![CDATA[💡 作者：华东师范大学 数据科学与工程学院 DBHammer项目组 东亚男儿团队 本文主体面向对OceanBase数据库源码以及系统性能优化感兴趣的初学者供以技术交流，笔者来自华东师范大学数据科学与工程学院DBHammer项目组。 在OceanBase数据库大赛的复赛阶段，我们需要对OceanBase 3.1版本的Nested Loop Join（下称NLJ）这一功能进行优化。因此，在这里我们以NLJ为例，简要介绍我们对OB从查询树的生成到查询执行的一些认识。文章主要从OB的基础代码组织逻辑、查询树的生成和查询执行三个方面进行介绍。 OB的代码组织逻辑 在刚开始研读OB的代码时，OB简洁的代码风格让人印象深刻。一般而言，OB的函数返回值不是函数的处理结果，而是函数的执行状态。与之相对的，函数的处理结果会通过修改引用参数的方式向上传递。因此，通过分析每个函数的返回值，可以简单得知这个函数的执行流程。所以，在OB的代码中经常能看到类似的代码： if (OB_FAIL(func_1)){ logging } else (OB_FAIL(func_2)){ logging } else (OB_SUCC(func_3)){ logging } 这样的代码实际上相当于顺序执行func_1、func_2、func_3，并在函数执行失败/成功的时候输出日志信息，不继续执行剩下的函数。为了后续叙述的简洁，对于类似的代码，我们将省略函数执行失败的异常处理。 查询树的生成：以Index Nested Loop Join为例 在生成物理的查询树之前，OB会先生成逻辑的查询计划，然后根据这个逻辑的查询计划确定实际执行的物理计划。在NLJ中，最重要的就是join算子的生成，所以我们以join算子的生成为例介绍这一部分。 join算子的生成主要依赖于ObStaticEngineCG::generate_join_spec(ObLogJoin&amp; op, ObJoinSpec&amp; spec)函数。这一函数接收两个引用参数op和spec，op是join对应的逻辑计划，当有多个join条件时，op详细记录第一个join条件，并将其他的join条件存储在other_join_conditions里；spec是生成的物理执行计划。generate_join_spec()分为两个部分。在第一部分，它会处理首要的join条件，判断逻辑计划中首要的join条件的join类型，根据这个类型选择生成对应的物理计划。在第二部分，它遍历other_join_conditions，逐个调用接口函数生成物理执行计划。通过这种方式，OB能够自然地实现对逻辑计划的递归展开，从而构建出对应的物理执行树。我们对这一函数进行了整理和提取，抽象出其中的逻辑如下： int ObStaticEngineCG::generate_join_spec(ObLogJoin&amp; op, ObJoinSpec&amp; spec) { int ret = OB_SUCCESS; bool is_late_mat = (phy_plan_-&gt;get_is_late_materialized() || op.is_late_mat()); phy_plan_-&gt;set_is_late_materialized(is_late_mat); spec.join_type_ = op.get_join_type(); if (MERGE_JOIN == op.get_join_algo()) { // 查询计划是merge join时的处理，暂略 } else if (NESTED_LOOP_JOIN == op.get_join_algo()) { // nested loop join if (0 != op.get_equal_join_conditions().count()) { } else { ObBasicNestedLoopJoinSpec&amp; nlj_spec = static_cast&lt;ObBasicNestedLoopJoinSpec&amp;&gt;(spec); nlj_spec.enable_gi_partition_pruning_ = op.is_enable_gi_partition_pruning(); const ObIArray&lt;std::pair&lt;int64_t, ObRawExpr*&gt;&gt;&amp; nl_params = op.get_nl_params(); if (nlj_spec.enable_gi_partition_pruning_ &amp;&amp; OB_FAIL(do_gi_partition_pruning(op, nlj_spec))) { // 如果可以的话，进行分区裁剪 } else if (OB_FAIL(nlj_spec.init_param_count(nl_params.count()))) { // 初始化join的参数数量 } else { ObIArray&lt;ObRawExpr*&gt;&amp; exec_param_exprs = op.get_stmt()-&gt;get_exec_param_ref_exprs(); ARRAY_FOREACH(nl_params, i)// 遍历每个参数 { // 根据参数生成物理执行算子，具体内容略 } if (OB_SUCC(ret) &amp;&amp; PHY_NESTED_LOOP_JOIN == spec.type_) { // 判断能否使用batch nlj，如果可以就更新flag bool use_batch_nlj = false; ObNestedLoopJoinSpec&amp; nlj = static_cast&lt;ObNestedLoopJoinSpec&amp;&gt;(spec); if (OB_FAIL(op.can_use_batch_nlj(use_batch_nlj))) {// 判断能不能用，当join条件有复数字段组成时不使用batch } else if (use_batch_nlj) { nlj.use_group_ = use_batch_nlj; if (OB_ISNULL(nlj.get_right()) || PHY_TABLE_SCAN != nlj.get_right()-&gt;type_) { // 只有table scan的时候才会选择batch nlj } else { const ObTableScanSpec* right_tsc = static_cast&lt;const ObTableScanSpec*&gt;(nlj.get_right()); const_cast&lt;ObTableScanSpec*&gt;(right_tsc)-&gt;batch_scan_flag_ = true; } } } } } } else if (HASH_JOIN == op.get_join_algo()) { // 查询计划是hash join时的处理，暂略 } const common::ObIArray&lt;std::pair&lt;int64_t, ObRawExpr*&gt;&gt;&amp; exec_params = op.get_exec_params(); // 2. add other join conditions const ObIArray&lt;ObRawExpr*&gt;&amp; other_join_conds = op.get_other_join_conditions(); // 初始化other condition OZ(spec.other_join_conds_.init(other_join_conds.count())); ARRAY_FOREACH(other_join_conds, i) // 遍历剩下的条件 { // 逐个生成剩下条件的物理算子 } // end for return ret; } generate_join_spec() 对于NLJ最为特别的处理就是判断能否使用batch NLJ，这也是我们所关注的第一个优化点。通过debug我们知道，由于比赛中的查询语句的join条件含有两个字段，这个函数不会选择使用batch NLJ。因此，我们通过扩展OB对batch NLJ的支持和修改判定条件，使得generate_join_spec()能将比赛中的查询转化为batch NLJ的物理执行计划。 查询执行：以Index Nested Loop Join为例 当逻辑执行计划里的NLJ经过generate_join_spec转换为物理的执行算子之后，OB就会通过火山模型逐层执行物理算子，并一次向上层算子呈递一行数据。对于NLJ而言，这部分功能主要由ObNestedLoopJoinOp::inner_get_next_row()实现。这一函数并不需要返回值，是因为它会直接通过修改上下文信息的方式保存一次调用所获取的数据。 作为一个join算子，ObNestedLoopJoinOp很自然地含有两个子节点。对于比赛中的查询语句，我们可以很容易知道这两个子节点都是table scan算子。且不论左右节点的区别，每次调用join算子时需要从左右节点分别获取一行数据并连接。但在执行过程中可能出现很多种情况，比如右节点获取的数据与左节点获取的数据不匹配，遍历完右节点都无法与左节点当前的数据匹配等等。因此，为了更好地实现这部分逻辑，OB在函数中实现了一个小型的状态机，根据当前状态选择后续需要执行的函数。示意图如下： start=&gt;start: 开始inner_get_next_row end=&gt;end: 返回结果 left_op=&gt;operation: read_left_operate() left_going=&gt;operation: read_left_func_going() left_end=&gt;operation: read_left_func_end() right_op=&gt;operation: read_right_operate() right_going=&gt;operation: read_right_func_going() right_end=&gt;operation: read_right_func_end() iter_end1=&gt;condition: 左节点存在下一行数据 iter_end2=&gt;condition: 右节点存在下一行数据 output_product=&gt;condition: 连接成功 start-&gt;left_op-&gt;iter_end1 iter_end1(yes)-&gt;left_going-&gt;right_op-&gt;iter_end2 iter_end1(no)-&gt;left_end-&gt;end iter_end2(yes)-&gt;right_going-&gt;output_product iter_end2(no)-&gt;right_end-&gt;output_product output_product(yes)-&gt;end output_product(no)-&gt;left_op 其代码抽象如下： int ObNestedLoopJoinOp::inner_get_next_row() { int ret = OB_SUCCESS; if (OB_UNLIKELY(LEFT_SEMI_JOIN == MY_SPEC.join_type_ || LEFT_ANTI_JOIN == MY_SPEC.join_type_)) { // 处理半连接，具体内容略 } else { state_operation_func_type state_operation = NULL; state_function_func_type state_function = NULL; int func = -1; output_row_produced_ = false; while (OB_SUCC(ret) &amp;&amp; !output_row_produced_) { state_operation = this-&gt;ObNestedLoopJoinOp::state_operation_func_[state_];// state_取值为left right，表示当前执行左节点还是右节点的任务 if (OB_ITER_END == (ret = (this-&gt;*state_operation)())) { func = FT_ITER_END;// func的取值为end、going，对应取流程图中end或是going后缀的函数 ret = OB_SUCCESS; } else if (OB_FAIL(ret)) { } else { func = FT_ITER_GOING; } if (OB_SUCC(ret)) { state_function = this-&gt;ObNestedLoopJoinOp::state_function_func_[state_][func]; if (OB_FAIL((this-&gt;*state_function)()) &amp;&amp; OB_ITER_END != ret) { } } } // while end } return ret; } 其中一共包含6个函数，分别是左右节点的operate(),func_going(),func_end()函数。其中，operate()函数负责从对应子节点获取一行数据；func_going()函数负责判断是否需要切换为另一个节点的函数，并做预处理，如left_func_going()会根据左节点获取的数据，准备扫描右节点所需要的参数（值得一提的是，因为实际上这个函数会深入底层更新右节点的迭代器，所以开销是很大的），right_func_going()负责判断是否连接成功；func_end()判断是否得到了需要的结果，并修改inner_get_next_row()的返回值。 在这6个函数中，func_going()和func_end()的函数逻辑都比较简单，我们不再在这里赘述。以下主要介绍左右节点的operate()函数，这也是我们优化batch_nlj所主要关心的函数。 Join的左子节点实现：batch, or not batch 根据在构建物理执行树时的判断，left_operate()存在两种执行逻辑，分别对应不使用batch NLJ和使用batch NLJ。当不使用batch nlj时，函数直接调用对应算子的next_row()方法。这一方法会不断向下获取一行数据，具体的调用栈如下： oceanbase::storage::ObmultipleScanMergeImpI::supply_consume() oceanbase::storage::ObmultipleScanMergeImpI::inner_get_next_row() oceanbase::storage::ObmultipleScanMerge::inner_get_next_row() oceanbase::storage::ObmultipleMerge::get_next_row() oceanbase::storage::ObTableScanStoreRowIterator::get_next_row() oceanbase::storage::ObTableScanRangeArrayRowIterator::get_next_row() oceanbase::storage::ObTableScanIterator::get_next_row() oceanbase::sql::ObTableScanOp::get_next_row_with_mode() oceanbase::sql::ObTableScanOp::inner_get_next_row() oceanbase::sql::ObOperator::get_next_row() oceanbase::sql::ObJoinOp::get_next_left_row() oceanbase::sql::ObBasicNestedLoopJoin::get_next_left_row() 通过检查代码，我们发现每层调用的逻辑都非常清晰，主要是调用下层接口以及异常处理，因此不再赘述。我们主要讨论使用batch NLJ时的执行逻辑。 当使用batch NLJ时，join算子会先取出左节点中的一批数据，然后再逐个与右节点进行匹配。这种方式可以更好地利用数据的局部性，提高对磁盘数据的访问效率。为了实现批量获取数据，同时又不改变程序其他部分的逻辑，使用batch NLJ时需要在第一次调用时连续调用多次算子的next_row()方法并存储对应的数据，在后续调用时直接从存储的数据中导出对应数据。其代码抽象如下： int ObNestedLoopJoinOp::group_read_left_operate() { int ret = OB_SUCCESS; ObTableScanOp* right_tsc = reinterpret_cast&lt;ObTableScanOp*&gt;(right_); if (left_store_iter_.is_valid() &amp;&amp; left_store_iter_.has_next()) { // 当当前还有存储数据时，直接获取存储数据，具体内容略 } else { // 没有存储数据时，批量获取数据 if (OB_FAIL(right_tsc-&gt;group_rescan_init(MY_SPEC.batch_size_))) { } else if (is_left_end_) { // 判断左节点是否已经取完 } else { if (OB_ISNULL(mem_context_)) { // 初始化存储数据的结构，具体内容略 } bool ignore_end = false; if (OB_SUCC(ret)) { //初始化或重置访问数据和它的迭代器left_store_和left_store_iter_，具体内容略 } save_last_row_ = false; while (OB_SUCC(ret) &amp;&amp; !is_full()) { // 批量获取数据 clear_evaluated_flag(); // 清理访问参数 if (OB_FAIL(get_next_left_row())) { // 获取下一行 if (OB_ITER_END != ret) { } else { is_left_end_ = true; } } else if (OB_FAIL(left_store_.add_row(left_-&gt;get_spec().output_, &amp;eval_ctx_))) {// 存储到存储到对应的结构left_store_中 } else if (OB_FAIL(prepare_rescan_params(true /*is_group*/))) { } else if (OB_FAIL(deep_copy_dynamic_obj())) { } else if (OB_FAIL(right_tsc-&gt;group_add_query_range())) { // 存储对应的右节点访问参数 } else { ignore_end = true; } } } if (OB_SUCC(ret) || (ignore_end &amp;&amp; OB_ITER_END == ret)) { // 更新迭代器left_store_iter_，并更新右节点的访问参数，具体内容略 } } } if (OB_SUCC(ret)) { // 从存储结构left_store_里拿一行数据，作为本次调用的返回结果 if (OB_FAIL(left_store_iter_.get_next_row(left_-&gt;get_spec().output_, eval_ctx_))) { } else { left_row_joined_ = false; } } return ret; } 可以注意到，在执行过程中，group_read_left_operate()会存储每一行数据对应的右节点访问参数。更具体来说，是这一行数据对应第一个join条件字段的值。因此，如果存在多于一个join字段，剩余的字段值不会被存储，这导致使用batch NLJ时无法正确根据join条件过滤结果，这也是OB原本只限制在join条件唯一时使用batch NLJ的原因。为了使用batch NLJ，我们对存储结构进行扩展，使它能存储剩余条件字段的值，从而保证对于比赛的查询语句也能有效且正确使用batch NLJ。 Join的右节点实现：index merge 与左节点稍有不同，右节点并非是一个普通的table scan，而是一个带有index的scan。因此，在这个算子扫描结果的时候，会首先在索引表中进行搜索和定位，然后基于定位的结果直接构造数据表中的迭代器，从而加速数据的获取。这一流程在代码上的体现便是这个算子同时维护了两张表的迭代器，分别是数据表的main_iter_和索引表的index_iter_。其代码抽象如下： int ObIndexMerge::get_next_row(ObStoreRow*&amp; row) { int ret = OB_SUCCESS; if (OB_UNLIKELY(NULL == index_iter_) || OB_UNLIKELY(NULL == access_ctx_)) { // 没有初始化 } else if (access_ctx_-&gt;is_end()) { // 已经扫描完了 } else { while (OB_SUCC(ret)) { if (NULL != main_iter_ &amp;&amp; OB_SUCC(main_iter_-&gt;get_next_row(row))) { // 数据表获取到一行数据，直接结束 break; } else { if (OB_ITER_END == ret) { if (!access_ctx_-&gt;is_end()) { ret = OB_SUCCESS; } main_iter_ = NULL; } if (OB_SUCC(ret)) { // batch get main table rowkeys from index table // 初始化键值参数等结构，具体内容略 for (int64_t i = 0; OB_SUCC(ret) &amp;&amp; i &lt; MAX_NUM_PER_BATCH; ++i) { if (OB_FAIL(index_iter_-&gt;get_next_row(index_row))) { // 索引表获取数据 if (OB_ARRAY_BINDING_SWITCH_ITERATOR == ret) { ++index_range_array_cursor_; if (OB_FAIL(index_iter_-&gt;switch_iterator(index_range_array_cursor_))) { } } else if (OB_ITER_END != ret) { } } else { // 存储获取的数据 src_key.assign(index_row-&gt;row_val_.cells_, rowkey_cnt_); if (OB_FAIL(src_key.deep_copy(dest_key.get_store_rowkey(), rowkey_allocator_))) { } else { dest_key.set_range_array_idx(index_row-&gt;range_array_idx_); if (OB_FAIL(rowkeys_.push_back(dest_key))) { } } } } if (OB_SUCC(ret)) { if (OB_FAIL(table_iter_.open(rowkeys_))) { // 根据索引表的数据初始化数据表的迭代器 } else { main_iter_ = &amp;table_iter_; } } } } } } return ret; } 总结：如何从代码结构上对OB的源码进行分析？ 或许很多人会像我们一样，在一开始看到源码的时候不知所措。毕竟，OB作为一个非常庞大的项目，很难快速找到入手的部分。根据我们的经验，在这种情况下，一般可以先使用perf来快速找到最耗时的执行部分，这部分代码往往与核心逻辑紧密相关，如我们上述展示的部分代码。在此基础上，结合gdb debug，可以获取到执行过程中的主要调用栈。往往我们会发现调用栈很深，但是其中大部分的代码都不会涉及到核心逻辑，不需要逐个深入地研究。因此，再通过OB的代码自注释和基本的数据库知识可以有效地定位到所想要修改的部分。 当定位到要修改的部分之后，如何具体地分析源码的逻辑呢？首先需要熟悉OB的代码风格，能将连续的条件嵌套重新翻译为顺序执行。然后通过代码的自注释去感受实现的功能，由于OB中大部分的类都带有很多层的嵌套，在一开始不断深挖某个细节很容易晕头转向，采用“不求甚解”的态度，先宏观上把握整个执行流程，对于代码的分析会更有利。在了解了整体的流程之后，再结合逐步调试去深入理解其中的执行流程。 希望上述的方法分享，能够帮助大家分析OB代码，为大家在开源社区中的贡献添砖加瓦。]]></summary></entry><entry><title type="html">OceanBase对分布式事务的支持能力评测与分析</title><link href="https://dbhammer.github.io/2022/03/31/OceanBase%E5%AF%B9%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E7%9A%84%E6%94%AF%E6%8C%81%E8%83%BD%E5%8A%9B%E8%AF%84%E6%B5%8B%E4%B8%8E%E5%88%86%E6%9E%90.html" rel="alternate" type="text/html" title="OceanBase对分布式事务的支持能力评测与分析" /><published>2022-03-31T00:00:00+00:00</published><updated>2026-03-31T13:16:28+00:00</updated><id>https://dbhammer.github.io/2022/03/31/OceanBase%E5%AF%B9%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E7%9A%84%E6%94%AF%E6%8C%81%E8%83%BD%E5%8A%9B%E8%AF%84%E6%B5%8B%E4%B8%8E%E5%88%86%E6%9E%90</id><content type="html" xml:base="https://dbhammer.github.io/2022/03/31/OceanBase%E5%AF%B9%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E7%9A%84%E6%94%AF%E6%8C%81%E8%83%BD%E5%8A%9B%E8%AF%84%E6%B5%8B%E4%B8%8E%E5%88%86%E6%9E%90.html"><![CDATA[<h2 id="一目的">一、目的</h2>

<ul>
  <li>分布式数据库的一大设计目标是通过增加分布式节点来提高数据库的性能，如吞吐。但是分布式环境给事务处理带来的优势有可能会由于分布式事务的产生而削弱，甚至会造成性能的恶化。本文主要评测OceanBase对分布式事务的支持能力以及OceanBase提出的tablegroup技术对分布式事务执行性能产生的影响。</li>
</ul>

<h2 id="二初探tpc-c中neworder事务">二、初探TPC-C中NewOrder事务</h2>

<ul>
  <li>
    <p>在<em>TPC-C</em>中，<em>NewOrder</em>事务负责下订单任务，它会在<em>Stock</em>表中更新5-15个items的库存。<em>NewOrder</em>事务会有1%的概率更新远程的仓库的<em>Stock</em>信息，因此会产生1%的分布式事务。<em>NewOrder</em>的事务模板如下所示。</p>

    <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">TX</span><span class="p">[</span><span class="n">NewOrder</span><span class="p">]</span>
<span class="k">SELECT</span> <span class="n">C_DISCOUNT</span><span class="p">,</span> <span class="n">C_LAST</span><span class="p">,</span> <span class="n">C_CREDIT</span> <span class="k">FROM</span> <span class="n">CUSTOMER</span> <span class="k">WHERE</span> <span class="n">C_W_ID</span> <span class="o">=</span> <span class="o">?</span> <span class="k">AND</span> <span class="n">C_D_ID</span> <span class="o">=</span> <span class="o">?</span> <span class="k">AND</span> <span class="n">C_ID</span> <span class="o">=</span> <span class="o">?</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="n">W_TAX</span> <span class="k">FROM</span> <span class="n">WAREHOUSE</span> <span class="k">WHERE</span> <span class="n">W_ID</span> <span class="o">=</span> <span class="o">?</span><span class="p">;</span>
<span class="k">SELECT</span> <span class="n">D_NEXT_O_ID</span><span class="p">,</span> <span class="n">D_TAX</span> <span class="k">FROM</span> <span class="n">DISTRICT</span> <span class="k">WHERE</span> <span class="n">D_W_ID</span> <span class="o">=</span> <span class="o">?</span> <span class="k">AND</span> <span class="n">D_ID</span> <span class="o">=</span> <span class="o">?</span> <span class="k">FOR</span> <span class="k">UPDATE</span><span class="p">;</span>
<span class="k">UPDATE</span> <span class="n">DISTRICT</span> <span class="k">SET</span> <span class="n">D_NEXT_O_ID</span> <span class="o">=</span> <span class="n">D_NEXT_O_ID</span> <span class="o">+</span> <span class="mi">1</span> <span class="k">WHERE</span> <span class="n">D_W_ID</span> <span class="o">=</span> <span class="o">?</span> <span class="k">AND</span> <span class="n">D_ID</span> <span class="o">=</span> <span class="o">?</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">OORDER</span> <span class="p">(</span><span class="n">O_ID</span><span class="p">,</span> <span class="n">O_D_ID</span><span class="p">,</span> <span class="n">O_W_ID</span><span class="p">,</span> <span class="n">O_C_ID</span><span class="p">,</span> <span class="n">O_ENTRY_D</span><span class="p">,</span> <span class="n">O_OL_CNT</span><span class="p">,</span> <span class="n">O_ALL_LOCAL</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span> <span class="p">,</span> <span class="o">?</span><span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">NEW_ORDER</span> <span class="p">(</span><span class="n">NO_O_ID</span><span class="p">,</span> <span class="n">NO_D_ID</span><span class="p">,</span> <span class="n">NO_W_ID</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">);</span>
<span class="n">Multiple</span>
<span class="k">SELECT</span> <span class="n">I_PRICE</span><span class="p">,</span> <span class="n">I_NAME</span><span class="p">,</span> <span class="n">I_DATA</span> <span class="k">FROM</span> <span class="n">ITEM</span> <span class="k">WHERE</span> <span class="n">I_ID</span> <span class="o">=</span> <span class="o">?</span><span class="p">;</span> <span class="n">C_DISCOUNT</span>
<span class="k">SELECT</span> <span class="n">S_QUANTITY</span><span class="p">,</span> <span class="n">S_DATA</span><span class="p">,</span> <span class="n">S_DIST_01</span><span class="p">,</span> <span class="n">S_DIST_02</span><span class="p">,</span> <span class="n">S_DIST_03</span><span class="p">,</span> <span class="n">S_DIST_04</span><span class="p">,</span> <span class="n">S_DIST_05</span><span class="p">,</span> <span class="n">S_DIST_06</span><span class="p">,</span> <span class="n">S_DIST_07</span><span class="p">,</span> <span class="n">S_DIST_08</span><span class="p">,</span> <span class="n">S_DIST_09</span><span class="p">,</span> <span class="n">S_DIST_10</span> <span class="k">FROM</span> <span class="n">STOCK</span> <span class="k">WHERE</span> <span class="n">S_I_ID</span> <span class="o">=</span> <span class="o">?</span> <span class="k">AND</span> <span class="n">S_W_ID</span> <span class="o">=</span> <span class="o">?</span> <span class="k">FOR</span> <span class="k">UPDATE</span><span class="p">;</span>
<span class="o">//</span><span class="err">此处有</span><span class="mi">1</span><span class="o">%</span><span class="err">的可能</span><span class="n">s_w_id</span> <span class="o">!=</span> <span class="n">w_id</span><span class="err">，从而产生分布式事务</span>
<span class="k">UPDATE</span> <span class="n">STOCK</span> <span class="k">SET</span> <span class="n">S_QUANTITY</span> <span class="o">=</span> <span class="o">?</span><span class="p">,</span> <span class="n">S_YTD</span> <span class="o">=</span> <span class="n">S_YTD</span> <span class="o">+</span> <span class="o">?</span><span class="p">,</span> <span class="n">S_ORDER_CNT</span> <span class="o">=</span> <span class="n">S_ORDER_CNT</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">S_REMOTE_CNT</span> <span class="o">=</span> <span class="n">S_REMOTE_CNT</span> <span class="o">+</span> <span class="o">?</span> <span class="k">WHERE</span> <span class="n">S_I_ID</span> <span class="o">=</span> <span class="o">?</span> <span class="k">AND</span> <span class="n">S_W_ID</span> <span class="o">=</span> <span class="o">?</span><span class="p">;</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="n">ORDER_LINE</span> <span class="p">(</span><span class="n">OL_O_ID</span><span class="p">,</span> <span class="n">OL_D_ID</span><span class="p">,</span> <span class="n">OL_W_ID</span><span class="p">,</span> <span class="n">OL_NUMBER</span><span class="p">,</span> <span class="n">OL_I_ID</span><span class="p">,</span> <span class="n">OL_SUPPLY_W_ID</span><span class="p">,</span> <span class="n">OL_QUANTITY</span><span class="p">,</span> <span class="n">OL_AMOUNT</span><span class="p">,</span> <span class="n">OL_DIST_INFO</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span><span class="o">?</span><span class="p">,</span><span class="o">?</span><span class="p">,</span><span class="o">?</span><span class="p">,</span> <span class="o">?</span><span class="p">,</span><span class="o">?</span><span class="p">,</span><span class="o">?</span><span class="p">,</span><span class="o">?</span><span class="p">,</span><span class="o">?</span><span class="p">,</span><span class="o">?</span><span class="p">);</span>
<span class="n">EndMultiple</span>
<span class="n">EndTX</span> 
</code></pre></div>    </div>
  </li>
  <li>
    <p>为了分析OceanBase对分布式事务的支持能力，我们将分布式事务比例进行参数化，将它分别设为1%，10%，20%，40%，80%和100%。关于BenchmarkSQL的代码修改见实验配置。</p>
  </li>
</ul>

<h2 id="三实验准备">三、实验准备</h2>

<ul>
  <li>(必选) BenchmarkSQL运行TPC-C中的NewOrder事务，为了合理设置分布式事务比例，对NewOrder事务做了略微的改动，改动内容以及benchmarkSQL的配置参数在下方。</li>
  <li>（必须）OceanBase v3.1.2版本</li>
</ul>

<h2 id="四实验配置">四、实验配置</h2>

<h3 id="1-机器配置">1. 机器配置</h3>

<ul>
  <li>
    <p>为测试分布式事务的影响，本次实验将OceanBase部署在10台机器上。其中9台机器的配置为：8核CPU，32G内存，上面各部署了一个OBServer；其中1台机器的配置为：16核CPU, 16G内存，上面部署了一个OBProxy，同时也作为实验的客户端。</p>
  </li>
  <li>下面介绍了我们的集群配置情况，对BenchmarkSQL修订的内容和配置文件。运行BenchmarkSQL对OceanBase的系统变量和用户变量的调整见官网。</li>
  <li>本实验中使用的是mysql mode，需要将BenchmarkSQL和OceanBase适配，可见官网。</li>
</ul>

<h3 id="2-oceanbase集群配置">2. OceanBase集群配置</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">## Only need to configure when remote login is required</span>
user:
  username: xxx
  password: xxx
  <span class="c">#key_file: .ssh/authorized_keys</span>
oceanbase-ce:
  servers:
    - name: host1
      ip: 10.24.14.8
    - name: host2
      ip: 10.24.14.136
    - name: host3
      ip: 10.24.14.75
    - name: host4
      ip: 10.24.14.178
    - name: host5
      ip: 10.24.14.60
    - name: host6
      ip: 10.24.14.120
    - name: host7
      ip: 10.24.14.126
    - name: host8
      ip: 10.24.14.171
    - name: host9
      ip: 10.24.14.181
  global:
    devname: eth0
    cluster_id: 1
    memory_limit: 28G
    system_memory: 8G
    stack_size: 512K
    cpu_count: 16
    cache_wash_threshold: 1G
    __min_full_resource_pool_memory: 268435456
    workers_per_cpu_quota: 10
    schema_history_expire_time: 1d
    net_thread_count: 4
    major_freeze_duty_time: Disable
    minor_freeze_times: 10
    enable_separate_sys_clog: 0
    enable_merge_by_turn: FALSE
    datafile_disk_percentage: 35
    syslog_level: WARN
    enable_syslog_recycle: <span class="nb">true
    </span>max_syslog_file_count: 4
    appname: ob209
  host1:
    mysql_port: 2883
    rpc_port: 2882
    home_path: /data/obdata
    zone: zone0
  host2:
    mysql_port: 2883
    rpc_port: 2882
    home_path: /data/obdata
    zone: zone0
  host3:
    mysql_port: 2883
    rpc_port: 2882
    home_path: /data/obdata
    zone: zone0
  host4:
    mysql_port: 2883
    rpc_port: 2882
    home_path: /data/obdata
    zone: zone1
  host5:
    mysql_port: 2883
    rpc_port: 2882
    home_path: /data/obdata
    zone: zone1
  host6:
    mysql_port: 2883
    rpc_port: 2882
    home_path: /data1/obdata
    zone: zone1
  host7:
    mysql_port: 2883
    rpc_port: 2882
    home_path: /data1/obdata
    zone: zone2
  host8:
    mysql_port: 2883
    rpc_port: 2882
    home_path: /data1/obdata
    zone: zone2
  host9:
    mysql_port: 2883
    rpc_port: 2882
    home_path: /data1/obdata
    zone: zone2

obproxy:
  servers:
    - 10.24.14.215
  global:
    listen_port: 2883
    home_path: /data/obproxy
    rs_list: 10.24.14.8:2883<span class="p">;</span>10.24.14.136:2883<span class="p">;</span>10.24.14.75:2883<span class="p">;</span>10.24.14.178:2883<span class="p">;</span>10.24.14.60:2883<span class="p">;</span>10.24.14.120:2883<span class="p">;</span>10.24.14.126:2883<span class="p">;</span>10.24.14.171:2883<span class="p">;</span>10.24.14.181:2883
    enable_cluster_checkout: <span class="nb">false
    </span>cluster_name: ob209
</code></pre></div></div>
<h3 id="3-benchmarksql修改内容和配置文件">3. BenchmarkSQL修改内容和配置文件</h3>

<h4 id="31-benchmarksql下载链接">3.1 BenchmarkSQL下载链接</h4>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>  <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">sourceforge</span><span class="p">.</span><span class="n">net</span><span class="o">/</span><span class="n">projects</span><span class="o">/</span><span class="n">benchmarksql</span><span class="o">/</span><span class="n">files</span><span class="o">/</span><span class="n">latest</span><span class="o">/</span><span class="n">download</span>
</code></pre></div></div>
<h4 id="32-benchmarksql修改内容">3.2 BenchmarkSQL修改内容</h4>

<ul>
  <li>
    <p>修改benchmark-5.0/src/client/jTPCC.java 文件，增加分布式事务比例的参数化</p>

    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">private</span> <span class="kt">double</span> <span class="n">tpmC</span><span class="o">;</span>
 <span class="kd">private</span> <span class="n">jTPCCRandom</span> <span class="n">rnd</span><span class="o">;</span>
 <span class="kd">private</span> <span class="nc">OSCollector</span> <span class="n">osCollector</span> <span class="o">=</span> <span class="kc">null</span><span class="o">;</span>
 <span class="c1">//声明neworder事务中的分布式事务比例</span>
 <span class="kd">private</span> <span class="kd">static</span> <span class="kt">double</span> <span class="n">newOrderDistributedRate</span><span class="o">;</span>
</code></pre></div>    </div>

    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">String</span>  <span class="n">iWarehouses</span>  <span class="o">=</span> <span class="n">getProp</span><span class="o">(</span><span class="n">ini</span><span class="o">,</span><span class="s">"warehouses"</span><span class="o">);</span>
 <span class="nc">String</span>  <span class="n">iTerminals</span>  <span class="o">=</span> <span class="n">getProp</span><span class="o">(</span><span class="n">ini</span><span class="o">,</span><span class="s">"terminals"</span><span class="o">);</span>
 <span class="c1">//获取配置文件参数</span>
 <span class="n">newOrderDistributedRate</span> <span class="o">=</span> <span class="nc">Double</span><span class="o">.</span><span class="na">parseDouble</span><span class="o">(</span><span class="n">getProp</span><span class="o">(</span><span class="n">ini</span><span class="o">,</span> <span class="s">"newOrderDistributedRate"</span><span class="o">));</span>
     
 <span class="nc">String</span>  <span class="n">iRunTxnsPerTerminal</span> <span class="o">=</span> <span class="n">ini</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"runTxnsPerTerminal"</span><span class="o">);</span>
 <span class="nc">String</span> <span class="n">iRunMins</span>  <span class="o">=</span>  <span class="n">ini</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"runMins"</span><span class="o">);</span>
</code></pre></div>    </div>

    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">private</span> <span class="nc">String</span> <span class="nf">getFileNameSuffix</span><span class="o">()</span>
 <span class="o">{</span>
 	<span class="nc">SimpleDateFormat</span> <span class="n">dateFormat</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">SimpleDateFormat</span><span class="o">(</span><span class="s">"yyyyMMddHHmmss"</span><span class="o">);</span>
 	<span class="k">return</span> <span class="n">dateFormat</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="k">new</span> <span class="n">java</span><span class="o">.</span><span class="na">util</span><span class="o">.</span><span class="na">Date</span><span class="o">());</span>
     <span class="o">}</span>
     
 <span class="c1">//创建外部类的访问接口</span>
 <span class="kd">public</span> <span class="kd">static</span> <span class="kt">double</span> <span class="nf">getNewOrderDistributedRate</span><span class="o">()</span> <span class="o">{</span>
 		<span class="k">return</span> <span class="n">newOrderDistributedRate</span><span class="o">;</span>
 	<span class="o">}</span>
</code></pre></div>    </div>
  </li>
</ul>

<ol>
  <li>
    <p>修改benchmark-5.0/src/client/jTPCCTData.java 文件，修改NewOrder的分布式事务比例</p>

    <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="o">(</span><span class="n">i</span> <span class="o">&lt;</span> <span class="n">o_ol_cnt</span><span class="o">)</span>                    <span class="c1">// 2.4.1.5</span>
	<span class="o">{</span>
	  <span class="n">newOrder</span><span class="o">.</span><span class="na">ol_i_id</span><span class="o">[</span><span class="n">i</span><span class="o">]</span>         <span class="o">=</span> <span class="n">rnd</span><span class="o">.</span><span class="na">getItemID</span><span class="o">();</span>
		<span class="c1">//更改分布式事务比例</span>
	   <span class="k">if</span> <span class="o">(</span><span class="n">rnd</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">100</span><span class="o">)</span> <span class="o">&lt;=</span> <span class="mi">100</span><span class="o">-</span><span class="n">jTPCC</span><span class="o">.</span><span class="na">getNewOrderDistributedRate</span><span class="o">()*</span><span class="mi">100</span><span class="o">)</span>
		<span class="n">newOrder</span><span class="o">.</span><span class="na">ol_supply_w_id</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">terminalWarehouse</span><span class="o">;</span>
	    <span class="k">else</span>
		<span class="n">newOrder</span><span class="o">.</span><span class="na">ol_supply_w_id</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">rnd</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="n">numWarehouses</span><span class="o">);</span>
	    <span class="n">newOrder</span><span class="o">.</span><span class="na">ol_quantity</span><span class="o">[</span><span class="n">i</span><span class="o">]</span> <span class="o">=</span> <span class="n">rnd</span><span class="o">.</span><span class="na">nextInt</span><span class="o">(</span><span class="mi">1</span><span class="o">,</span> <span class="mi">10</span><span class="o">);</span>
     
</code></pre></div>    </div>
  </li>
</ol>

<ul>
  <li>
    <p>修改benchmark-5.0/run/probs.ob文件</p>

    <div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nv">db</span><span class="o">=</span>oracle
 <span class="nv">driver</span><span class="o">=</span>com.alipay.oceanbase.obproxy.mysql.jdbc.Driver
 <span class="nv">conn</span><span class="o">=</span>jdbc:oceanbase://10.24.14.188:2883/tpcc_100?useUnicode<span class="o">=</span><span class="nb">true</span>&amp;characterEncoding<span class="o">=</span>utf-8
 <span class="nv">user</span><span class="o">=</span>tpcc@test
 <span class="nv">password</span><span class="o">=</span>
     
 <span class="nv">warehouses</span><span class="o">=</span>100
 <span class="nv">loadWorkers</span><span class="o">=</span>30
     
 <span class="nv">terminals</span><span class="o">=</span>100
 //To run specified transactions per terminal- runMins must equal zero
 <span class="nv">runTxnsPerTerminal</span><span class="o">=</span>0
 //To run <span class="k">for </span>specified minutes- runTxnsPerTerminal must equal zero
 <span class="nv">runMins</span><span class="o">=</span>5
 //Number of total transactions per minute
 <span class="nv">limitTxnsPerMin</span><span class="o">=</span>0
     
 //将生成的数据放在该目录
 <span class="nv">fileLocation</span><span class="o">=</span>/data/ob/tpcc_100/
     
 //Distributed Transaction Ratio For NewOrder Transaction
 <span class="nv">newOrderDistributedRate</span><span class="o">=</span>0.01
     
 //Set to <span class="nb">true </span>to run <span class="k">in </span>4.x compatible mode. Set to <span class="nb">false </span>to use the
 //entire configured database evenly.
 <span class="nv">terminalWarehouseFixed</span><span class="o">=</span><span class="nb">false</span>
     
 //The following five values must add up to 100
 <span class="nv">newOrderWeight</span><span class="o">=</span>100
 <span class="nv">paymentWeight</span><span class="o">=</span>0
 <span class="nv">orderStatusWeight</span><span class="o">=</span>0
 <span class="nv">deliveryWeight</span><span class="o">=</span>0
 <span class="nv">stockLevelWeight</span><span class="o">=</span>0
     
 // Directory name to create <span class="k">for </span>collecting detailed result data.
 // Comment this out to suppress.
 <span class="nv">resultDirectory</span><span class="o">=</span>my_result_%tY-%tm-%td_%tH%tM%tS
 <span class="nv">osCollectorScript</span><span class="o">=</span>./misc/os_collector_linux.py
 <span class="nv">osCollectorInterval</span><span class="o">=</span>1
     
</code></pre></div>    </div>
  </li>
  <li>
    <p>修改benchmark-5.0/run/sql.oceanbase/tableCreates.sql 文件</p>

    <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code>   <span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_config</span> <span class="p">(</span>
     <span class="n">cfg_name</span>    <span class="nb">varchar</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="k">primary</span> <span class="k">key</span><span class="p">,</span>
     <span class="n">cfg_value</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">50</span><span class="p">)</span>
   <span class="p">);</span>
     
   <span class="k">create</span> <span class="n">tablegroup</span> <span class="n">tpcc_group</span> <span class="n">binding</span> <span class="k">true</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
     
   <span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_warehouse</span> <span class="p">(</span>
     <span class="n">w_id</span>        <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">w_ytd</span>       <span class="nb">decimal</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">w_tax</span>       <span class="nb">decimal</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">),</span>
     <span class="n">w_name</span>      <span class="nb">varchar</span><span class="p">(</span><span class="mi">10</span><span class="p">),</span>
     <span class="n">w_street_1</span>  <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
     <span class="n">w_street_2</span>  <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
     <span class="n">w_city</span>      <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
     <span class="n">w_state</span>     <span class="nb">char</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">w_zip</span>       <span class="nb">char</span><span class="p">(</span><span class="mi">9</span><span class="p">),</span>
     <span class="k">primary</span> <span class="k">key</span><span class="p">(</span><span class="n">w_id</span><span class="p">)</span>
   <span class="p">)</span><span class="n">tablegroup</span><span class="o">=</span><span class="s1">'tpcc_group'</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
     
   <span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_district</span> <span class="p">(</span>
     <span class="n">d_w_id</span>       <span class="nb">integer</span>       <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">d_id</span>         <span class="nb">integer</span>       <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">d_ytd</span>        <span class="nb">decimal</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">d_tax</span>        <span class="nb">decimal</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">),</span>
     <span class="n">d_next_o_id</span>  <span class="nb">integer</span><span class="p">,</span>
     <span class="n">d_name</span>       <span class="nb">varchar</span><span class="p">(</span><span class="mi">10</span><span class="p">),</span>
     <span class="n">d_street_1</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
     <span class="n">d_street_2</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
     <span class="n">d_city</span>       <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
     <span class="n">d_state</span>      <span class="nb">char</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">d_zip</span>        <span class="nb">char</span><span class="p">(</span><span class="mi">9</span><span class="p">),</span>
     <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">d_w_id</span><span class="p">,</span> <span class="n">d_id</span><span class="p">)</span>
   <span class="p">)</span><span class="n">tablegroup</span><span class="o">=</span><span class="s1">'tpcc_group'</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">d_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
     
   <span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_customer</span> <span class="p">(</span>
     <span class="n">c_w_id</span>         <span class="nb">integer</span>        <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">c_d_id</span>         <span class="nb">integer</span>        <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">c_id</span>           <span class="nb">integer</span>        <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">c_discount</span>     <span class="nb">decimal</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">),</span>
     <span class="n">c_credit</span>       <span class="nb">char</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">c_last</span>         <span class="nb">varchar</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
     <span class="n">c_first</span>        <span class="nb">varchar</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
     <span class="n">c_credit_lim</span>   <span class="nb">decimal</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">c_balance</span>      <span class="nb">decimal</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">c_ytd_payment</span>  <span class="nb">decimal</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">c_payment_cnt</span>  <span class="nb">integer</span><span class="p">,</span>
     <span class="n">c_delivery_cnt</span> <span class="nb">integer</span><span class="p">,</span>
     <span class="n">c_street_1</span>     <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
     <span class="n">c_street_2</span>     <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
     <span class="n">c_city</span>         <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
     <span class="n">c_state</span>        <span class="nb">char</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">c_zip</span>          <span class="nb">char</span><span class="p">(</span><span class="mi">9</span><span class="p">),</span>
     <span class="n">c_phone</span>        <span class="nb">char</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
     <span class="n">c_since</span>        <span class="nb">timestamp</span><span class="p">,</span>
     <span class="n">c_middle</span>       <span class="nb">char</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">c_data</span>         <span class="nb">varchar</span><span class="p">(</span><span class="mi">500</span><span class="p">),</span>
     <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">c_w_id</span><span class="p">,</span> <span class="n">c_d_id</span><span class="p">,</span> <span class="n">c_id</span><span class="p">)</span>
   <span class="p">)</span><span class="n">tablegroup</span><span class="o">=</span><span class="s1">'tpcc_group'</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">c_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
     
     
   <span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_history</span> <span class="p">(</span>
     <span class="n">hist_id</span>  <span class="nb">integer</span><span class="p">,</span>
     <span class="n">h_c_id</span>   <span class="nb">integer</span><span class="p">,</span>
     <span class="n">h_c_d_id</span> <span class="nb">integer</span><span class="p">,</span>
     <span class="n">h_c_w_id</span> <span class="nb">integer</span><span class="p">,</span>
     <span class="n">h_d_id</span>   <span class="nb">integer</span><span class="p">,</span>
     <span class="n">h_w_id</span>   <span class="nb">integer</span><span class="p">,</span>
     <span class="n">h_date</span>   <span class="nb">timestamp</span><span class="p">,</span>
     <span class="n">h_amount</span> <span class="nb">decimal</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">h_data</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">24</span><span class="p">)</span>
   <span class="p">)</span><span class="n">tablegroup</span><span class="o">=</span><span class="s1">'tpcc_group'</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">h_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
     
   <span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_new_order</span> <span class="p">(</span>
     <span class="n">no_w_id</span>  <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span> <span class="p">,</span>
     <span class="n">no_d_id</span>  <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">no_o_id</span>  <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">no_w_id</span><span class="p">,</span> <span class="n">no_d_id</span><span class="p">,</span> <span class="n">no_o_id</span><span class="p">)</span>
   <span class="p">)</span><span class="n">tablegroup</span><span class="o">=</span><span class="s1">'tpcc_group'</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">no_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
     
   <span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_oorder</span> <span class="p">(</span>
     <span class="n">o_w_id</span>       <span class="nb">integer</span>      <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">o_d_id</span>       <span class="nb">integer</span>      <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">o_id</span>         <span class="nb">integer</span>      <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">o_c_id</span>       <span class="nb">integer</span><span class="p">,</span>
     <span class="n">o_carrier_id</span> <span class="nb">integer</span><span class="p">,</span>
     <span class="n">o_ol_cnt</span>     <span class="nb">integer</span><span class="p">,</span>
     <span class="n">o_all_local</span>  <span class="nb">integer</span><span class="p">,</span>
     <span class="n">o_entry_d</span>    <span class="nb">timestamp</span><span class="p">,</span>
     <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">o_w_id</span><span class="p">,</span> <span class="n">o_d_id</span><span class="p">,</span> <span class="n">o_id</span><span class="p">)</span>
   <span class="p">)</span><span class="n">tablegroup</span><span class="o">=</span><span class="s1">'tpcc_group'</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">o_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
     
   <span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_order_line</span> <span class="p">(</span>
     <span class="n">ol_w_id</span>         <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">ol_d_id</span>         <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">ol_o_id</span>         <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">ol_number</span>       <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">ol_i_id</span>         <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">ol_delivery_d</span>   <span class="nb">timestamp</span><span class="p">,</span>
     <span class="n">ol_amount</span>       <span class="nb">decimal</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">ol_supply_w_id</span>  <span class="nb">integer</span><span class="p">,</span>
     <span class="n">ol_quantity</span>     <span class="nb">integer</span><span class="p">,</span>
     <span class="n">ol_dist_info</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">ol_w_id</span><span class="p">,</span> <span class="n">ol_d_id</span><span class="p">,</span> <span class="n">ol_o_id</span><span class="p">,</span> <span class="n">ol_number</span><span class="p">)</span>
   <span class="p">)</span><span class="n">tablegroup</span><span class="o">=</span><span class="s1">'tpcc_group'</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">ol_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
     
   <span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_item</span> <span class="p">(</span>
     <span class="n">i_id</span>     <span class="nb">integer</span>      <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">i_name</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="n">i_price</span>  <span class="nb">decimal</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
     <span class="n">i_data</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">50</span><span class="p">),</span>
     <span class="n">i_im_id</span>  <span class="nb">integer</span><span class="p">,</span>
     <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">i_id</span><span class="p">)</span>
   <span class="p">)</span> <span class="n">locality</span><span class="o">=</span><span class="s1">'F,R{all_server}@zone0, F,R{all_server}@zone1, F,R{all_server}@zone2'</span>  <span class="n">duplicate_scope</span><span class="o">=</span><span class="s1">'cluster'</span><span class="p">;</span>
   <span class="c1">-- );</span>
     
   <span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_stock</span> <span class="p">(</span>
     <span class="n">s_w_id</span>       <span class="nb">integer</span>       <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">s_i_id</span>       <span class="nb">integer</span>       <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
     <span class="n">s_quantity</span>   <span class="nb">integer</span><span class="p">,</span>
     <span class="n">s_ytd</span>        <span class="nb">integer</span><span class="p">,</span>
     <span class="n">s_order_cnt</span>  <span class="nb">integer</span><span class="p">,</span>
     <span class="n">s_remote_cnt</span> <span class="nb">integer</span><span class="p">,</span>
     <span class="n">s_data</span>       <span class="nb">varchar</span><span class="p">(</span><span class="mi">50</span><span class="p">),</span>
     <span class="n">s_dist_01</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="n">s_dist_02</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="n">s_dist_03</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="n">s_dist_04</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="n">s_dist_05</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="n">s_dist_06</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="n">s_dist_07</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="n">s_dist_08</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="n">s_dist_09</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="n">s_dist_10</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
     <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">s_w_id</span><span class="p">,</span> <span class="n">s_i_id</span><span class="p">)</span>
   <span class="p">)</span><span class="n">tablegroup</span><span class="o">=</span><span class="s1">'tpcc_group'</span> <span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">s_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
     
</code></pre></div>    </div>
  </li>
</ul>

<h2 id="五实验过程">五、实验过程</h2>

<h3 id="1-创建schema并在本地生成数据文件">1. 创建schema并在本地生成数据文件</h3>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>benchmark-5.0/run
./runDatabaseBuild.sh props.ob
</code></pre></div></div>
<h3 id="2-导入数据">2. 导入数据</h3>

<ul>
  <li>这里采用的是外部load infile方式导入数据库，因为我们导入的数据量比较大，这种方式更加快速。首先，需要将生成的数据文件如customer.csv等移动到rootserver所在的机器上，我们这边放在10.24.14.245(rootserver)上的/data/ob/tpcc_100/目录。此外，schema的创建方式</li>
</ul>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>obclient <span class="nt">-h10</span>.24.14.245 <span class="nt">-P2883</span> <span class="nt">-uroot</span>@test <span class="nt">-c</span>  <span class="nt">-D</span> tpcc_100 <span class="nt">-e</span> <span class="s2">"load data /*+ parallel(80) */ infile '/data/ob/tpcc_100/warehouse.csv' into table bmsql_warehouse fields terminated by ',';"</span>  
obclient <span class="nt">-h10</span>.24.14.245 <span class="nt">-P2883</span> <span class="nt">-uroot</span>@test <span class="nt">-c</span>  <span class="nt">-D</span> tpcc_100 <span class="nt">-e</span> <span class="s2">"load data /*+ parallel(80) */ infile '/data/ob/tpcc_100/district.csv' into table bmsql_district fields terminated by ',';"</span>  
obclient <span class="nt">-h10</span>.24.14.245 <span class="nt">-P2883</span> <span class="nt">-uroot</span>@test <span class="nt">-c</span>  <span class="nt">-D</span> tpcc_100 <span class="nt">-e</span> <span class="s2">"load data /*+ parallel(80) */ infile '/data/ob/tpcc_100/config.csv' into table bmsql_config fields terminated by ',';"</span> 
obclient <span class="nt">-h10</span>.24.14.245 <span class="nt">-P2883</span> <span class="nt">-uroot</span>@test <span class="nt">-c</span>  <span class="nt">-D</span> tpcc_100 <span class="nt">-e</span> <span class="s2">"load data /*+ parallel(80) */ infile '/data/ob/tpcc_100/item.csv' into table bmsql_item fields terminated by ',';"</span>  
obclient <span class="nt">-h10</span>.24.14.245 <span class="nt">-P2883</span> <span class="nt">-uroot</span>@test <span class="nt">-c</span>  <span class="nt">-D</span> tpcc_100 <span class="nt">-e</span> <span class="s2">"load data /*+ parallel(80) */ infile '/data/ob/tpcc_100/order.csv' into table bmsql_oorder fields terminated by ',';"</span>  
obclient <span class="nt">-h10</span>.24.14.245 <span class="nt">-P2883</span> <span class="nt">-uroot</span>@test <span class="nt">-c</span>  <span class="nt">-D</span> tpcc_100 <span class="nt">-e</span> <span class="s2">"load data /*+ parallel(80) */ infile '/data/ob/tpcc_100/stock.csv' into table bmsql_stock fields terminated by ',';"</span> 
obclient <span class="nt">-h10</span>.24.14.245 <span class="nt">-P2883</span> <span class="nt">-uroot</span>@test <span class="nt">-c</span>  <span class="nt">-D</span> tpcc_100 <span class="nt">-e</span> <span class="s2">"load data /*+ parallel(80) */ infile '/data/ob/tpcc_100/cust-hist.csv' into table bmsql_history fields terminated by ',';"</span> 
obclient <span class="nt">-h10</span>.24.14.245 <span class="nt">-P2883</span> <span class="nt">-uroot</span>@test <span class="nt">-c</span>  <span class="nt">-D</span> tpcc_100 <span class="nt">-e</span> <span class="s2">"load data /*+ parallel(80) */ infile '/data/ob/tpcc_100/new-order.csv' into table bmsql_new_order fields terminated by ',';"</span> 
obclient <span class="nt">-h10</span>.24.14.245 <span class="nt">-P2883</span> <span class="nt">-uroot</span>@test <span class="nt">-c</span>  <span class="nt">-D</span> tpcc_100 <span class="nt">-e</span> <span class="s2">"load data /*+ parallel(80) */ infile '/data/ob/tpcc_100/order-line.csv' into table bmsql_order_line fields terminated by ',';"</span> 
obclient <span class="nt">-h10</span>.24.14.245 <span class="nt">-P2883</span> <span class="nt">-uroot</span>@test <span class="nt">-c</span>  <span class="nt">-D</span> tpcc_100 <span class="nt">-e</span> <span class="s2">"load data /*+ parallel(80) */ infile '/data/ob/tpcc_100/customer.csv' into table bmsql_customer fields terminated by ',';"</span> 
</code></pre></div></div>
<h3 id="3-分布式事务比例实验的运行负载">3. 分布式事务比例实验的运行负载</h3>

<p>实验中分别将newOrderDistributedRate设为0.01，0.1，0.2，0.4，0.6，0.8，1 分别代表不同的NewOrder事务的分布式事务比例</p>

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">.</span><span class="o">/</span><span class="n">runBenchmark</span><span class="p">.</span><span class="n">sh</span> <span class="n">probs</span><span class="p">.</span><span class="n">ob</span>
</code></pre></div></div>

<h2 id="六实验结果展示与分析">六、实验结果展示与分析</h2>

<ul>
  <li>我们将上方运行的分布式事务的实验结果列在下方，横坐标是不同的分布式事务比例，纵坐标是每分钟的吞吐数。我们可以观察到，分布式事务比例从0.01一直到1，吞吐从188639下降到64699，下降了65.7%。实验结果符合我们的预期，分布式事务对分布式数据库的性能会造成比较大的影
响。</li>
  <li>为了尽量减少分布式事务比例的影响，OceanBase其实提出了tablegroup机制，它能让经常被一起访问的记录尽可能地放在用一个partition中。比如，一个歌手名下有多张唱片，那么如果一个事务更新歌手及其名下的唱片，那么将该歌手和其唱片放在一起，就可以减少分布式事务。OceanBase中的tablegroup机制就是基于这样的想法创建出来的，为了验证tablegroup机制提升性能的效果，我们将去掉原始的tablegroup机制，来看性能下降了多少。</li>
</ul>

<p><img src="/auto-image/picrepo/b4c987fe-f308-492b-aa36-399ecede06b7.png" alt="imgalt" /></p>

<h2 id="七进一步实验">七、进一步实验</h2>

<ul>
  <li>
    <p>为了完成这个实验，需要修改benchmark-5.0/run/sql.oceanbase/tableCreates.sql 文件。</p>

    <div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_config</span> <span class="p">(</span>
  <span class="n">cfg_name</span>    <span class="nb">varchar</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="k">primary</span> <span class="k">key</span><span class="p">,</span>
  <span class="n">cfg_value</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">50</span><span class="p">)</span>
<span class="p">);</span>
  
  
<span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_warehouse</span> <span class="p">(</span>
  <span class="n">w_id</span>        <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">w_ytd</span>       <span class="nb">decimal</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">w_tax</span>       <span class="nb">decimal</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">),</span>
  <span class="n">w_name</span>      <span class="nb">varchar</span><span class="p">(</span><span class="mi">10</span><span class="p">),</span>
  <span class="n">w_street_1</span>  <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
  <span class="n">w_street_2</span>  <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
  <span class="n">w_city</span>      <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
  <span class="n">w_state</span>     <span class="nb">char</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">w_zip</span>       <span class="nb">char</span><span class="p">(</span><span class="mi">9</span><span class="p">),</span>
  <span class="k">primary</span> <span class="k">key</span><span class="p">(</span><span class="n">w_id</span><span class="p">)</span>
<span class="p">)</span><span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
  
<span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_district</span> <span class="p">(</span>
  <span class="n">d_w_id</span>       <span class="nb">integer</span>       <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">d_id</span>         <span class="nb">integer</span>       <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">d_ytd</span>        <span class="nb">decimal</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">d_tax</span>        <span class="nb">decimal</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">),</span>
  <span class="n">d_next_o_id</span>  <span class="nb">integer</span><span class="p">,</span>
  <span class="n">d_name</span>       <span class="nb">varchar</span><span class="p">(</span><span class="mi">10</span><span class="p">),</span>
  <span class="n">d_street_1</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
  <span class="n">d_street_2</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
  <span class="n">d_city</span>       <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
  <span class="n">d_state</span>      <span class="nb">char</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">d_zip</span>        <span class="nb">char</span><span class="p">(</span><span class="mi">9</span><span class="p">),</span>
  <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">d_w_id</span><span class="p">,</span> <span class="n">d_id</span><span class="p">)</span>
<span class="p">)</span><span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">d_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
  
<span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_customer</span> <span class="p">(</span>
  <span class="n">c_w_id</span>         <span class="nb">integer</span>        <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">c_d_id</span>         <span class="nb">integer</span>        <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">c_id</span>           <span class="nb">integer</span>        <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">c_discount</span>     <span class="nb">decimal</span><span class="p">(</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">),</span>
  <span class="n">c_credit</span>       <span class="nb">char</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">c_last</span>         <span class="nb">varchar</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
  <span class="n">c_first</span>        <span class="nb">varchar</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
  <span class="n">c_credit_lim</span>   <span class="nb">decimal</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">c_balance</span>      <span class="nb">decimal</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">c_ytd_payment</span>  <span class="nb">decimal</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">c_payment_cnt</span>  <span class="nb">integer</span><span class="p">,</span>
  <span class="n">c_delivery_cnt</span> <span class="nb">integer</span><span class="p">,</span>
  <span class="n">c_street_1</span>     <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
  <span class="n">c_street_2</span>     <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
  <span class="n">c_city</span>         <span class="nb">varchar</span><span class="p">(</span><span class="mi">20</span><span class="p">),</span>
  <span class="n">c_state</span>        <span class="nb">char</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">c_zip</span>          <span class="nb">char</span><span class="p">(</span><span class="mi">9</span><span class="p">),</span>
  <span class="n">c_phone</span>        <span class="nb">char</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span>
  <span class="n">c_since</span>        <span class="nb">timestamp</span><span class="p">,</span>
  <span class="n">c_middle</span>       <span class="nb">char</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">c_data</span>         <span class="nb">varchar</span><span class="p">(</span><span class="mi">500</span><span class="p">),</span>
  <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">c_w_id</span><span class="p">,</span> <span class="n">c_d_id</span><span class="p">,</span> <span class="n">c_id</span><span class="p">)</span>
<span class="p">)</span><span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">c_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
  
  
<span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_history</span> <span class="p">(</span>
  <span class="n">hist_id</span>  <span class="nb">integer</span><span class="p">,</span>
  <span class="n">h_c_id</span>   <span class="nb">integer</span><span class="p">,</span>
  <span class="n">h_c_d_id</span> <span class="nb">integer</span><span class="p">,</span>
  <span class="n">h_c_w_id</span> <span class="nb">integer</span><span class="p">,</span>
  <span class="n">h_d_id</span>   <span class="nb">integer</span><span class="p">,</span>
  <span class="n">h_w_id</span>   <span class="nb">integer</span><span class="p">,</span>
  <span class="n">h_date</span>   <span class="nb">timestamp</span><span class="p">,</span>
  <span class="n">h_amount</span> <span class="nb">decimal</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">h_data</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">24</span><span class="p">)</span>
<span class="p">)</span><span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">h_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
  
<span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_new_order</span> <span class="p">(</span>
  <span class="n">no_w_id</span>  <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span> <span class="p">,</span>
  <span class="n">no_d_id</span>  <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">no_o_id</span>  <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">no_w_id</span><span class="p">,</span> <span class="n">no_d_id</span><span class="p">,</span> <span class="n">no_o_id</span><span class="p">)</span>
<span class="p">)</span><span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">no_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
  
<span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_oorder</span> <span class="p">(</span>
  <span class="n">o_w_id</span>       <span class="nb">integer</span>      <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">o_d_id</span>       <span class="nb">integer</span>      <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">o_id</span>         <span class="nb">integer</span>      <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">o_c_id</span>       <span class="nb">integer</span><span class="p">,</span>
  <span class="n">o_carrier_id</span> <span class="nb">integer</span><span class="p">,</span>
  <span class="n">o_ol_cnt</span>     <span class="nb">integer</span><span class="p">,</span>
  <span class="n">o_all_local</span>  <span class="nb">integer</span><span class="p">,</span>
  <span class="n">o_entry_d</span>    <span class="nb">timestamp</span><span class="p">,</span>
  <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">o_w_id</span><span class="p">,</span> <span class="n">o_d_id</span><span class="p">,</span> <span class="n">o_id</span><span class="p">)</span>
<span class="p">)</span><span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">o_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
  
<span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_order_line</span> <span class="p">(</span>
  <span class="n">ol_w_id</span>         <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">ol_d_id</span>         <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">ol_o_id</span>         <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">ol_number</span>       <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">ol_i_id</span>         <span class="nb">integer</span>   <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">ol_delivery_d</span>   <span class="nb">timestamp</span><span class="p">,</span>
  <span class="n">ol_amount</span>       <span class="nb">decimal</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">ol_supply_w_id</span>  <span class="nb">integer</span><span class="p">,</span>
  <span class="n">ol_quantity</span>     <span class="nb">integer</span><span class="p">,</span>
  <span class="n">ol_dist_info</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">ol_w_id</span><span class="p">,</span> <span class="n">ol_d_id</span><span class="p">,</span> <span class="n">ol_o_id</span><span class="p">,</span> <span class="n">ol_number</span><span class="p">)</span>
<span class="p">)</span><span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">ol_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
  
<span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_item</span> <span class="p">(</span>
  <span class="n">i_id</span>     <span class="nb">integer</span>      <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">i_name</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="n">i_price</span>  <span class="nb">decimal</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span><span class="mi">2</span><span class="p">),</span>
  <span class="n">i_data</span>   <span class="nb">varchar</span><span class="p">(</span><span class="mi">50</span><span class="p">),</span>
  <span class="n">i_im_id</span>  <span class="nb">integer</span><span class="p">,</span>
  <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">i_id</span><span class="p">)</span>
<span class="p">)</span> <span class="n">locality</span><span class="o">=</span><span class="s1">'F,R{all_server}@zone0, F,R{all_server}@zone1, F,R{all_server}@zone2'</span>  <span class="n">duplicate_scope</span><span class="o">=</span><span class="s1">'cluster'</span><span class="p">;</span>
<span class="c1">-- );</span>
  
<span class="k">create</span> <span class="k">table</span> <span class="n">bmsql_stock</span> <span class="p">(</span>
  <span class="n">s_w_id</span>       <span class="nb">integer</span>       <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">s_i_id</span>       <span class="nb">integer</span>       <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
  <span class="n">s_quantity</span>   <span class="nb">integer</span><span class="p">,</span>
  <span class="n">s_ytd</span>        <span class="nb">integer</span><span class="p">,</span>
  <span class="n">s_order_cnt</span>  <span class="nb">integer</span><span class="p">,</span>
  <span class="n">s_remote_cnt</span> <span class="nb">integer</span><span class="p">,</span>
  <span class="n">s_data</span>       <span class="nb">varchar</span><span class="p">(</span><span class="mi">50</span><span class="p">),</span>
  <span class="n">s_dist_01</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="n">s_dist_02</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="n">s_dist_03</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="n">s_dist_04</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="n">s_dist_05</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="n">s_dist_06</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="n">s_dist_07</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="n">s_dist_08</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="n">s_dist_09</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="n">s_dist_10</span>    <span class="nb">char</span><span class="p">(</span><span class="mi">24</span><span class="p">),</span>
  <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="n">s_w_id</span><span class="p">,</span> <span class="n">s_i_id</span><span class="p">)</span>
<span class="p">)</span><span class="k">partition</span> <span class="k">by</span> <span class="n">hash</span><span class="p">(</span><span class="n">s_w_id</span><span class="p">)</span> <span class="n">partitions</span> <span class="mi">128</span><span class="p">;</span>
  
</code></pre></div>    </div>
  </li>
  <li>
    <p>为了简化实验，这边将props.ob中的变量newOrderDistributedRate=0.01</p>
  </li>
  <li>
    <p>加载数据&amp;运行负载</p>
  </li>
  <li>
    <p>实验展示与分析</p>

    <ul>
      <li>我们将运行的实验结果列在下方，横坐标是两种不同的schema方法，纵坐标表是NewOrder事务的每分钟的吞吐。我们可以看到不建立tablegroup的时候，吞吐为61977，而建立tablegroup之后，吞吐提高了3倍左右，为188639。可见，OceanBase提供的tablegroup机制能大大提升性能。</li>
    </ul>
  </li>
</ul>

<p><img src="/auto-image/picrepo/475c03c0-3083-4eeb-a4d2-942b246ae6f5.png" alt="imgalt" /></p>

<h2 id="八总结">八、总结</h2>

<ul>
  <li>经过上方的实验，我们可以将OceanBase在分布式事务的支持能力上的实验表现总结为两点：
    <ul>
      <li>在良好分区下，OceanBase能提供非常高的吞吐，表现十分优异。随着分布式事务比例的增长，OceanBase的性能会呈现下降的趋势，这也是大多数分布式事务型数据库所面临的难题和挑战<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</li>
      <li>OceanBase所提供的tablegroup技术能够迎合业务的需求，就数据和业务进行良好的绑定。在我们的实验中，加入tablegroup机制让整个吞吐上升了整整3倍。</li>
    </ul>
  </li>
</ul>

<h2 id="参考文献">参考文献</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p><span name="ref1">Pavlo A, Curino C, Zdonik S. Skew-aware automatic database partitioning in shared-nothing, parallel OLTP systems[C]//Proceedings of the 2012 ACM SIGMOD International Conference on Management of Data. 2012: 61-72.</span> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2">
      <p>L. Qu, Q. Wang, T. Chen, K. Li, R. Zhang, X. Zhou, Q. Xu, Z. Yang, C. Yang, W. Qian, and A. Zhou, “Are current benchmarks adequate to evaluate distributed transactional databases?” BenchCouncil Transactions on Benchmarks, Standards and Evaluations, vol. 2, no. 1,p. 100031, 2022. [Online]. Available: https://www.sciencedirect.com/science/article/pii/S2772485922000187 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>瞿璐祎</name></author><category term="join" /><summary type="html"><![CDATA[一、目的]]></summary></entry><entry><title type="html">OceanBase连接顺序选择的优劣评估</title><link href="https://dbhammer.github.io/2022/03/30/OceanBase%E8%BF%9E%E6%8E%A5%E9%A1%BA%E5%BA%8F%E9%80%89%E6%8B%A9%E7%9A%84%E4%BC%98%E5%8A%A3%E8%AF%84%E4%BC%B0.html" rel="alternate" type="text/html" title="OceanBase连接顺序选择的优劣评估" /><published>2022-03-30T00:00:00+00:00</published><updated>2026-03-31T13:16:28+00:00</updated><id>https://dbhammer.github.io/2022/03/30/OceanBase%E8%BF%9E%E6%8E%A5%E9%A1%BA%E5%BA%8F%E9%80%89%E6%8B%A9%E7%9A%84%E4%BC%98%E5%8A%A3%E8%AF%84%E4%BC%B0</id><content type="html" xml:base="https://dbhammer.github.io/2022/03/30/OceanBase%E8%BF%9E%E6%8E%A5%E9%A1%BA%E5%BA%8F%E9%80%89%E6%8B%A9%E7%9A%84%E4%BC%98%E5%8A%A3%E8%AF%84%E4%BC%B0.html"><![CDATA[<h2 id="一目的">一、目的</h2>

<p>在OLAP场景中，多表连接是十分常见的，查询的执行效率跟它涉及的表的连接顺序息息相关。以A、B、C三张表为例，有一条查询：SELECT * FROM A, B, C WHERE …，那么这三张表的连接顺序可以是<code class="language-plaintext highlighter-rouge">(A⋈B)⋈C</code>、<code class="language-plaintext highlighter-rouge">(A⋈B)⋈C</code>、<code class="language-plaintext highlighter-rouge">(A⋈C)⋈B</code>等共6种连接顺序，我们将全部连接顺序称为搜索空间。不同的连接顺序是语义等价的，即能获得相同的结果集，但是对于查询效率有着非常大的影响。从搜索空间中选出性能最优的连接顺序是一个关键的DBMS优化问题，但是随着连接表数量的增加，搜索空间的大小呈指数级增长，这使得连接顺序选择成为一个NP-hard问题。本文主要评测OceanBase连接顺序选择策略的优劣，以分析OceanBase对多表连接查询的处理能力以及优化空间。</p>

<h2 id="二oceanbase查询计划分析">二、OceanBase查询计划分析</h2>

<p>为了得到OceanBase执行某查询时选择的连接顺序，我们需要分析该查询的执行计划，具体方法如下。</p>

<h3 id="21-explain关键字">2.1 EXPLAIN关键字</h3>

<p>OceanBase可以利用<em>EXPLAIN</em>关键字得到查询的执行计划。</p>

<ul>
  <li>示例：
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>obclient&gt; explain select count(*) from table_0, table_6, table_5, table_3 
where table_0.fk_0 = table_6.primaryKey and table_0.fk_1 = table_5.primaryKey 	
and table_0.fk_3 = table_3.primaryKey and table_0.col_0 &gt; 10000 
and table_5.col_6 &gt; 10000 and table_6.col_3 &gt; 10000 and table_3.col_2 &gt; 10000;  
      
| Query Plan  
===================================================  
|ID|OPERATOR             |NAME    |EST. ROWS|COST |  
---------------------------------------------------  
|0 |SCALAR GROUP BY      |        |1        |15299|  
|1 | HASH JOIN           |        |334      |15236|  
|2 |  PX COORDINATOR     |        |338      |11647|  
|3 |   EXCHANGE OUT DISTR|:EX10000|338      |11519|  
|4 |    HASH JOIN        |        |338      |11519|  
|5 |     HASH JOIN       |        |341      |6906 |  
|6 |      TABLE SCAN     |table_3 |400      |3148 |  
|7 |      TABLE SCAN     |table_0 |344      |2932 |  
|8 |     TABLE SCAN      |table_6 |452      |3580 |  
|9 |  TABLE SCAN         |table_5 |313      |2486 |  
===================================================  
      
Outputs &amp; filters: 
-------------------------------------
  0 - output([T_FUN_COUNT(*)]), filter(nil), 
  group(nil), agg_func([T_FUN_COUNT(*)])
  1 - output([1]), filter(nil), 
  equal_conds([table_0.fk_1 = table_5.primaryKey]), other_conds(nil)
  2 - output([table_0.fk_1]), filter(nil)
  3 - output([table_0.fk_1]), filter(nil), is_single, dop=1
  4 - output([table_0.fk_1]), filter(nil), 
  equal_conds([table_0.fk_0 = table_6.primaryKey]), other_conds(nil)
  5 - output([table_0.fk_1], [table_0.fk_0]), filter(nil), 
  equal_conds([table_0.fk_3 = table_3.primaryKey]), other_conds(nil)
  6 - output([table_3.primaryKey]), filter([table_3.col_2 &gt; 10000]), 
  access([table_3.primaryKey], [table_3.col_2]), partitions(p0)
  7 - output([table_0.fk_0], [table_0.fk_1], [table_0.fk_3]), filter([table_0.col_0 &gt; 10000]), 
  access([table_0.fk_0], [table_0.fk_1], [table_0.fk_3], [table_0.col_0]), partitions(p0)
  8 - output([table_6.primaryKey]), filter([table_6.col_3 &gt; 10000]), 
  access([table_6.primaryKey], [table_6.col_3]), partitions(p0)
  9 - output([table_5.primaryKey]), filter([table_5.col_6 &gt; 10000]), 
  access([table_5.primaryKey], [table_5.col_6]), partitions(p0)
|
</code></pre></div>    </div>
  </li>
  <li>
    <p><em>EXPLAIN</em>输出的第一部分是查询执行计划的树形结构，第二部分是各个算子的详细信息。在第一部分中，<strong>ID</strong>表示查询执行树按照前序遍历的方式得到的编号，<strong>OPERATOR</strong>表示算子的名称，<strong>NAME</strong>表示操作涉及的表名，<strong>EST.ROWS</strong>表示该算子的估算中间结果行数，<strong>COST</strong>表示该算子的执行代价。</p>
  </li>
  <li>在<strong>OPERATOR</strong>一列，每一个操作算子以缩进的形式表示其在树中的层次，层次深的算子先执行。以上面的查询执行计划为例：最先执行ID为6的TABLE SCAN算子和ID为7的TABLE SCAN算子，再执行ID为5的HASH JOIN操作，即先扫描表table_3和table_0，再对它们进行hash join。上层操作以此类推，最终得到的连接顺序为 <code class="language-plaintext highlighter-rouge">((table_3⋈table_0)⋈table_6)⋈table_5 </code>。</li>
</ul>

<h3 id="22-查询执行树">2.2 查询执行树</h3>

<ul>
  <li>
    <p>当查询涉及的表增多，我们可能无法一目了然地从<em>EXPLAIN</em>的查询计划中得到当前的连接顺序。因此，我们利用图形化的方式，将EXPLAIN的查询执行计划画成对应查询执行树，以更形象地展示各表的连接顺序。为了简化查询执行树，树上的节点只包含scan和join类型。</p>
  </li>
  <li>
    <p>我们利用<strong>DOT</strong>语言来实现以上目的，<strong>DOT</strong>语言是一种文本图形描述语言，语法相对简单。</p>
  </li>
  <li>
    <p>2.1节示例的DOT描述如下：</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>digraph binaryTree{  
"1_HASH_JOIN"-&gt;"4_HASH_JOIN";"1_HASH_JOIN"-&gt;"table_5";  
"4_HASH_JOIN"-&gt;"5_HASH_JOIN";"4_HASH_JOIN"-&gt;"table_6";  
"5_HASH_JOIN"-&gt;"table_3";"5_HASH_JOIN"-&gt;"table_0";  
}
</code></pre></div>    </div>
  </li>
  <li>
    <p>命令：</p>

    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dot -Tpng example.dot -o example.png
</code></pre></div>    </div>
  </li>
  <li>
    <p>生成简化的查询执行树为：</p>
  </li>
</ul>

<p class="center"><img src="/auto-image/picrepo/29da373e-bc69-46c2-8847-8f58a7622ab5.png" alt="image" /></p>

<h2 id="三实验设计">三、实验设计</h2>

<h3 id="31-实验流程">3.1 实验流程</h3>

<ul>
  <li>实验流程图如下：</li>
</ul>

<p><img src="/auto-image/picrepo/d59dfb71-d6ba-420a-aefc-c90cb43a85af.png" alt="image" /></p>
<ul>
  <li>
    <p>对于每一个查询，我们枚举它所有可能的连接顺序，并利用hint关键字<em>LEADING</em>，强制OceanBase以特定连接顺序执行查询。</p>
  </li>
  <li>
    <p>我们得到查询的所有连接顺序及其对应执行时间，从而得到OceanBase所选连接顺序在搜索空间中的排名情况。</p>
  </li>
  <li>
    <p>示例：</p>

    <ul>
      <li>
        <p><code class="language-plaintext highlighter-rouge">query: SELECT * FROM A, B, C WHERE A.id = B.id AND A.id = C.id;</code></p>
      </li>
      <li>
        <p>hint关键字_LEADING_应用举例：</p>

        <p><code class="language-plaintext highlighter-rouge">SELECT /*+LEADING(A, B, C)*/ FROM A, B, C WHERE A.id = B.id AND A.id = C.id;</code></p>

        <p>表示强制以 <code class="language-plaintext highlighter-rouge">(A⋈B)⋈C</code> 的顺序执行查询。</p>
      </li>
      <li>
        <p>按照执行时间由小到大将连接顺序排序：</p>
      </li>
    </ul>

    <p class="center"><img src="/auto-image/picrepo/248c5296-4519-45dd-9589-4a09673e45be.png" alt="image" /></p>

    <ul>
      <li>其中OceanBase所选的连接顺序为(A⋈C)⋈B，则其排名为2。</li>
    </ul>
  </li>
  <li>
    <p><strong>注意</strong>：由于随着连接的表数量增加，搜索空间的大小呈指数级增长，如6张表的搜索空间为720，7张表的搜索空间为5040，8张表的搜索空间为40320……因此想让数据库执行搜索空间中每一个连接顺序是十分耗时的。在实验中，当搜索空间大于100时，我们将OceanBase选择的连接顺序作为一个候选项放入评估池，在搜索空间中再随机选择100个连接顺序，共101个评估对象；然后我们评估OceanBase选择的连接对象在101个评估对象中的相对位置，实现对OceanBase多表join的效果评估。</p>
  </li>
</ul>

<h3 id="32-评价指标">3.2 评价指标</h3>

<ul>
  <li>
    <p><strong>Mean Reciprocal Rank (MRR)</strong></p>

    <ul class="center">
      <li>
        <p>在检索系统中，<strong>MRR</strong>值表示正确检索结果值在检索结果中的排名，用来评估检索系统的性能。</p>
      </li>
      <li>
        <p>公式：<br />
<img src="/auto-image/picrepo/942b2538-92b9-437e-aa63-9f54453e4116.png" alt="image" /></p>
      </li>
    </ul>
    <p>其中，|Q| 是query的个数，*rank<sub>i</sub>* 是对于第*i*个query，OB选择的连接顺序的执行时间在所有连接顺序执行时间中的排列位置（从小到大）。
  	*   举例：</p>

    <p class="center"><img src="/auto-image/picrepo/d6c204d0-542b-4743-8e87-243061e6d0ea.png" alt="image" /></p>
    <p>得到 MRR值为：</p>

    <p class="center"><img src="/auto-image/picrepo/5e1e18b1-3263-4916-b1f3-dbe43d07975d.png" alt="image" /></p>
  </li>
  <li>
    <p><strong>Deviation</strong> 偏差</p>

    <ul>
      <li>
        <p>计算优化器所选连接顺序的执行时间与最优执行时间之间的偏差。</p>
      </li>
      <li>
        <p>公式：</p>
      </li>
    </ul>

    <p class="center"><img src="/auto-image/picrepo/79dceb0f-62b7-4beb-8b4c-092b599fa848.png" alt="image" /></p>
    <p>其中，<em>T</em> 表示优化器所选连接顺序的执行时间，<em>T<sub>b</sub></em> 表示枚举的连接顺序搜索空间中最优（最短）的执行时间。</p>
  </li>
</ul>

<h2 id="四实验配置">四、实验配置</h2>

<h3 id="41-机器配置">4.1 机器配置</h3>

<ul>
  <li>
    <p>本次实验将OceanBase部署在4台机器上，机器配置如下：</p>

    <p><img src="/auto-image/picrepo/f39af140-7d93-46c7-b086-4a3abab1715e.png" alt="image" /></p>
    <h3 id="42-集群配置">4.2 集群配置</h3>
  </li>
  <li>
    <p>OceanBase数据库部署的配置文件如下：</p>
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>user:
username: xxx
password: xxx
# key_file: .ssh/authorized_keys
oceanbase-ce:
version: 3.1.2
servers:
    - name: host1
    ip: 10.24.14.55
    - name: host2
    ip: 10.24.14.228
    - name: host3
    ip: 10.24.14.111
global:
    devname: eth0
    cluster_id: 1
    memory_limit: 28G
    system_memory: 8G
    stack_size: 512K
    cpu_count: 16
    cache_wash_threshold: 1G
    __min_full_resource_pool_memory: 268435456
    workers_per_cpu_quota: 10
    schema_history_expire_time: 1d
    net_thread_count: 4
    major_freeze_duty_time: Disable
    minor_freeze_times: 10
    enable_separate_sys_clog: 0
    enable_merge_by_turn: FALSE
    datafile_disk_percentage: 35
    syslog_level: WARN
    enable_syslog_recycle: true
    max_syslog_file_count: 4
    appname: obct
host1:
    mysql_port: 3883
    rpc_port: 3882
    home_path: /data/obdata1
    zone: zone0
host2:
    mysql_port: 3883
    rpc_port: 3882
    home_path: /data/obdata1
    zone: zone1
host3:
    mysql_port: 3883
    rpc_port: 3882
    home_path: /data/obdata1
    zone: zone2

obproxy:
servers:
    - 10.24.14.188
global:
    listen_port: 3883
    home_path: /data/obproxy1
    rs_list: 10.24.14.55:3883;10.24.14.228:3883;10.24.14.111:3883
    enable_cluster_checkout: false
    cluster_name: obct
</code></pre></div>    </div>
  </li>
</ul>

<h3 id="43-实验数据">4.3 实验数据</h3>

<ul>
  <li>本实验采用随机生成的数据和负载，并且保证所有实验负载的有效性，即负载中的每个算子均有返回结果集。
    <ul>
      <li>
        <p>表规模：</p>

        <ul>
          <li>每次生成16张表，其中40%的表有1,000~5,000行数据（约500KB），30%的表有10,000~50,000行数据（约6MB），30%的表有100,000~500,000行数据（约140MB）。</li>
        </ul>
      </li>
      <li>
        <p>查询:</p>

        <ul>
          <li>分别针对3、4、5、6、7、8张表的连接，各随机生成100条不同的查询。</li>
        </ul>
      </li>
    </ul>
  </li>
  <li>
    <p>实验数据获取地址</p>

    <ul>
      <li><a href="https://drive.google.com/drive/folders/13MgsQpy1pwxru73csPgiH38_-zP33WKC?usp=sharing">https://drive.google.com/drive/folders/13MgsQpy1pwxru73csPgiH38_-zP33WKC?usp=sharing</a></li>
    </ul>
  </li>
</ul>

<h3 id="44-注意点">4.4 注意点</h3>

<ul>
  <li>为避免查询计划缓存对实验结果的影响，我们将系统变量<strong>ob_enable_plan_cache</strong> 设置为 <strong>FALSE</strong> ，表示 SQL 请求不可以使用计划缓存。</li>
</ul>

<h2 id="五实验结果展示与分析">五、实验结果展示与分析</h2>

<h3 id="51-实验结果">5.1 实验结果</h3>

<ul>
  <li>
    <p>下表展示了3~8张表连接时的MRR值。<br />
<img src="/auto-image/picrepo/5d777389-3c34-4d64-b286-7c445ca426d2.png" alt="image" /></p>

    <p>同时，我们也在<strong>TiDB</strong>上进行了3 join 与4 join的实验，得到的MRR值分别为0.35与0.22，明显劣于OceanBase。</p>
  </li>
  <li>
    <p>图1 展示了3~8张表连接时的deviation结果，横坐标表示参与连接表的数量，纵坐标表示优化器选择的连接顺序的执行时间与最优执行时间的偏差。</p>

    <p><img src="/auto-image/picrepo/46732774-a3a4-46c1-8a84-2a43c128cb1a.png" alt="image" /></p>
  </li>
  <li>
    <p>从图1中可以看到，当参与连接表的数量小于等于5时，偏差大部分低于20%。经过计算，我们得到去除异常值后，平均执行时间差低于42毫秒（执行时间差 = OceanBase选择的连接顺序执行时间 - 最优连接顺序执行时间）。</p>
  </li>
  <li>
    <p>我们可以观察到，参与连接的表数目从3增长到6时，MRR值逐渐减少，deviation整体呈现增长趋势，如中位线从0.82%增长到15.35%（增长了17.7倍），均值从6.33%增长到17.89%（增长了1.8倍）。这个结果说明了随着参与连接的表数量增大，OceanBase选择的连接顺序在搜索空间中的排名越来越低，与最优连接顺序的执行时间偏差越来越大，优化器从连接顺序搜索空间中选择出最优连接顺序的性能下降了。</p>
  </li>
  <li>
    <p>当表数量增加到7时，MRR值逐渐增大，deviation整体呈现下降趋势，这是因为我们仅仅从庞大的搜索空间中随机选择100个连接顺序进行评估，如7张表时仅选择了2% (100/5040)的连接顺序，8张表时仅选择了0.25% (100/40320)的连接顺序。我们很有可能并没有随机到最优的连接顺序。为此，我们增加了图2 的实验。</p>

    <p><img src="/auto-image/picrepo/3a0b91a6-e9eb-41bc-bb9e-9b933656940d.png" alt="image" /></p>
  </li>
  <li>
    <p>图2 展示了7张表参与连接时，分别随机选择100个连接顺序（random100）与随机选择200个连接顺序（random200）的deviation结果对比。我们可以看到，当随机的搜索空间增大时，deviation整体呈现增长趋势，中位线从11.29%增长到14.28%（增长了0.3倍），均值从12.53%增长到17.00%（增长了0.4倍）。同时，random200的MRR值为0.25，比random100的MRR值0.34下降了26.47%。这个结果说明了图1 的下降趋势与搜索空间的大小有关，即我们设定的搜索空间大小很大程度上影响了最终结果的准确性。</p>
  </li>
  <li>
    <p>为了得到更精确的结果，更好的评估连接顺序选择，我们将在今后的实验中改进搜索空间的剪枝策略来代替随机选择。</p>
  </li>
</ul>

<h3 id="52-案例分析">5.2 案例分析</h3>

<p>从图1中，我们可以看到有不少偏差较大的离散点，下面我们对其中两个点进行分析，探究偏差产生的原因。</p>

<h4 id="521-案例一">5.2.1 案例一</h4>

<ul>
  <li>
    <p>图1 4张表参与连接时最大偏差为<strong>58.02%</strong>。</p>
  </li>
  <li>该点对应的查询为：
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select count(*) as result from table_0, table_9, table_4, table_5  
	where table_4.col_8 &lt;= 1922008581 and table_5.col_0 &lt; -348809115.7006844609905468  
	and table_0.col_7 &gt; -4255518.1398595209726152 and table_9.col_5 &lt; -211145821.32382347996192376  
	and table_4.fk_3 = table_5.primaryKey  
	and table_4.fk_11 = table_0.primaryKey  
	and table_9.fk_2 = table_0.primaryKey;
</code></pre></div>    </div>
  </li>
  <li>
    <p>为了避免高偏差是由数据异常等原因导致的，我们重新执行该查询及其对应最优连接顺序：</p>

    <p>分别执行7次，计算平均执行时间（去除最大最小值）。</p>

    <p><img src="/auto-image/picrepo/c657df70-d091-40f3-957e-f3f571a046bc.png" alt="image" />  <br />
两者偏差为<strong>30.43%</strong>，可见该查询的偏差依旧较大。</p>
  </li>
  <li>EXPLAIN该查询得到执行计划如下：</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>| ======================================================
|ID|OPERATOR               |NAME    |EST. ROWS|COST  |
------------------------------------------------------
|0 |SCALAR GROUP BY        |        |1        |923737|
|1 | HASH JOIN             |        |353299   |856253|
|2 |  PX COORDINATOR       |        |7804     |19664 |
|3 |   EXCHANGE OUT DISTR  |:EX10000|7804     |18925 |
|4 |    TABLE SCAN         |table_9 |7804     |18925 |
|5 |  HASH JOIN            |        |32468    |606413|
|6 |   PX COORDINATOR      |        |710      |1777  |
|7 |    EXCHANGE OUT DISTR |:EX20000|710      |1710  |
|8 |     TABLE SCAN        |table_0 |710      |1710  |
|9 |   HASH JOIN           |        |32796    |573707|
|10|    TABLE SCAN         |table_4 |33127    |274978|
|11|    PX COORDINATOR     |        |82996    |203567|
|12|     EXCHANGE OUT DISTR|:EX30000|82996    |195711|
|13|      TABLE SCAN       |table_5 |82996    |195711|
====================================================== 
</code></pre></div></div>

<ul>
  <li>OceanBase优化器选择的连接顺序为<code class="language-plaintext highlighter-rouge">((table_4⋈table_5)⋈table_0)⋈table_9</code>，实验得到最优的连接顺序为<code class="language-plaintext highlighter-rouge">((table_0⋈table_9)⋈table_4)⋈table_5</code>。</li>
  <li>
    <p>我们画出两个连接顺序的查询树如下（上面为OceanBase选择的连接顺序，下面为最优连接顺序）：</p>

    <ul>
      <li>
        <p>节点中”[]”内的数字是操作的真实基数（中间结果大小），其中join操作的基数是两表连接后的中间结果大小，scan操作的基数是表经过条件过滤后的中间结果大小。</p>

        <p><img src="/auto-image/picrepo/9ff56d47-7f7c-4771-aa51-ab8254327ab5.png" alt="imgalt" />        <img src="/auto-image/picrepo/8712d716-ebaf-4260-ac6e-66d3fc06854d.png" alt="imgalt" /></p>
      </li>
    </ul>
  </li>
  <li>我们可以看出，最优连接顺序的join操作的基数更小，或许是优化器对基数的错误估计误导了连接顺序的选择。</li>
</ul>

<h4 id="522-案例二">5.2.2 案例二</h4>

<ul>
  <li>
    <p>从图1中，我们可以看到偏差最大的是<strong>3 join</strong>中的一点 (<strong>85.73%</strong>)。</p>
  </li>
  <li>
    <p>该点查询为：</p>
  </li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    select count(*) as result from table_2, table_14, table_10  
    	where table_2.col_4 &lt; 1702600163 and table_14.col_1 &lt;= 39586856.6599801245771715  
    	and table_10.col_8 &gt; -633452491.72604654429895750  
    	and table_2.fk_0 = table_14.primaryKey  
    	and table_10.fk_0 = table_14.primaryKey;
</code></pre></div></div>

<ul>
  <li>OceanBase优化器选择的连接顺序为<code class="language-plaintext highlighter-rouge">(table_14⋈table_2)⋈table_10</code>，实验得到最优的连接顺序为<code class="language-plaintext highlighter-rouge">(table_2⋈table_14)⋈table_10</code>。</li>
  <li>
    <p>为了避免高偏差是由数据异常等原因导致的，我们重新执行该query及其对应最优连接顺序：</p>

    <p>分别执行7次，计算平均执行时间（去除最大最小值）。</p>

    <p><img src="/auto-image/picrepo/90e32a86-06a7-486b-8432-d40ad6d338ae.png" alt="image" />  <br />
两者偏差为<strong>64.85%</strong>，可见该query的偏差的确较大。</p>
  </li>
  <li>EXPLAIN该查询得到执行计划如下：</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>obclient&gt; EXPLAIN select count(*) as result from table_2, table_14, table_10 
	where table_2.col_4 &lt; 1702600163 and table_14.col_1 &lt;= 39586856.6599801245771715 
	and table_10.col_8 &gt; -633452491.72604654429895750 
	and table_2.fk_0 = table_14.primaryKey 
	and table_10.fk_0 = table_14.primaryKey;

| ======================================================
|ID|OPERATOR             |NAME    |EST. ROWS|COST    |
------------------------------------------------------
|0 |SCALAR GROUP BY      |        |1        |12360781|
|1 | HASH JOIN           |        |14725630 |9548003 |
|2 |  PX COORDINATOR     |        |44555    |414608  |
|3 |   EXCHANGE OUT DISTR|:EX10000|44555    |406173  |
|4 |    HASH JOIN        |        |44555    |406173  |
|5 |     TABLE SCAN      |table_14|356      |874     |
|6 |     TABLE SCAN      |table_2 |45005    |363648  |
|7 |  TABLE SCAN         |table_10|118626   |287577  |
======================================================
</code></pre></div></div>
<ul>
  <li>EXPLAIN最优连接顺序得到执行计划如下：</li>
</ul>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>obclient&gt; EXPLAIN select /*+LEADING(table_2, table_14, table_10)*/ count(*) as result from table_2, table_14, table_10 
	where table_2.col_4 &lt; 1702600163 and table_14.col_1 &lt;= 39586856.6599801245771715 
	and table_10.col_8 &gt; -633452491.72604654429895750 
	and table_2.fk_0 = table_14.primaryKey 
	and table_10.fk_0 = table_14.primaryKey;

| ======================================================
|ID|OPERATOR             |NAME    |EST. ROWS|COST    |
------------------------------------------------------
|0 |SCALAR GROUP BY      |        |1        |12360781|
|1 | HASH JOIN           |        |14725630 |9548003 |
|2 |  PX COORDINATOR     |        |44555    |414608  |
|3 |   EXCHANGE OUT DISTR|:EX10000|44555    |406173  |
|4 |    HASH JOIN        |        |44555    |406173  |
|5 |     TABLE SCAN      |table_2 |45005    |363648  |
|6 |     TABLE SCAN      |table_14|356      |874     |
|7 |  TABLE SCAN         |table_10|118626   |287577  |
======================================================
</code></pre></div></div>

<ul>
  <li>
    <p>其中，table_2经条件过滤后共450068行数据，table_14经条件过滤后共1066行数据。</p>
  </li>
  <li>
    <p>这里我们发现一个有趣的现象，两个执行计划的区别在于table_2和table_14进行hash join时谁作为hash表，前者以table_14作为hash表，后者以table_2作为hash表。按理来说，小表作为hash表，然后去扫描另一张表的每一行数据，用得出来的行数据根据连接条件去映射建立的hash表。而这里table_14作为小表充当hash表的执行效率却不如table_2充当hash表。后续的hash join算子优化或许可以将这个现象考虑进去。</p>
  </li>
</ul>

<h2 id="六总结">六、总结</h2>

<p>经过上述实验，我们将OceanBase在连接顺序选择上的表现作以下总结：</p>
<ul>
  <li>当参与连接的表数量小于等于5时，OceanBase的表现还是不错的，执行时间与最优的偏差大部分低于20%，平均执行时间差低于42毫秒。</li>
  <li>随着参与连接的表数量增长，OceanBase选择的连接顺序在搜索空间中的排名逐渐降低，与最优连接顺序的执行时间偏差逐渐增大，优化器从搜索空间中选择出最优连接顺序的性能呈现下降趋势。可以看出多表连接的优化还是一个难点问题。</li>
</ul>]]></content><author><name>陈婷</name></author><category term="join" /><summary type="html"><![CDATA[一、目的]]></summary></entry><entry><title type="html">Dike：面向分布式事务型数据库评测基准及工具介绍</title><link href="https://dbhammer.github.io/2022/03/14/Dike-%E9%9D%A2%E5%90%91%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AF%84%E6%B5%8B%E5%9F%BA%E5%87%86%E5%8F%8A%E5%B7%A5%E5%85%B7%E4%BB%8B%E7%BB%8D.html" rel="alternate" type="text/html" title="Dike：面向分布式事务型数据库评测基准及工具介绍" /><published>2022-03-14T00:00:00+00:00</published><updated>2026-03-31T13:16:28+00:00</updated><id>https://dbhammer.github.io/2022/03/14/Dike%EF%BC%9A%E9%9D%A2%E5%90%91%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AF%84%E6%B5%8B%E5%9F%BA%E5%87%86%E5%8F%8A%E5%B7%A5%E5%85%B7%E4%BB%8B%E7%BB%8D</id><content type="html" xml:base="https://dbhammer.github.io/2022/03/14/Dike-%E9%9D%A2%E5%90%91%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93%E8%AF%84%E6%B5%8B%E5%9F%BA%E5%87%86%E5%8F%8A%E5%B7%A5%E5%85%B7%E4%BB%8B%E7%BB%8D.html"><![CDATA[<h2 id="一工作的意义">一、工作的意义</h2>
<p>随着业务规模的增长，很多业务方都将自己的应用从集中的事务型数据库部署到分布式事务型数据库来追求可扩展和高可用。分布式事务型数据库根据存储和计算的不同扩展方式通常分为Share-Nothing架构、Share-Nothing计算和存储分离架构以及Share-Storage架构（如图1）。这三种不同的架构有不同的适用场景，而且不同的数据库也有存在不同的瓶颈点和优化技术，这就需要一种评测手段能够给出公平的评价和分析。</p>

<p>我们将常用的事务型评测基准标记在图1中，他们被分为三类，分别是：微观的评测基准、应用驱动的评测基准和目的驱动的评测基准。我们发现事务型评测基准大多数在2010前被提出来，而分布式事务型数据库却在2010年之后开始迅猛发展。 所以，这就引发了一个问题：现有的事务型评测基准是否仍然适用于评测分布式事务型数据库？
<img src="/illustration/分布式事务型数据库和OLTP评测基准的发展历史.png" alt="图1 分布式事务型数据库和OLTP评测基准的发展历史" style="width: 100%;" /></p>

<p class="center">图1 分布式事务型数据库和OLTP评测基准的发展历史</p>
<p>在对现有的分布式事务型数据库进行了调研和总结之后，我们认为面向分布式事务型数据库的评测基准必须涵盖如下三个方面：分布式事务型数据库的瓶颈点（分布式事务比例、分布式事务跨节点、分布式查询的性能、冲突）， 分布式数据库数据库的优化手段（读写分离、小表广播、数据放置方式）等和功能测试（分布式死锁、全局快照、分布式并发控制、一致性测试、可用性测试）。 然而，经过我们的调研和实验发现，现有的事务型评测基准没有完全覆盖到以上三个方面的评测。于是，我们提出并设计了一款面向分布式事务型数据库评测的基准并实现了Dike工具， 它不仅能够评判被测数据库是否满足分布式事务型数据库的基本特征，而且还能针对性地对分布式事务型数据库的瓶颈点和优化手段进行评测。</p>

<h2 id="二-benchmark特征">二、	Benchmark特征</h2>
<p>为了公平且有效地评测各个分布式事务型数据库，我们对Dike提出了以下四点设计要求：第一，功能健全性，它能帮助我们去回答当前的数据库是否是分布式事务型数据库；第二，微观性，它能够针对性地评测被测的分布式事务型数据库在研发过程中的优化点和瓶颈点；第三，定量性，它能够针对分布式事务型数据库中瓶颈点进行定量的评测分析，比如，通过定量地改变事务跨节点数的多少来看分布式事务型数据库的性能变化情况；第四，动态性，它能够提供模拟业务动态变化的情况，这也是符合现实的应用需求，如热点和负载量的动态变化。</p>

<h2 id="三-工具设计与实现">三、	工具设计与实现</h2>
<p><img src="/illustration/Dike架构图.png" alt="图2 Dike架构图" style="width: 100%;" /></p>

<p class="center">图2 Dike架构图</p>
<p>根据分布式事务型数据库评测的要求，我们定义并实现了Dike，架构图如图2所示。Dike包括了数据生成器、负载生成器、混沌生成器、验证器和性能收集器。
为验证被测数据库是否满足分布式事务型数据库的基本特征，Dike首先从配置文件中读取数据库信息等，之后调用数据生成器来生成指定规格的数据。然后，Dike调用验证器来生成负载对被测数据库进行功能测试。最后，Dike将功能测试的结果反馈给用户。</p>

<p>为评测被测数据库潜在的瓶颈点和优化技术，Dike同样先从配置文件中读取数据库信息和评测内容（如分布式事务的处理能力），之后根据配置信息调用数据生成器来生成指定规格的数据。然后，Dike调用负载生成器来生成指定的负载进行性能测试。最后，Dike调用性能收集器将结果整合并反馈给用户。</p>

<p>为评测被测数据库的可用性，Dike专门设计了混沌生成器。它能够根据用户的要求进行一系列分布式场景中可能的故障注入。</p>

<p>此外，该工具提供了可定制化的服务，用户可以通过修改配置项来评测特定的内容。</p>]]></content><author><name>瞿璐祎</name></author><category term="benchmark" /><summary type="html"><![CDATA[一、工作的意义 随着业务规模的增长，很多业务方都将自己的应用从集中的事务型数据库部署到分布式事务型数据库来追求可扩展和高可用。分布式事务型数据库根据存储和计算的不同扩展方式通常分为Share-Nothing架构、Share-Nothing计算和存储分离架构以及Share-Storage架构（如图1）。这三种不同的架构有不同的适用场景，而且不同的数据库也有存在不同的瓶颈点和优化技术，这就需要一种评测手段能够给出公平的评价和分析。]]></summary></entry></feed>