<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>Longda Feng</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <icon>https://ilongda.com/img/my.jpg</icon>
  <id>https://ilongda.com/</id>
  <link href="https://ilongda.com/" rel="alternate"/>
  <link href="https://ilongda.com/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, Longda Feng</rights>
  <subtitle>Longda's Interesting World</subtitle>
  <title>Longda's Interesting World</title>
  <updated>2026-06-09T08:46:25.934Z</updated>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="读书笔记" scheme="https://ilongda.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <category term="读书笔记" scheme="https://ilongda.com/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"HashCorp Reading Notes","description":"HashCorp 开源商业化万字文章读后感：探讨开源与企业级、渠道合作、S&M 成本及工具背后方法论等商业与产品观点，更多细节与示例见正文。","image":"https://ilongda.com/assets/640.png","wordCount":1828,"datePublished":"2022-02-28T11:07:43.000Z","dateModified":"2026-06-09T08:46:25.934Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2022/hashcorp-read-notes/"},"url":"https://ilongda.com/2022/hashcorp-read-notes/","inLanguage":"zh-CN","keywords":["读书笔记"],"articleSection":["读书笔记"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"读书笔记","item":"https://ilongda.com/categories/读书笔记/"},{"@type":"ListItem","position":3,"name":"HashCorp Reading Notes","item":"https://ilongda.com/2022/hashcorp-read-notes/"}]}</script><h1 id="随谈"><a href="#随谈" class="headerlink" title="随谈"></a>随谈</h1><p>万字 大文章 <span class="exturl" data-url="aHR0cHM6Ly9tcC53ZWl4aW4ucXEuY29tL3MvWTJBNy1VaTJuelVnb2RrRWJnUjZsUQ==">https://mp.weixin.qq.com/s/Y2A7-Ui2nzUgodkEbgR6lQ<i class="fa fa-external-link-alt"></i></span></p><p>有很多理念,还是比较认同的:</p><span id="more"></span><ol><li>技术挑战 放到 开源里面做,  这一点不是很认同, 我认为, 开源解决的是规模小的需求, 或者某个特定的的非常common的问题, 而当规模上来后, 是需要商业化来解决, 或者当需求变得复杂, 需要一系列的措施来解决, 是需要商业化的技术方案来解决. </li><li>个人或者小的团队, 就应该免费使用, 这个逻辑基本成立. 跨团队协作的需求, 商业化机会比较大</li><li>要把价格门槛低的核心产品做的简单易用，同时又要兼顾未来组织内部对产品的复杂需求。 – 这个很认同</li><li>即使某些功能在开源中有，但是这些功能无法从一个完整的use case level来解决企业面对的问题。  真实操作中, 会将这个放大. </li><li>讨论放到一个具体的use case,而不是某一个功能，这样对于sales也会更好沟通</li><li>开源公司要拿到一些客户订单并不难。但是这里说到市场和销售，核心是repeatable&#x2F;scalable。</li><li>利用开源形成事实标准，是企业最牢固的隐形护城河。</li><li>开源模式下，sales最大的挑战就是公司自己的开源产品。</li><li>开源模式的S&amp;M (Sales &amp; Marketing)花费应该比传统软件公司要少呀。但是Hashicorp S&amp;M&#x2F;Rev 比例超过60%，在整个public SaaS公司中，算是比较高的了. 时代在变, 有很多开源运作的成本, 处于技术和品牌交叉的, 这块如果放在marketing, 自然markteing的成本会大幅上升, 而且这个会成为趋势. </li><li>开源天生就是global的生意。– Hashicorp和Confluent的国际业务发展都很快。两家商业化都是5年左右的时间，都已经有35%的收入来自美国以外。</li><li>渠道玩得溜溜的 – Hashicorp在开始商业化以后第二年就开始大力发展partners, 三四年的时间，已经建立起来一个170+ ISVs，超过450个integration partner的网络</li><li>面对大客户，只是交给对方工具远远不够。 – 最先进的enterprise software公司，输出的不仅是工具，更是工具背后的方法论(当然，输出方法论的成本也是不低的)。  – 于这样一个大工程，你不能光是提供一系列工具，还要向他们展示the way to get there.  – 工具类产品比拼的往往不是单纯的性能，而是工具背后的代表新的生产方式的方法论。把best practice抽象成方法论，难度可不比工程上的性能提升要小。一旦让这个方法论成为事实标准，才是真正的护城河。</li><li>护城河绝不是单纯把产品做成大而全的平台，把一堆60-70分的产品盲目堆砌起来。</li><li>Hashicorp的S-1中，把这个模式进一步细化为adopt, land, expand, and extend。其实本质也是PLG里的套路：</li></ol><ul><li>用社区&#x2F;marketing促进Adopt</li><li>用简单易上手的产品+初始低价降低初始Landing的门槛</li><li>基于Usage实现自然增长Expand</li><li>最后用product portfolio在每个cohort中Extend</li></ul><ol start="15"><li>在SaaS land&amp;expand这个模式中，不可缺少的一环就是usage-based pricing。– 看看现在HCP上几个产品的pricing,你也许会发现一个问题，就是这个pricing unit的设计其实很有讲究。– 你设定的pricing unit除了要计算方便之外，必须避免Usage is discouraged when customers feel the marginal cost of consumption</li><li>etl 公司, Airbyte（<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2FpcmJ5dGVocS9haXJieXRlJUVGJUJDJTg5">https://github.com/airbytehq/airbyte）<i class="fa fa-external-link-alt"></i></span>,  – 刚刚close了B轮融资$150M, 估值已经飙升到$1.5Bn！不到20个月，已经刷刷刷地三轮融了$181M</li><li>在SaaS模式已经被广泛接受的年代，后来者迅速开发Cloud版本抢占“基层”市场，几乎是个定式，只会到来得越来越快。</li><li>Win developer’s mind and heart! hashcorp – 他们旗下的repo加起来超过220k stars </li><li>几乎没有哪个成功的开源社区，早期的时候没有在线下做大量后来看起来完全不scalable的事情。  纯粹误解: Github一上线，HN、Reddit各种线上宣传一下，回答问题和PR，然后做好技术和performance，社区啊用户啊就应该自己围过来了么？</li><li>首先，Meetup不可少，抓住各种community抱大腿。– 一开始靠身边的亲朋好友宣传，速度当然很慢。后来，两位创始人很积极到各种Seattle当地的社区meetup、Ruby社区、QCon,DevOpsDay等等，在各种活动上寻找刷脸的机会</li><li>Hashicorp就开始全方位建立自己的社区，其中最重要的就是HUG - Hashicorp User Groups. 这个分散在世界各地的自发性组织，如今已经有37k+的会员，遍及53个国家。各种自发的Meetup和活动，不断深化与开发者的关系</li><li>Hashicorp对于Conference的投入格外重视。</li><li>特别重要的是，主动出击，在一线跟早期用户深度交流。</li><li>你要能叫出你的项目前100个用户的名字！</li><li>社区不是最终目的。M小姐认为，终极目标，还是成为行业的事实标准. – 要实现这一点，产品设计、社区搭建，以及商业伙伴的合作，是不可割裂的整体。</li><li>从产品设计上来说：不要憋大招，第一个产品只要能prove idea就可以。</li><li>简单对比了几个比较顶尖的开源项目Star数和Contributor的比例，很有意思的发现是，这个比例惊人的相似，几乎都在0.03！</li><li>有些开源公司将社群运营看成了一个纯粹marketing的“用户社区”，忽视了开源这个复杂生态中，每个stakeholder的重要性。– 要能在商业有起色之前，有如此热血的坚持，真爱是必要条件。</li><li>产品设计的一套方法论, 首先，永远被摆在第一位的，Built for workflow, not technologies. – 他们将workflow拆解成三个部分：People, process, tools. – 设计一个workflow产品&#x2F;工具的时候，很多人只是看着工具本身的功能，而没有想到，这里面对人的技能的要求是怎样的，对IT流程的假设是self-service还是工单系统，这些随着环境和具体技术的变化，有什么可以抽象出来保持一致的？</li><li>尊重技术，但是更要重视human element. 就像前面说的Cloud Operation Model.他们发现你不能直接把最终的牛逼哄哄的最佳实践给客户，向客户展现your way to get there，就要接受在这个过程中一些不那么完美的方案。</li><li>跟很多开源公司很像，Hashicorp也遵循transparent operation的理念，将公司的很多管理细则、决策原则等等，都公开在网上. 这个挺难的, 刚开始还比较容易, 当商业化逐渐深入, 很多事情反而扑朔迷离. </li><li>两家公司都非常非常强调writing以及Over communication! 这个不错.</li></ol><img data-src="/assets/640.png"  alt="HashCorp Reading Notes"><img data-src="/assets/640-2.png"  alt="HashCorp Reading Notes"><img data-src="/assets/640-3.png"  alt="HashCorp Reading Notes"><img data-src="/assets/640-4.png"  alt="HashCorp Reading Notes"><img data-src="/assets/640-5.png"  alt="HashCorp Reading Notes"><img data-src="/assets/640-6.png"  alt="HashCorp Reading Notes"><img data-src="/assets/640-7.png"  alt="HashCorp Reading Notes"><img data-src="/assets/640-8.png"  alt="HashCorp Reading Notes"><img data-src="/assets/640-9.png"  alt="HashCorp Reading Notes"><img data-src="/assets/640-10.png"  alt="HashCorp Reading Notes"><img data-src="/assets/640-11.png"  alt="HashCorp Reading Notes"><img data-src="/assets/640-12.png"  alt="HashCorp Reading Notes">]]>
    </content>
    <id>https://ilongda.com/2022/hashcorp-read-notes/</id>
    <link href="https://ilongda.com/2022/hashcorp-read-notes/"/>
    <published>2022-02-28T11:07:43.000Z</published>
    <summary>
      <![CDATA[HashCorp 开源商业化万字文章读后感：探讨开源与企业级、渠道合作、S&M 成本及工具背后方法论等商业与产品观点，更多细节与示例见正文。]]>
    </summary>
    <title>HashCorp Reading Notes</title>
    <updated>2026-06-09T08:46:25.934Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="生活" scheme="https://ilongda.com/categories/%E7%94%9F%E6%B4%BB/"/>
    <category term="Tesla" scheme="https://ilongda.com/tags/Tesla/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Tesla 行驶中突然失去动力","description":"特斯拉行驶中突然失动力经历：换挡误触触发前电机禁用，记录故障现象、远程数据上传与官方检修离谱回复，更多细节与示例见正文。","image":"https://ilongda.com/assets/tesla-stop.jpg","wordCount":1212,"datePublished":"2022-02-26T11:07:43.000Z","dateModified":"2026-06-09T08:46:25.945Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2022/tesla-is-bullshit/"},"url":"https://ilongda.com/2022/tesla-is-bullshit/","inLanguage":"zh-CN","keywords":["Tesla"],"articleSection":["生活"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"生活","item":"https://ilongda.com/categories/生活/"},{"@type":"ListItem","position":3,"name":"Tesla 行驶中突然失去动力","item":"https://ilongda.com/2022/tesla-is-bullshit/"}]}</script><h1 id="随谈"><a href="#随谈" class="headerlink" title="随谈"></a>随谈</h1><p>从这次开车开车过程中, 车子突然失去动力后, 坦白讲, 我从一位Tesla 粉开始转黑. 过去4年一直推荐朋友买Tesla, 我现在只会建议, 大家还是买老牌的油车, 不要选择一个以互联网方式做事的车企. </p><p>Tesla 太激进了, 一直将用户当作小白鼠来实验, 从Tesla 系统的升级策略上来讲, 基本上一个月一次, 这么高的频率, 试问怎么可能会做大量充分的测试呢. 而且, 过去很多事实(1. 在中国偷工减料; 2. 私传数据; 3. 刹车门事件)已经证明, Tesla 只是对中国消费者身上如何获得利润, 而不是如何更好的服务. </p><img data-src="/assets/tesla-stop.jpg"  alt="Tesla 行驶中突然失去动力"><span id="more"></span><h1 id="经过"><a href="#经过" class="headerlink" title="经过"></a>经过</h1><p>即使Tesla 对我赔偿, 我根本也不care, 只是国内太多的Tesla粉, 觉得这个事情太傻了. 我先陈述一下事实,  事情经过是:</p><ol><li>2022年2月26日早上9点半，在路上正常行驶，拿手机过程中可能碰了一下换挡拨片，不知道切到了哪个档位，一下子屏幕出现报警，平时白色字显示的档位，全部变成红色，屏幕不停提示“前电机禁用” 过一会儿出现“路边安全停车”，此时车子完全失去动力.  (以前也出现过, 行驶中无意中触碰换挡拨片, 但都是只告警一后, 处理一下就恢复正常)</li><li>此时, 无论怎么切换档位，踩油门或者刹车都不行，就依靠车子惯性路边停车。 还好这个时候, 不是在高速上, 在一个流量不大的行驶道上, 否则不堪设想. </li><li>停车后，操作启动，换挡，加油，开门，关门 都无法启动车子，车子始终显示“前电机禁用” ，平时白色字显示的档位全部红色加粗显示。</li><li>强制重启特斯拉，花了很久（比平时关机时间长）才关机成功，重启也花了很久（也比平时重启的时间长，期间屏幕一直黑，估计后台自检程序检测不过），屏幕亮后，和重启前一样，屏幕显示“前电机禁用”，平时白色字显示的档位全部红色加粗显示，操作启动，换挡，加油，刹车，开门关门，各种操作无法启动车子。</li><li>最神奇的事情来了, 打电话给400特斯拉客服，告知安排拖车过来，人下车注意安全，下车后，过了15分钟左右，再上车发现一切恢复正常了，可以正常启动和操作，此时拖车公司电话过来，就取消了拖车. 事后, 和Tesla 维修人员沟通才了解, Tesla 自带一块手机卡, 非用户实名注册的手机卡, 可以直接将数据直接传输到Tesla 服务中心. </li><li>400特斯拉客服电话过来，我要求解释为什么行驶中突然失去动力？客服一堆官方各种推脱, 后来客户看实在是回答不下去了, 答应半小时给一个回复. </li><li>过了3小时，一直没有收到Tesla答复, 直到再次电话400特斯拉客服，客服才安排第二天送到4s店对电机进行检修。</li><li>再一次神奇的事情发生了,  Tesla 检修完, 给出一个官方的答复是 “数据量太大, 数据网关处理不过来, 发生故障”. 也就是这个时候, Tesla工作人员解释了这件事情, Tesla 会在后台收集数据, 然后上传到Tesla 数据中心, 数据量太大, 程序响应不过来, 现在, 已经讲固件重新格式化, 重新安装最新版本.</li></ol><img data-src="/assets/tesla_fix.jpg"  alt="Tesla 行驶中突然失去动力"><h1 id="反思"><a href="#反思" class="headerlink" title="反思"></a>反思</h1><p>以前, 以为Tesla 在美国已经跑了很多年, 应该是非常成熟的技术, 而且用户量也非常大, 现在想想, Tesla 还是太激进了, 而且百度一下 “tesla 失去动力”, 搜出一大堆问题, 这种事情, 个人推测tesla 为了更快的迭代速度, 根本没有做足够多的测试, 就把产品放到用户身上进行测试.<br>另外一点也一直想不通, tesla的自动驾驶, 居然还要花钱去买, 这种完全把用户当小白鼠来进行测试的一个不成熟的产品, 还要用户花 2万 ~ 8万, 将性命交给一个自动驾驶level 仅仅为2级的系统, 就算免费给我使用, 我都不会使用, 而且还有各种宣传各种开车睡觉的视频, 这种草菅人命的营销, 纯属误导消费者的行为, 希望国家或有关单位能勒令禁止. </p>]]>
    </content>
    <id>https://ilongda.com/2022/tesla-is-bullshit/</id>
    <link href="https://ilongda.com/2022/tesla-is-bullshit/"/>
    <published>2022-02-26T11:07:43.000Z</published>
    <summary>特斯拉行驶中突然失动力经历：换挡误触触发前电机禁用，记录故障现象、远程数据上传与官方检修离谱回复，更多细节与示例见正文。</summary>
    <title>Tesla 行驶中突然失去动力</title>
    <updated>2026-06-09T08:46:25.945Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="OceanBase" scheme="https://ilongda.com/categories/OceanBase/"/>
    <category term="DBA" scheme="https://ilongda.com/categories/OceanBase/DBA/"/>
    <category term="OceanBase" scheme="https://ilongda.com/tags/OceanBase/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"OceanBase监控对接Prometheus/Grafana","description":"OceanBase 入门 -- OceanBase监控对接Prometheus/Grafana","image":"https://ilongda.com/img/ob/obagent1.jpg","wordCount":1022,"datePublished":"2021-11-21T11:42:57.000Z","dateModified":"2026-06-08T06:29:57.852Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2021/obagent/"},"url":"https://ilongda.com/2021/obagent/","inLanguage":"zh-CN","keywords":["OceanBase"],"articleSection":["OceanBase","DBA"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"OceanBase","item":"https://ilongda.com/categories/OceanBase/"},{"@type":"ListItem","position":3,"name":"OceanBase监控对接Prometheus/Grafana","item":"https://ilongda.com/2021/obagent/"}]}</script><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>本文讲介绍如何让OceanBase监控对接Prometheus和Grafana. </p><p>​<img data-src="/img/ob/obagent1.jpg"  alt="OceanBase监控对接Prometheus/Grafana"><br>​<span id="more"></span></p><h1 id="安装流程"><a href="#安装流程" class="headerlink" title="安装流程"></a>安装流程</h1><p>大致过程, 分为3大步骤:</p><ol><li>安装oceanbase和obagent</li><li>安装prometheus和grafana</li><li>配置prometheus和grafana</li></ol><h2 id="安装OceanBase和Obagent"><a href="#安装OceanBase和Obagent" class="headerlink" title="安装OceanBase和Obagent"></a>安装OceanBase和Obagent</h2><p>如何安装OceanBase 可以参考上一篇文章<a href="https://ilongda.com/2021/ob_offline_install/">《OceanBase离线安装》</a>, 本节重点介绍如何安装obagent, 可以参考文档<span class="exturl" data-url="aHR0cHM6Ly9vcGVuLm9jZWFuYmFzZS5jb20vZG9jcy9jb21tdW5pdHkvb2NlYW5iYXNlLWRhdGFiYXNlL1YzLjEuMS91c2Utb2JkLXRvLWRlcGxveS1vYmFnZW50">使用 OBD 部署 OBAgent<i class="fa fa-external-link-alt"></i></span></p><p>OBAgent 是一个监控采集框架。OBAgent 支持推、拉两种数据采集模式，可以满足不同的应用场景。OBAgent 默认支持的插件包括主机数据采集、OceanBase 数据库指标的采集、监控数据标签处理和 Prometheus 协议的 HTTP 服务。要使 OBAgent 支持其他数据源的采集，或者自定义数据的处理流程，您只需要开发对应的插件即可。</p><p>obagent 的配置, 在原来的配置基础上, 增加了obagent的配置, 详情可以参考<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vYmRlcGxveS9ibG9iL21hc3Rlci9leGFtcGxlL2F1dG9kZXBsb3kvZGlzdHJpYnV0ZWQtd2l0aC1vYnByb3h5LWFuZC1vYmFnZW50LWV4YW1wbGUueWFtbA==">distributed-with-obproxy-and-obagent-example<i class="fa fa-external-link-alt"></i></span>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">obagent:</span><br><span class="line">  depends:</span><br><span class="line">    - oceanbase-ce</span><br><span class="line">  # The list of servers to be monitored. This list is consistent with the servers in oceanbase-ce. </span><br><span class="line">  servers:</span><br><span class="line">    - name: server1</span><br><span class="line">      # Please don&#x27;t use hostname, only IP is supported.</span><br><span class="line">      ip: 172.19.33.2</span><br><span class="line">    - name: server2</span><br><span class="line">      ip: 172.19.33.3</span><br><span class="line">    - name: server3</span><br><span class="line">      ip: 172.19.33.4</span><br><span class="line">  # Set dependent components for the component.</span><br><span class="line">  # When the associated configurations are not done, OBD will automatically get the these configurations from the dependent components.</span><br><span class="line">  depends:</span><br><span class="line">    - oceanbase-ce</span><br><span class="line">  global:</span><br><span class="line">    # The working directory for obagent. obagent is started under this directory. This is a required field.</span><br><span class="line">    home_path: /root/observer</span><br><span class="line">    skip_proxy_sys_private_check: true</span><br></pre></td></tr></table></figure><p>特别说明:</p><ol><li>depends里面的 “oceanbase-ce” 的名字必须和配置文件集群的名字一致. </li><li>servers里面的配置必须与在配置文件中”oceanbase-ce”一节中servers 配置一摸一样</li><li>记住home_path, 后续需要用到这个路径.</li></ol><p>安装完成后, 可以执行“obd cluster display ” 看到obagent 已经启动了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">obd cluster display obtest</span><br><span class="line">Get local repositories and plugins ok</span><br><span class="line">Open ssh connection ok</span><br><span class="line">Cluster status check ok</span><br><span class="line">Connect to observer ok</span><br><span class="line">Wait for observer init ok</span><br><span class="line">+-------------------------------------------------+</span><br><span class="line">|                     observer                    |</span><br><span class="line">+---------------+---------+------+-------+--------+</span><br><span class="line">| ip            | version | port | zone  | status |</span><br><span class="line">+---------------+---------+------+-------+--------+</span><br><span class="line">| 172.30.62.210 | 3.1.1   | 2881 | zone1 | active |</span><br><span class="line">| 172.30.62.211 | 3.1.1   | 2881 | zone2 | active |</span><br><span class="line">| 172.30.62.212 | 3.1.1   | 2881 | zone3 | active |</span><br><span class="line">+---------------+---------+------+-------+--------+</span><br><span class="line"></span><br><span class="line">Connect to obproxy ok</span><br><span class="line">+-------------------------------------------------+</span><br><span class="line">|                     obproxy                     |</span><br><span class="line">+---------------+------+-----------------+--------+</span><br><span class="line">| ip            | port | prometheus_port | status |</span><br><span class="line">+---------------+------+-----------------+--------+</span><br><span class="line">| 172.30.62.213 | 2883 | 2884            | active |</span><br><span class="line">+---------------+------+-----------------+--------+</span><br><span class="line">+---------------------------------------------------+</span><br><span class="line">|                      obagent                      |</span><br><span class="line">+---------------+-------------+------------+--------+</span><br><span class="line">| ip            | server_port | pprof_port | status |</span><br><span class="line">+---------------+-------------+------------+--------+</span><br><span class="line">| 172.30.62.210 | 8088        | 8089       | active |</span><br><span class="line">| 172.30.62.211 | 8088        | 8089       | active |</span><br><span class="line">| 172.30.62.212 | 8088        | 8089       | active |</span><br><span class="line">+---------------+-------------+------------+--------+</span><br></pre></td></tr></table></figure><h2 id="安装prometheus和grafana"><a href="#安装prometheus和grafana" class="headerlink" title="安装prometheus和grafana"></a>安装prometheus和grafana</h2><p>选择一台机器上安装prometheus 和grafana, 这台机器尽量不是observer 中的一台, 本例中, prometheus 和grafana 部署在obproxy机器上. </p><ol><li>从<span class="exturl" data-url="aHR0cHM6Ly9wcm9tZXRoZXVzLmlvL2Rvd25sb2FkLw==">https://prometheus.io/download/<i class="fa fa-external-link-alt"></i></span> 上把prometheus 和alertmanager 下载下来, 本章将不介绍 alertmanager 怎么使用. </li><li>从<span class="exturl" data-url="aHR0cHM6Ly9ncmFmYW5hLmNvbS9ncmFmYW5hL2Rvd25sb2FkP3BnPWdldCZwbGNtdD1zZWxmbWFuYWdlZC1ib3gxLWN0YTE=">https://grafana.com/grafana/download?pg=get&amp;plcmt=selfmanaged-box1-cta1<i class="fa fa-external-link-alt"></i></span> 上下载grapha</li><li>讲prometheus 和grafana 压缩包拷贝到obproxy 的机器上, </li><li>解压prometheus 和grafana</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># tar -xzf prometheus-2.31.0.linux-amd64.tar.gz</span><br><span class="line"># tar -xzf grafana-enterprise-8.2.3.linux-amd64.tar.gz</span><br></pre></td></tr></table></figure><h2 id="配置prometheus和grafana"><a href="#配置prometheus和grafana" class="headerlink" title="配置prometheus和grafana"></a>配置prometheus和grafana</h2><h3 id="配置prometheus"><a href="#配置prometheus" class="headerlink" title="配置prometheus"></a>配置prometheus</h3><ol><li>讲obagent上的prometheus 的配置文件给拷贝到prometheus 的安装目录中</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># cd prometheus-2.31.0.linux-amd64</span><br><span class="line"># mv prometheus.yml prometheus.yml.old</span><br><span class="line"># scp -r observer001:/root/observer/conf/prometheus_config/* . </span><br></pre></td></tr></table></figure><p>备注说明: </p><ol><li>observer001 为安装obagent的一台机器</li><li>&#x2F;root&#x2F;observer 为之前在配置文件中, 配置obagent中配置的home_path路径</li><li>从observer001 上会copy 过来几个文件, prometheus.yaml 和rules, rules 是存储拉取规则, prometheus 是配置prometheus的文件.</li></ol><p>启动prometheus</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nohup ./prometheus --config.file=./prometheus.yaml &gt;&gt; run.log 2&gt;&amp;1 &amp;</span><br></pre></td></tr></table></figure><p>检查run.log 可以查看运行日志. 正常情况下, </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># curl http://localhost:9090/metrics</span><br></pre></td></tr></table></figure><p>可以获得大量的数据. </p><h3 id="配置grafana"><a href="#配置grafana" class="headerlink" title="配置grafana"></a>配置grafana</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># cd grafana-8.2.3/</span><br><span class="line"># nohup bin/grafana-server &gt; run.log 2&gt;&amp;1 &amp;</span><br><span class="line"># ps -ef|grep grafana</span><br></pre></td></tr></table></figure><p>可以坚持run.log 或ps -ef|grep grafana 均可以查看到grafana 正常工作. </p><p>打开grafana 的页面,  第一次登录, 输入admin&#x2F;admin, 然后设置管理员密码, 然后增加data source<br>​<img data-src="/img/ob/obagent2.jpg"  alt="OceanBase监控对接Prometheus/Grafana"><br>进入增加data source后, 选择prometheus, 然后进入配置prometheus 后, 关键设置url<br>​<img data-src="/img/ob/obagent3.jpg"  alt="OceanBase监控对接Prometheus/Grafana"></p><p>import 配置项<br>​<img data-src="/img/ob/obagent4.jpg"  alt="OceanBase监控对接Prometheus/Grafana"></p><p>ob 已经提前准备好了 15215和15216 , 一个是监控oceanbase, 一个是监控host的.<br>当加载好模版后, 在dashboard 就可以看到2个预设好的dashboard, </p><p>​<img data-src="/img/ob/obagent1.jpg"  alt="OceanBase监控对接Prometheus/Grafana"></p><p>恭喜你, 已经完成配置oceanbase对接prometheus 和grafana</p>]]>
    </content>
    <id>https://ilongda.com/2021/obagent/</id>
    <link href="https://ilongda.com/2021/obagent/"/>
    <published>2021-11-21T11:42:57.000Z</published>
    <summary>OceanBase 入门 -- OceanBase监控对接Prometheus/Grafana</summary>
    <title>OceanBase监控对接Prometheus/Grafana</title>
    <updated>2026-06-08T06:29:57.852Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="OceanBase" scheme="https://ilongda.com/categories/OceanBase/"/>
    <category term="DBA" scheme="https://ilongda.com/categories/OceanBase/DBA/"/>
    <category term="OceanBase" scheme="https://ilongda.com/tags/OceanBase/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"OceanBase离线安装","description":"OceanBase 分布式三副本离线安装指南：涵盖 SSH 免密、磁盘规划、OBD 部署 OBServer/OBProxy 及 tenant 验证全流程","image":"https://ilongda.com/img/ob/install1.jpg","wordCount":7594,"datePublished":"2021-11-20T03:42:57.000Z","dateModified":"2026-06-09T08:46:25.942Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2021/ob_offline_install/"},"url":"https://ilongda.com/2021/ob_offline_install/","inLanguage":"zh-CN","keywords":["OceanBase"],"articleSection":["OceanBase","DBA"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"OceanBase","item":"https://ilongda.com/categories/OceanBase/"},{"@type":"ListItem","position":3,"name":"OceanBase离线安装","item":"https://ilongda.com/2021/ob_offline_install/"}]}</script><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>将8月份写的离线安装文档, share 出来. 后续可能会发一系列的使用文档. </p><p>本文将以线上业务运行OceanBase为目标, 部署分布式版本, 如果想简单测试和试用OceanBase, 请参考<span class="exturl" data-url="aHR0cHM6Ly9vcGVuLm9jZWFuYmFzZS5jb20vcXVpY2tTdGFydA==">https://open.oceanbase.com/quickStart<i class="fa fa-external-link-alt"></i></span><br>​</p><h1 id="安装流程"><a href="#安装流程" class="headerlink" title="安装流程"></a>安装流程</h1><p>​<img data-src="/img/ob/install1.jpg"  alt="OceanBase离线安装"></p><span id="more"></span><h1 id="安装前准备"><a href="#安装前准备" class="headerlink" title="安装前准备"></a>安装前准备</h1><p>安装前, 均使用root 进行操作, 安装过程中, 可以使用对应的普通用户</p><h2 id="名词解释"><a href="#名词解释" class="headerlink" title="名词解释"></a>名词解释</h2><ul><li>OBD: OceanBase Deployer,   OceanBase 部署工具</li><li>主控机: 运行OBD 安装包机器</li><li>OBServer : 每台安装 OceanBase 的物理机上面运行的 OceanBase 数据库进程&#x2F;服务，称为 OBServer  </li><li>OBProxy : OceanBase Proxy, 是 OceanBase 高性能反向代理服务器，具有防连接闪断、 屏蔽后端异常(宕机、升级、网络抖动)、MySQL 协议兼容、强校 验、支持热升级和多集群等功能<br>​</li></ul><h2 id="部署模式"><a href="#部署模式" class="headerlink" title="部署模式"></a>部署模式</h2><p>在本例采用经典的三幅本部署模式, 使用4台机器:</p><ul><li>1台机器 部署 OBProxy  – 推荐客户端应用和OBProxy 部署一起, 减少第一次网络时延.</li><li>1-1-1 部署3副本OceanBase 集群, 每个zone 表示一个副本, 在本例中, 一个zone 只包含一台机器. 3个zone 可以生产环境中常常部署两地三中心模式, 三个机房三个副本, 每个机房一个副本, 其中两个机房距离较近.</li></ul><p>​</p><h2 id="软硬件要求"><a href="#软硬件要求" class="headerlink" title="软硬件要求"></a>软硬件要求</h2><table><thead><tr><th>项目</th><th>描述</th></tr></thead><tbody><tr><td>系统</td><td>Red Hat Enterprise Linux Server 7.x 版本（内核 Linux 3.10.0 版本及以上） </br>CentOS Linux 7.x 版本（内核 Linux 3.10.0 版本及以上）</br> Anolis OS 8.x 版本（内核 Linux 3.10.0 版本及以上）</td></tr><tr><td>CPU</td><td>企业级用户最低要求16核, 推荐32核及以上 </br>个人测试最低要求2核, 推荐8核及以上</td></tr><tr><td>内存</td><td>企业级应用最低要求64G,  推荐256G 及以上 </br>个人测试最低要求8G, 推荐64G 及以上</td></tr><tr><td>磁盘类型</td><td>推荐SSD</td></tr><tr><td>磁盘空间</td><td>内存大小的4倍及以上</td></tr><tr><td>文件系统</td><td>ext4或xfs, 当数据量超过16TB时, 使用xfs</td></tr><tr><td>网卡</td><td>千兆互联及以上</td></tr></tbody></table><h2 id="设置无密码SSH-登录"><a href="#设置无密码SSH-登录" class="headerlink" title="设置无密码SSH 登录"></a>设置无密码SSH 登录</h2><p>在安装前, 需要对每台机器的环境进行设置, 这些设置都需在超级管理员下操作, 建议打通 主控机器 到OBServer 和OBProxy 机器的信任登陆(即无密码登录), 如何设置无密码SSH 登录, 详情参考 <span class="exturl" data-url="aHR0cHM6Ly9vcGVuLm9jZWFuYmFzZS5jb20vZG9jcy9jb21tdW5pdHkvb2NlYW5iYXNlLWRhdGFiYXNlL1YzLjEuMC9vcHRpb25hbC1zZXQtcGFzc3dvcmQtZnJlZS1zc2gtbG9nb24=">https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.0/optional-set-password-free-ssh-logon<i class="fa fa-external-link-alt"></i></span><br>​</p><p>推荐2个脚本, 方便在集群中批量执行命令和拷贝文件<br>批量拷贝文件,  可以将host list 换成自己实际机器列表</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">#/usr/bin/bash</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">hosts=(</span><br><span class="line">    &quot;ob001&quot;</span><br><span class="line">    &quot;ob002&quot;</span><br><span class="line">    &quot;ob003&quot;</span><br><span class="line">    &quot;obdriver&quot;</span><br><span class="line">    )</span><br><span class="line">    for host in &quot;$&#123;hosts[@]&#125;&quot;</span><br><span class="line">    do</span><br><span class="line"></span><br><span class="line">        echo &quot;begin to scp &quot; $@ &quot; on &quot; $host</span><br><span class="line">        scp -r $1 $host:$2</span><br><span class="line">    done</span><br></pre></td></tr></table></figure><p>批量执行命令, 可以将host list  换成自己实际的机器列表</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">/usr/bin/bash</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">hosts=(</span><br><span class="line">    &quot;ob001&quot;</span><br><span class="line">    &quot;ob002&quot;</span><br><span class="line">    &quot;ob003&quot;</span><br><span class="line">    &quot;obdriver&quot;</span><br><span class="line">    )</span><br><span class="line">    for host in &quot;$&#123;hosts[@]&#125;&quot;</span><br><span class="line">    do</span><br><span class="line">        echo &quot;begin to run &quot; $@ &quot; on &quot; $host</span><br><span class="line">        ssh $host $@</span><br><span class="line">    done</span><br></pre></td></tr></table></figure><h2 id="创建使用用户"><a href="#创建使用用户" class="headerlink" title="创建使用用户"></a>创建使用用户</h2><p>对于个人测试用户, 可以直接使用root 账号, 对于企业用户, 推荐创新普通用户, 避免对系统照成安全冲击.  在本例中, 使用admin 作为示范, 企业用户可以根据自己需要, 使用自己常用的账户<br>​</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">useradd -U admin -d /home/admin -s /bin/bash</span><br><span class="line">mkdir -p /home/admin</span><br><span class="line">sudo chown -R admin:admin /home/admin</span><br></pre></td></tr></table></figure><p>设置密码</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">passwd admin</span><br></pre></td></tr></table></figure><p>​</p><p>设置sudo 权限<br>vi &#x2F;etc&#x2F;sudoers      #添加oceanbase一行内容</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">## Same thing without a password</span><br><span class="line"># %wheel        ALL=(ALL)       NOPASSWD: ALL</span><br><span class="line">admin       ALL=(ALL)       NOPASSWD: ALL</span><br></pre></td></tr></table></figure><h2 id="磁盘规划"><a href="#磁盘规划" class="headerlink" title="磁盘规划"></a>磁盘规划</h2><p>OceanBase 数据库服务器依赖3个目录,   当个人测试使用时, 可以将所有数据放到1块盘下, 但在企业级用户中, 必须分别挂载3块磁盘: 数据盘, 事务日志盘, OBServer 安装盘. 当机器上没有3块盘时或者使用RAID 磁盘阵列时,  需要对磁盘或者磁盘阵列的逻辑卷进行分区, 分3块分区, 分区大小参考下面说明:</p><ul><li><p>数据盘</p><ul><li>配置参数为data_dir.  要根据业务需要，做好数据盘的规划. 数据盘承载了基线数据，物理上只有一个基线数据文件 block_file，在安装目录 store&#x2F;sstable 下。通过 OBServer 进程启动时一 次性创建，大小根据启动参数 datafile_disk_percentage 采用磁盘预分 配策略，默认值为 95%，创建后无法调整大小。OceanBase 的扩容缩 容采用加减机器的策略，目前不支持单机的磁盘级扩容和缩容。</li></ul></li><li><p>事务日志盘</p><ul><li>配置参数为redo_dir.  推荐大小为OBServer 内存3到4倍或以上. 事务日志盘包含多个固定大小的小文件，位于安装目录 store&#x2F;{clog,ilog,slog}，按需自动创建和清除，磁盘写到 80%会触发自 清除逻辑，但前提是这部分日志数据对应的内存数据已经通过合并融 合到了基线数据中，才能被删除。同等数据量， 事务日志的大小约为内存数据大小的三倍。所以事务日志盘所需空间 上限与两次合并操作间的业务数据总量成正比，经验公式是:事务日 志文件大小 &#x3D; 增量数据内存上限的 3 到 4 倍。</li></ul></li><li><p>OBServer 安装盘</p><ul><li><p>配置参数home_path. 推荐200G 或以上(保存7天或以上的日志量). OceanBase 的 rpm 包安装目录在&#x2F;home&#x2F;admin&#x2F;oceanbase 下，其中 基线数据文件和事务日志文件会通过软连接指向上述的两个独立磁 盘，还有另外一个不断增长的文件是 OB 运行日志，在安装目录 log 下。OB 进程本身无法自删除运行日志，需要定时任务或运维脚本完 成删除逻辑。 </p><p>磁盘划分后, 可以通过df -h 命令检查, 结果如下:</p></li></ul></li></ul><p>​<img data-src="/img/ob/install2.png"  alt="OceanBase离线安装"><br>在本示例中: &#x2F;data 为数据磁盘, 大小1TB, &#x2F;redo 存放 redo 日志,  &#x2F;home&#x2F;admin&#x2F;oceanbase 存放oceanbase binary 和运行日志<br>​</p><p>检查目录权限</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">ls –al #执行该命令</span><br><span class="line">drwxr-xr-x 2 admin admin 4096 2 月 9 18:43 </span><br><span class="line">drwxr-xr-x 2 admin admin 4096 2 月 9 18:43 log1</span><br><span class="line"></span><br><span class="line">若 admin 用户无权限，则以 root 用户执行如下命令 </span><br><span class="line">chown -R admin:admin /data</span><br><span class="line">chown -R admin:admin /redo</span><br><span class="line">chown -R admin:admin /home/admin</span><br></pre></td></tr></table></figure><p>​</p><h2 id="预检查"><a href="#预检查" class="headerlink" title="预检查"></a>预检查</h2><p>企业级用户建议运行OBServer 所有机器硬件配置和软件配置(操作系统, 操作系统内核, glibc, python 等软件包) 一致, OBProxy 机器和OBServer 机器软件配置一致(操作系统, 操作系统内核, glibc, python等软件包). </p><h3 id="检查操作系统"><a href="#检查操作系统" class="headerlink" title="检查操作系统"></a>检查操作系统</h3><p>当前支持的操作系统为:<br>​</p><p>Red Hat Enterprise Linux Server 7.x 版本（内核 Linux 3.10.0 版本及以上）<br>CentOS Linux 7.x 版本（内核 Linux 3.10.0 版本及以上）<br>​</p><ol><li>以root 用户登录服务器</li><li>查看os 版本</li></ol><p>RedHat7 系统显示如下 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root@redhat-04 /root]#cat /etc/redhat-release</span><br><span class="line">Red Hat Enterprise Linux Server release 7.2 (Maipo)</span><br></pre></td></tr></table></figure><p>CentOS7 系统显示如下: </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root@centos-01 /root]#cat /etc/redhat-release </span><br><span class="line">CentOS Linux release 7.2.1511 (Core)</span><br></pre></td></tr></table></figure><p>在anolis 系统上:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">[root@anolis ~]# cat /etc/os-release</span><br><span class="line">NAME=&quot;Anolis OS&quot;</span><br><span class="line">VERSION=&quot;8.2&quot;</span><br><span class="line">ID=&quot;anolis&quot;</span><br><span class="line">ID_LIKE=&quot;rhel fedora centos&quot;</span><br><span class="line">VERSION_ID=&quot;8.2&quot;</span><br><span class="line">PLATFORM_ID=&quot;platform:an8&quot;</span><br><span class="line">PRETTY_NAME=&quot;Anolis OS 8.2&quot;</span><br><span class="line">ANSI_COLOR=&quot;0;31&quot;</span><br><span class="line">HOME_URL=&quot;https://openanolis.org/&quot;</span><br></pre></td></tr></table></figure><p>​</p><p>其他系统, 如Debian9 系统显示如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">root@ob001:~# cat /etc/os-release</span><br><span class="line">PRETTY_NAME=&quot;Debian GNU/Linux 9 (stretch)&quot;</span><br><span class="line">NAME=&quot;Debian GNU/Linux&quot;</span><br><span class="line">VERSION_ID=&quot;9&quot;</span><br><span class="line">VERSION=&quot;9 (stretch)&quot;</span><br><span class="line">VERSION_CODENAME=stretch</span><br><span class="line">ID=debian</span><br><span class="line">HOME_URL=&quot;https://www.debian.org/&quot;</span><br><span class="line">SUPPORT_URL=&quot;https://www.debian.org/support&quot;</span><br><span class="line">BUG_REPORT_URL=&quot;https://bugs.debian.org/&quot;</span><br></pre></td></tr></table></figure><p>在unbutu 系统上:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">NAME=&quot;Ubuntu&quot;</span><br><span class="line">VERSION=&quot;20.04.2 LTS (Focal Fossa)&quot;</span><br><span class="line">ID=ubuntu</span><br><span class="line">ID_LIKE=debian</span><br><span class="line">PRETTY_NAME=&quot;Ubuntu 20.04.2 LTS&quot;</span><br><span class="line">VERSION_ID=&quot;20.04&quot;</span><br><span class="line">HOME_URL=&quot;https://www.ubuntu.com/&quot;</span><br><span class="line">SUPPORT_URL=&quot;https://help.ubuntu.com/&quot;</span><br><span class="line">BUG_REPORT_URL=&quot;https://bugs.launchpad.net/ubuntu/&quot;</span><br><span class="line">PRIVACY_POLICY_URL=&quot;https://www.ubuntu.com/legal/terms-and-policies/privacy-policy&quot;</span><br><span class="line">VERSION_CODENAME=focal</span><br><span class="line">UBUNTU_CODENAME=focal</span><br></pre></td></tr></table></figure><p>对于一些系统, 如Ubuntu&#x2F;Debian, 需要安装yum:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">sudo apt-get update</span><br><span class="line">sudo apt-get install build-essential </span><br><span class="line">sudo apt-get install yum -y</span><br><span class="line">sudo apt install yum-utils -y</span><br><span class="line">sudo ln -s /bin/bash /bin/sh</span><br><span class="line">apt-get install alien -y</span><br><span class="line">apt-get install rpm</span><br></pre></td></tr></table></figure><pre><code>3. 查看内核版本, 要求操作系统3.10.0 及以上</code></pre><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root@centos-01 /root]#uname -r </span><br><span class="line">3.10.0-327.el7.x86_64</span><br></pre></td></tr></table></figure><h3 id="检查内存"><a href="#检查内存" class="headerlink" title="检查内存"></a>检查内存</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">free -g</span><br></pre></td></tr></table></figure><p>企业级应用最低要求64G,  推荐256G 及以上<br>如果free -g<br>​<img data-src="/img/ob/install3.png"  alt="OceanBase离线安装"><br>显示的free列的内存小于配置文件中的memory_limit配置, 需要清理缓存或者修改配置memory_limit, 将memory_limit修改小于free 列的值. 清理缓存操作如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># echo 3 &gt; /proc/sys/vm/drop_caches</span><br></pre></td></tr></table></figure><p>​</p><h2 id="检查磁盘"><a href="#检查磁盘" class="headerlink" title="检查磁盘"></a>检查磁盘</h2><p>​</p><p>确保配置文件中, <code>data_dir, redo_dir, home_path</code>,  对应的磁盘已经完成挂载,   data_dir和redo_dir 对应目录为空, data_dir 对应目录的磁盘已经使用率必须低于4%.<br>​<img data-src="/img/ob/install4.png"  alt="OceanBase离线安装"></p><h2 id="检查网卡名称"><a href="#检查网卡名称" class="headerlink" title="检查网卡名称"></a>检查网卡名称</h2><p>​<img data-src="/img/ob/install5.png"  alt="OceanBase离线安装"><br>配置文件中, 有一个配置项”devname” 需要指定网卡. 在启动 OBServer 服务时，需要通过“-i”参数指定网卡，服务器有 可能有多个网卡以及多个 IP，OBServer 之间通信依赖指定的网卡和 IP。可以通过 ifconfig 命令查看网卡名称 (需要先安装 net-tools 依赖 包)，确保存在有效的网卡即可。<br>在本例中:<br>​<img data-src="/img/ob/install6.png"  alt="OceanBase离线安装"></p><h2 id="配置limits-conf"><a href="#配置limits-conf" class="headerlink" title="配置limits.conf"></a>配置limits.conf</h2><p>ulimit 用于限制 shell 启动进程所占用的资源。个人测试使用,可以不用设置, 但企业用户必须设置.<br>有两种方法可以修 改资源限制，一种是通过启动时 session 级别指定，另外一种是修改 &#x2F;etc&#x2F;security&#x2F;limits.conf 配置文件，全局生效。<br>OBServer 进程涉及的几个限制包括线程最大栈空间大小(stack)， 最大文件句柄数(open files)，core 文件大小(core file size)。<br>如下在启动 OBServer 进程时，session 级别设置最大栈空间大小 为 unlimited，最大文件句柄数为 655350，core 文件大小为 unlimited </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">$vi /etc/security/limits.conf 添加</span><br><span class="line">root soft nofile 655350</span><br><span class="line">root hard nofile 655350</span><br><span class="line">* soft nofile 655350</span><br><span class="line">* hard nofile 655350</span><br><span class="line">* soft stack 20480</span><br><span class="line">* hard stack 20480</span><br><span class="line">* soft nproc 655360</span><br><span class="line">* hard nproc 655360</span><br><span class="line">* soft core unlimited</span><br><span class="line">* hard core unlimited</span><br></pre></td></tr></table></figure><pre><code>退出当前session, 重新登录</code></pre><p>​</p><p>检查配置是否生效</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">ulimit -a</span><br><span class="line"># 执行该命令，资源限制详情如下 (blocks, -c) 0</span><br><span class="line">core file size</span><br><span class="line">data seg size scheduling priority file size</span><br><span class="line">pending signals max locked memory</span><br><span class="line">(kbytes, -d) unlimited (-e) 0</span><br><span class="line">(blocks, -f) unlimited (-i) 772861</span><br><span class="line">(kbytes, -l) 64</span><br><span class="line">max memory size</span><br><span class="line">open files</span><br><span class="line">pipe size</span><br><span class="line">POSIX message queues real-time priority</span><br><span class="line">stack size</span><br><span class="line">cpu time</span><br><span class="line">max user processes virtual memory file locks</span><br><span class="line">(kbytes, -m) unlimited (-n) 1024</span><br><span class="line">(512 bytes, -p) 8</span><br><span class="line">(bytes, -q) 819200</span><br><span class="line">(-r) 0 (kbytes, -s) 8192</span><br><span class="line">(seconds, -t) unlimited (-u) 655360</span><br><span class="line">(kbytes, -v) unlimited (-x) unlimited</span><br></pre></td></tr></table></figure><p>​</p><h2 id="配置“sysctl-conf”文件"><a href="#配置“sysctl-conf”文件" class="headerlink" title="配置“sysctl.conf”文件"></a>配置“sysctl.conf”文件</h2><p>为保证 OceanBase 正常运行，请在安装 OceanBase 前修改所有物理机的“&#x2F;etc&#x2F;sysctl.conf”配置(用以提高 Linux 的系统性能)。</p><p>有一些参数, 操作系统已经提前设置了</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"># for oceanbase</span><br><span class="line">## 修改内核异步 I/O 限制</span><br><span class="line">fs.aio-max-nr=1048576</span><br><span class="line"></span><br><span class="line">## 网络优化</span><br><span class="line">net.core.somaxconn = 2048</span><br><span class="line">net.core.netdev_max_backlog = 10000 </span><br><span class="line">net.core.rmem_default = 16777216 </span><br><span class="line">net.core.wmem_default = 16777216 </span><br><span class="line">net.core.rmem_max = 16777216 </span><br><span class="line">net.core.wmem_max = 16777216</span><br><span class="line"></span><br><span class="line">net.ipv4.ip_local_port_range = 3500 65535 </span><br><span class="line">net.ipv4.ip_forward = 0 </span><br><span class="line">net.ipv4.conf.default.rp_filter = 1 </span><br><span class="line">net.ipv4.conf.default.accept_source_route = 0 </span><br><span class="line">net.ipv4.tcp_syncookies = 0 </span><br><span class="line">net.ipv4.tcp_rmem = 4096 87380 16777216 </span><br><span class="line">net.ipv4.tcp_wmem = 4096 65536 16777216 </span><br><span class="line">net.ipv4.tcp_max_syn_backlog = 16384 </span><br><span class="line">net.ipv4.tcp_fin_timeout = 15 </span><br><span class="line">net.ipv4.tcp_max_syn_backlog = 16384 </span><br><span class="line">net.ipv4.tcp_tw_reuse = 1 </span><br><span class="line">net.ipv4.tcp_tw_recycle = 1 </span><br><span class="line">net.ipv4.tcp_slow_start_after_idle=0</span><br><span class="line"></span><br><span class="line">vm.swappiness = 0</span><br><span class="line">vm.min_free_kbytes = 2097152</span><br><span class="line"></span><br><span class="line"># 此处为oceanbase 的data 目录</span><br><span class="line">kernel.core_pattern = /data/core-%e-%p-%t </span><br></pre></td></tr></table></figure><p>其中, “kernel.core_pattern &#x3D; &#x2F;data&#x2F;core-%e-%p-%t ”,  &#x2F;data 为 oceanbase的data 目录,  另外如果是个人测试, 也可以只设置 “fs.aio-max-nr&#x3D;1048576”<br>​</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"># 配置生效</span><br><span class="line">sysctl -p</span><br></pre></td></tr></table></figure><p>​</p><h2 id="关闭防火墙和-SELinux"><a href="#关闭防火墙和-SELinux" class="headerlink" title="关闭防火墙和 SELinux"></a>关闭防火墙和 SELinux</h2><p>个人测试可以不用设置, 但企业用户建议进行设置</p><h3 id="firewalld关闭"><a href="#firewalld关闭" class="headerlink" title="firewalld关闭"></a>firewalld关闭</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">#依次执行这 3 条命令 </span><br><span class="line">systemctl disable firewalld </span><br><span class="line">systemctl stop firewalld</span><br><span class="line">systemctl status firewalld</span><br></pre></td></tr></table></figure><h3 id="selinux关闭"><a href="#selinux关闭" class="headerlink" title="selinux关闭"></a>selinux关闭</h3><p>vi &#x2F;etc&#x2F;selinux&#x2F;linux</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">SELINUX=disabled</span><br></pre></td></tr></table></figure><p>执行该命令</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">setenforce 0</span><br><span class="line">#查看配置已生效</span><br><span class="line">cat /etc/selinux/config</span><br></pre></td></tr></table></figure><h2 id="设置时钟同步"><a href="#设置时钟同步" class="headerlink" title="设置时钟同步"></a>设置时钟同步</h2><p>当下面任一状况时, 可以跳过设置时钟同步:</p><ol><li>若 NTP 时钟已经处于同步状态</li><li>部署为单机版</li><li>个人测试</li></ol><p>​</p><p>OceanBase 集群中各服务器的时间需保持一致，否则会导致 OceanBase 集群无法启动，运行时也会出现故障。对于企业用户来说, 时钟同步是<strong>非常非常重要</strong>, 物理机与时钟服务器的误差在 50ms 以下可认为时钟是同步状态 , 最大容忍误差不能超过200ms. 当超过200ms, 会出现无主状况, 恢复时钟同步后, 重启observer, 可以恢复状态. </p><h3 id="检查时钟同步"><a href="#检查时钟同步" class="headerlink" title="检查时钟同步"></a>检查时钟同步</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo clockdiff  $IP</span><br></pre></td></tr></table></figure><h3 id="配置时钟同步"><a href="#配置时钟同步" class="headerlink" title="配置时钟同步"></a>配置时钟同步</h3><p><span class="exturl" data-url="aHR0cHM6Ly9vcGVuLm9jZWFuYmFzZS5jb20vZG9jcy9jb21tdW5pdHkvb2NlYW5iYXNlLWRhdGFiYXNlL1YzLjEuMC9vcHRpb25hbC1jb25maWd1cmluZy1jbG9jay1zb3VyY2Vz">https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.0/optional-configuring-clock-sources<i class="fa fa-external-link-alt"></i></span><br>​</p><h1 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h1><h2 id="安装包组件"><a href="#安装包组件" class="headerlink" title="安装包组件"></a>安装包组件</h2><p>从<span class="exturl" data-url="aHR0cHM6Ly9vcGVuLm9jZWFuYmFzZS5jb20vc29mdHdhcmVDZW50ZXIvY29tbXVuaXR5">https://open.oceanbase.com/softwareCenter/community<i class="fa fa-external-link-alt"></i></span> 上下载所有的安装包, 本文展示的安装包的版本,可能已经过期, 麻烦从开源OceanBase官网下载最新版本的安装包.<br>​<img data-src="/img/ob/install7.png"  alt="OceanBase离线安装"><br>如您的机器可以访问公网，并能够添加三方 YUM 软件源，您可以运行以下命令，使用 OceanBase 的官方软件源安装 OBD：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">sudo yum install -y yum-utils</span><br><span class="line">sudo yum-config-manager --add-repo https://mirrors.aliyun.com/oceanbase/OceanBase.repo</span><br><span class="line">sudo yum install -y ob-deploy</span><br></pre></td></tr></table></figure><p>​</p><p>将所有的软件包, scp至 主控机器上</p><p>​</p><h2 id="安装OBD"><a href="#安装OBD" class="headerlink" title="安装OBD"></a>安装OBD</h2><p>当前使用root 用户, 当前操作只在主控机器上进行操作</p><h3 id="在线安装"><a href="#在线安装" class="headerlink" title="在线安装"></a>在线安装</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install -y ob-deploy</span><br></pre></td></tr></table></figure><h3 id="本地安装"><a href="#本地安装" class="headerlink" title="本地安装"></a>本地安装</h3><p>centos或redhat</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install ob-deploy-1.1.0-1.el7.x86_64.rpm</span><br></pre></td></tr></table></figure><p>Ubuntu&#x2F;Debian</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">alien -i ob-deploy-1.1.0-1.el7.x86_64.rpm</span><br></pre></td></tr></table></figure><h2 id="安装OBLibs"><a href="#安装OBLibs" class="headerlink" title="安装OBLibs"></a>安装OBLibs</h2><p>当前使用root 用户, 需要在每台机器上执行,  </p><h3 id="在线安装-1"><a href="#在线安装-1" class="headerlink" title="在线安装"></a>在线安装</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install -y oceanbase-ce-libs</span><br></pre></td></tr></table></figure><h3 id="本地安装-1"><a href="#本地安装-1" class="headerlink" title="本地安装"></a>本地安装</h3><p>先将oceanbase-ce-libs-3.1.0-3.el7.x86_64.rpm 拷贝到每台机器下<br>​</p><p>centos或redhat或anolis</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install oceanbase-ce-libs-3.1.0-3.el7.x86_64.rpm</span><br></pre></td></tr></table></figure><p>Ubuntu&#x2F;Debian</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">alien -i oceanbase-ce-libs-3.1.0-3.el7.x86_64.rpm</span><br></pre></td></tr></table></figure><h2 id="安装OBServer-OBProxy"><a href="#安装OBServer-OBProxy" class="headerlink" title="安装OBServer &amp; OBProxy"></a>安装OBServer &amp; OBProxy</h2><p>切换到admin 用户下<br>​</p><h3 id="将OceanBase数据库的离线软件包加入本地镜像"><a href="#将OceanBase数据库的离线软件包加入本地镜像" class="headerlink" title="将OceanBase数据库的离线软件包加入本地镜像"></a>将OceanBase数据库的离线软件包加入本地镜像</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">admin@obdriver:/data/rpm$ obd mirror clone *.rpm</span><br><span class="line">name: libobclient</span><br><span class="line">version: 2.0.0</span><br><span class="line">release:2.el7</span><br><span class="line">arch: x86_64</span><br><span class="line">md5: f73cae67e2ff5be0682ac2803aba33a7ed26430e</span><br><span class="line">add libobclient-2.0.0-2.el7.x86_64.rpm to local mirror</span><br><span class="line">name: obclient</span><br><span class="line">version: 2.0.0</span><br><span class="line">release:2.el7</span><br><span class="line">arch: x86_64</span><br><span class="line">md5: 1d2c3ee31f40b9d2fbf97f653f549d896b7e7060</span><br><span class="line">add obclient-2.0.0-2.el7.x86_64.rpm to local mirror</span><br><span class="line">name: ob-deploy</span><br><span class="line">version: 1.1.0</span><br><span class="line">release:1.el7</span><br><span class="line">arch: x86_64</span><br><span class="line">md5: c01dbbebc7f44b700833ce6846df09f20033675c</span><br><span class="line">add ob-deploy-1.1.0-1.el7.x86_64.rpm to local mirror</span><br><span class="line">name: obproxy</span><br><span class="line">version: 3.1.0</span><br><span class="line">release:1.el7</span><br><span class="line">arch: x86_64</span><br><span class="line">md5: 0b17cf0459a3b53c5a2febb6572894d183154c64</span><br><span class="line">add obproxy-3.1.0-1.el7.x86_64.rpm to local mirror</span><br><span class="line">name: oceanbase-ce</span><br><span class="line">version: 3.1.0</span><br><span class="line">release:3.el7</span><br><span class="line">arch: x86_64</span><br><span class="line">md5: b73bcd531bdf3f087391991b290ff2cbcdaa0dc9</span><br><span class="line">add oceanbase-ce-3.1.0-3.el7.x86_64.rpm to local mirror</span><br><span class="line">name: oceanbase-ce-libs</span><br><span class="line">version: 3.1.0</span><br><span class="line">release:3.el7</span><br><span class="line">arch: x86_64</span><br><span class="line">md5: 528144ec7ff0194a8b326491a396b8f5c87b1eaa</span><br><span class="line">add oceanbase-ce-libs-3.1.0-3.el7.x86_64.rpm to local mirror</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">admin@obdriver:~$ obd mirror list local</span><br><span class="line">+-------------------------------------------------------------------------------------------+</span><br><span class="line">|                                     local Package List                                    |</span><br><span class="line">+-------------------+---------+---------+--------+------------------------------------------+</span><br><span class="line">| name              | version | release | arch   | md5                                      |</span><br><span class="line">+-------------------+---------+---------+--------+------------------------------------------+</span><br><span class="line">| libobclient       | 2.0.0   | 2.el7   | x86_64 | f73cae67e2ff5be0682ac2803aba33a7ed26430e |</span><br><span class="line">| obclient          | 2.0.0   | 2.el7   | x86_64 | 1d2c3ee31f40b9d2fbf97f653f549d896b7e7060 |</span><br><span class="line">| ob-deploy         | 1.1.0   | 1.el7   | x86_64 | c01dbbebc7f44b700833ce6846df09f20033675c |</span><br><span class="line">| obproxy           | 3.1.0   | 1.el7   | x86_64 | 0b17cf0459a3b53c5a2febb6572894d183154c64 |</span><br><span class="line">| oceanbase-ce      | 3.1.0   | 3.el7   | x86_64 | b73bcd531bdf3f087391991b290ff2cbcdaa0dc9 |</span><br><span class="line">| oceanbase-ce-libs | 3.1.0   | 3.el7   | x86_64 | 528144ec7ff0194a8b326491a396b8f5c87b1eaa |</span><br><span class="line">+-------------------+---------+---------+--------+------------------------------------------+</span><br></pre></td></tr></table></figure><h3 id="下载配置文件"><a href="#下载配置文件" class="headerlink" title="下载配置文件"></a>下载配置文件</h3><p>到<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vYmRlcGxveS90cmVlL21hc3Rlci9leGFtcGxlL2F1dG9kZXBsb3k=">https://github.com/oceanbase/obdeploy/tree/master/example/autodeploy<i class="fa fa-external-link-alt"></i></span> 上将所有配置文件下载下来<br>当前有几个配置文件:<br>​</p><ul><li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vYmRlcGxveS9ibG9iL21hc3Rlci9leGFtcGxlL2F1dG9kZXBsb3kvZGlzdHJpYnV0ZWQtZXhhbXBsZS55YW1s">distributed-example.yaml<i class="fa fa-external-link-alt"></i></span>    :  分布式example</li><li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vYmRlcGxveS9ibG9iL21hc3Rlci9leGFtcGxlL2F1dG9kZXBsb3kvZGlzdHJpYnV0ZWQtd2l0aC1vYnByb3h5LWV4YW1wbGUueWFtbA==">distributed-with-obproxy-example.yaml<i class="fa fa-external-link-alt"></i></span>  : 分布式带obproxy的example</li><li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vYmRlcGxveS9ibG9iL21hc3Rlci9leGFtcGxlL2F1dG9kZXBsb3kvc2luZ2xlLWV4YW1wbGUueWFtbA==">single-example.yaml<i class="fa fa-external-link-alt"></i></span>  : 单机example</li><li><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vYmRlcGxveS9ibG9iL21hc3Rlci9leGFtcGxlL2F1dG9kZXBsb3kvc2luZ2xlLXdpdGgtb2Jwcm94eS1leGFtcGxlLnlhbWw=">single-with-obproxy-example.yaml<i class="fa fa-external-link-alt"></i></span>  : 单机example</li></ul><p>​</p><p>在本例中, 我们使用分布式example, 我们将分布式配置文件 scp到 主控机器上. </p><h3 id="修改配置文件"><a href="#修改配置文件" class="headerlink" title="修改配置文件"></a>修改配置文件</h3><p>本例中, 以distributed-with-obproxy-example.yaml为例</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line">## Only need to configure when remote login is required</span><br><span class="line"># user:</span><br><span class="line">#   username: your username</span><br><span class="line">#   password: your password if need</span><br><span class="line">#   key_file: your ssh-key file path if need</span><br><span class="line">#   port: your ssh port, default 22</span><br><span class="line">#   timeout: ssh connection timeout (second), default 30</span><br><span class="line">oceanbase-ce:</span><br><span class="line">  servers:</span><br><span class="line">    - name: z1</span><br><span class="line">      # Please don&#x27;t use hostname, only IP can be supported</span><br><span class="line">      ip: 192.168.1.2</span><br><span class="line">    - name: z2</span><br><span class="line">      ip: 192.168.1.3</span><br><span class="line">    - name: z3</span><br><span class="line">      ip: 192.168.1.4</span><br><span class="line">  global:</span><br><span class="line">    # The working directory for OceanBase Database. OceanBase Database is started under this directory. This is a required field.</span><br><span class="line">    home_path: /root/observer</span><br><span class="line">    # The directory for data storage. The default value is $home_path/store.</span><br><span class="line">    # data_dir: /data</span><br><span class="line">    # The directory for clog, ilog, and slog. The default value is the same as the data_dir value.</span><br><span class="line">    # redo_dir: /redo</span><br><span class="line">    # External port for OceanBase Database. The default value is 2881.</span><br><span class="line">    # mysql_port: 2881</span><br><span class="line">    # Internal port for OceanBase Database. The default value is 2882.</span><br><span class="line">    # rpc_port: 2882</span><br><span class="line">    # Defines the zone for an observer. The default value is zone1.</span><br><span class="line">    # zone: zone1</span><br><span class="line">    # The maximum running memory for an observer. When ignored, autodeploy calculates this value based on the current server available resource.</span><br><span class="line">    # memory_limit: 58G</span><br><span class="line">    # The percentage of the maximum available memory to the total memory. This value takes effect only when memory_limit is 0. The default value is 80.</span><br><span class="line">    # memory_limit_percentage: 80 </span><br><span class="line">    # The reserved system memory. system_memory is reserved for general tenants. The default value is 30G. Autodeploy calculates this value based on the current server available resource.</span><br><span class="line">    # system_memory: 22G</span><br><span class="line">    # The size of a data file. When ignored, autodeploy calculates this value based on the current server available resource.</span><br><span class="line">    # datafile_size: 200G</span><br><span class="line">    # The percentage of the data_dir space to the total disk space. This value takes effect only when datafile_size is 0. The default value is 90.</span><br><span class="line">    # datafile_disk_percentage: 90</span><br><span class="line">    # System log level. The default value is INFO.</span><br><span class="line">    # syslog_level: INFO</span><br><span class="line">    # Print system logs whose levels are higher than WARNING to a separate log file. The default value is true. The default value for autodeploy mode is false.</span><br><span class="line">    # enable_syslog_wf: false</span><br><span class="line">    # Enable auto system log recycling or not. The default value is false. The default value for autodeploy mode is on.</span><br><span class="line">    # enable_syslog_recycle: true</span><br><span class="line">    # The maximum number of reserved log files before enabling auto recycling. When set to 0, no logs are deleted. The default value for autodeploy mode is 4.</span><br><span class="line">    # max_syslog_file_count: 4</span><br><span class="line">    # Cluster name for OceanBase Database. The default value is obcluster. When you deploy OceanBase Database and obproxy, this value must be the same as the cluster_name for obproxy.</span><br><span class="line">    # appname: obcluster</span><br><span class="line">    # Password for root. The default value is empty.</span><br><span class="line">    # root_password:</span><br><span class="line">    # Password for proxyro. proxyro_password must be the same as observer_sys_password. The default value is empty.</span><br><span class="line">    # proxyro_password:</span><br><span class="line">  z1:</span><br><span class="line">    zone: zone1</span><br><span class="line">  z2:</span><br><span class="line">    zone: zone2</span><br><span class="line">  z3:</span><br><span class="line">    zone: zone3</span><br><span class="line">obproxy:</span><br><span class="line">  servers:</span><br><span class="line">    - 192.168.1.5</span><br><span class="line">  global:</span><br><span class="line">    # The working directory for obproxy. Obproxy is started under this directory. This is a required field.</span><br><span class="line">    home_path: /root/obproxy</span><br><span class="line">    # External port. The default value is 2883.</span><br><span class="line">    # listen_port: 2883</span><br><span class="line">    # The Prometheus port. The default value is 2884.</span><br><span class="line">    # prometheus_listen_port: 2884</span><br><span class="line">    # rs_list is the root server list for observers. The default root server is the first server in the zone.</span><br><span class="line">    # The format for rs_list is observer_ip:observer_mysql_port;observer_ip:observer_mysql_port.</span><br><span class="line">    # Ignore this value in autodeploy mode.</span><br><span class="line">    # rs_list: 127.0.0.1:2881</span><br><span class="line">    # Cluster name for the proxy OceanBase Database. The default value is obcluster. This value must be set to the same with the appname for OceanBase Database.</span><br><span class="line">    # cluster_name: obcluster</span><br><span class="line">    # Password for obproxy system tenant. The default value is empty.</span><br><span class="line">    # obproxy_sys_password:</span><br><span class="line">    # Password for proxyro. proxyro_password must be the same with proxyro_password. The default value is empty.</span><br><span class="line">    # observer_sys_password:</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">## Only need to configure when remote login is required</span><br><span class="line"># user:</span><br><span class="line">#   username: your username</span><br><span class="line">#   password: your password if need</span><br><span class="line">#   key_file: your ssh-key file path if need</span><br><span class="line">#   port: your ssh port, default 22</span><br><span class="line">#   timeout: ssh connection timeout (second), default 30</span><br></pre></td></tr></table></figure><p>修改用户名和密码<br>​</p><p>通常这几个变量需要人肉设置一下, 每台机器的ip,  home_path, data_dir, redo_dir,  在本例中, 分别修改为&#x2F;home&#x2F;admin&#x2F;oceanbase&#x2F;ob, &#x2F;data&#x2F;ob, &#x2F;redo&#x2F;ob,  分别为之前挂载的磁盘. </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">oceanbase-ce:</span><br><span class="line">  servers:</span><br><span class="line">    - name: z1</span><br><span class="line">      # Please don&#x27;t use hostname, only IP can be supported</span><br><span class="line">      ip: 172.30.62.200</span><br><span class="line">    - name: z2</span><br><span class="line">      ip: 172.30.62.201</span><br><span class="line">    - name: z3</span><br><span class="line">      ip: 172.30.62.202</span><br><span class="line">  global:</span><br><span class="line">    # The working directory for OceanBase Database. OceanBase Database is started under this directory. This is a required field.</span><br><span class="line">    home_path: /home/admin/oceanbase/ob</span><br><span class="line">    # The directory for data storage. The default value is $home_path/store.</span><br><span class="line">    data_dir: /data/ob</span><br><span class="line">    # The directory for clog, ilog, and slog. The default value is the same as the data_dir value.</span><br><span class="line">    redo_dir: /redo/ob</span><br></pre></td></tr></table></figure><p>配置proxy,  修改ip 和home_path</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">obproxy:</span><br><span class="line">  servers:</span><br><span class="line">    - 172.30.62.203</span><br><span class="line">  global:</span><br><span class="line">    # The working directory for obproxy. Obproxy is started under this directory. This is a required field.</span><br><span class="line">    home_path: /home/admin/oceanbase</span><br></pre></td></tr></table></figure><p>另外推荐一个网站<span class="exturl" data-url="aHR0cHM6Ly93d3cuYmVqc29uLmNvbS92YWxpZGF0b3JzL3lhbWxfZWRpdG9yLw==">https://www.bejson.com/validators/yaml_editor&#x2F;<i class="fa fa-external-link-alt"></i></span>, 可以对配置文件进行yaml 检测, 很多时候, 配置文件 多一个空格, 少一个空格, 极难发现,<br>​</p><p>​</p><h3 id="开始安装"><a href="#开始安装" class="headerlink" title="开始安装"></a>开始安装</h3><p>对于离线安装, 需要执行一步操作, 当连不上服务器, 需要把远程的repo 配置给删掉, 避免浪费时间消耗在连接远程的repo 之上. </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rm -fr ~/.obd/mirror/remote/*.repo</span><br></pre></td></tr></table></figure><p>开始安装</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">admin@obdriver:~$ obd cluster autodeploy obtest -c distributed-with-obproxy-example.yaml </span><br></pre></td></tr></table></figure><p>检查安装是否成功</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">admin@obdriver:~$ obd cluster list</span><br><span class="line">+------------------------------------------------------------+</span><br><span class="line">|                        Cluster List                        |</span><br><span class="line">+--------+---------------------------------+-----------------+</span><br><span class="line">| Name   | Configuration Path              | Status (Cached) |</span><br><span class="line">+--------+---------------------------------+-----------------+</span><br><span class="line">| obtest | /home/admin/.obd/cluster/obtest | running         |</span><br><span class="line">+--------+---------------------------------+-----------------+</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">admin@obdriver:~$ obd cluster display obtest</span><br><span class="line">Get local repositories and plugins ok</span><br><span class="line">Open ssh connection ok</span><br><span class="line">Cluster status check ok</span><br><span class="line">Connect to observer ok</span><br><span class="line">Wait for observer init ok</span><br><span class="line">+-------------------------------------------------+</span><br><span class="line">|                     observer                    |</span><br><span class="line">+---------------+---------+------+-------+--------+</span><br><span class="line">| ip            | version | port | zone  | status |</span><br><span class="line">+---------------+---------+------+-------+--------+</span><br><span class="line">| 172.30.62.200 | 3.1.0   | 2881 | zone1 | active |</span><br><span class="line">| 172.30.62.201 | 3.1.0   | 2881 | zone2 | active |</span><br><span class="line">| 172.30.62.202 | 3.1.0   | 2881 | zone3 | active |</span><br><span class="line">+---------------+---------+------+-------+--------+</span><br><span class="line"></span><br><span class="line">Connect to obproxy ok</span><br><span class="line">+-------------------------------------------------+</span><br><span class="line">|                     obproxy                     |</span><br><span class="line">+---------------+------+-----------------+--------+</span><br><span class="line">| ip            | port | prometheus_port | status |</span><br><span class="line">+---------------+------+-----------------+--------+</span><br><span class="line">| 172.30.62.203 | 2883 | 2884            | active |</span><br><span class="line">+---------------+------+-----------------+--------+</span><br></pre></td></tr></table></figure><h2 id="修改配置"><a href="#修改配置" class="headerlink" title="修改配置"></a>修改配置</h2><p>OceanBase 数据库有数百个配置项，有些配置是耦合的，在您熟悉 OceanBase 数据库之前，不建议您修改示例配件文件中的配置。此处示例用来说明如何修改配置，并使之生效。<br>​</p><p>所有的参数介绍请参考</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">https://github.com/oceanbase/obdeploy/blob/master/plugins/oceanbase/3.1.0/parameter.yaml</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"># 使用 edit-config 命令进入编辑模式，修改集群配置</span><br><span class="line">obd cluster edit-config lo</span><br><span class="line"># 修改 sys_bkgd_migration_retry_num 为 5</span><br><span class="line"># 注意 sys_bkgd_migration_retry_num 值最小为 3</span><br><span class="line"># 保存并退出后，OBD 会告知您如何使得此次改动生效</span><br><span class="line"># 此配置项仅需要 reload 即可生效</span><br><span class="line">obd cluster reload lo</span><br></pre></td></tr></table></figure><h1 id="验证​"><a href="#验证​" class="headerlink" title="验证​"></a>验证​</h1><h2 id="安装obclient"><a href="#安装obclient" class="headerlink" title="安装obclient"></a>安装obclient</h2><p>通常在主控机器上安装obclient,  需要切换到root 账号</p><h3 id="在线安装-2"><a href="#在线安装-2" class="headerlink" title="在线安装"></a>在线安装</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">yum install -y libobclient</span><br><span class="line">yum install -y obclient</span><br></pre></td></tr></table></figure><h3 id="本地安装-2"><a href="#本地安装-2" class="headerlink" title="本地安装"></a>本地安装</h3><p>​</p><p>centos或redhat或anolis</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">yum install libobclient-2.0.0-2.el7.x86_64.rpm</span><br><span class="line">yum install obclient-2.0.0-2.el7.x86_64.rpm</span><br></pre></td></tr></table></figure><p>Ubuntu&#x2F;Debian</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">alien -i libobclient-2.0.0-2.el7.x86_64.rpm</span><br><span class="line">alien -i obclient-2.0.0-2.el7.x86_64.rpm</span><br></pre></td></tr></table></figure><p>在debian 下需要把路径设置到系统环境变量中</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export PATH=/app/mariadb/bin:$PATH</span><br></pre></td></tr></table></figure><p>​</p><p>在ubuntu下需要把路径设置到系统环境变量中</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">export PATH=/u01/obclient/bin:$PATH</span><br></pre></td></tr></table></figure><p>​</p><p>​</p><h2 id="安装mysql-开发包"><a href="#安装mysql-开发包" class="headerlink" title="安装mysql 开发包"></a>安装mysql 开发包</h2><p>如果需要运行sysbench, 或者tpch 等程序, 需要安装mysql 开发包</p><p>​</p><p>centos或redhat或anolis</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">yum install mariadb</span><br><span class="line">yum install mariadb-libs</span><br><span class="line">yum install mariadb-devel</span><br></pre></td></tr></table></figure><p>Ubuntu</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install mariadb-server</span><br></pre></td></tr></table></figure><p>Debian</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install mysql-server mysql-client libmariadbd18 libmariadbd-dev</span><br></pre></td></tr></table></figure><h2 id="检查租户"><a href="#检查租户" class="headerlink" title="检查租户"></a>检查租户</h2><p>使用oceanbase, 需要创建租户, 用户真正应用必须运行在租户下<br>创建租户有2种方式:<br>可以使用obd 来创建租户, 使用obd 创建租户时, 会把所有的资源</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">obd cluster tenant create $&#123;cluster_name&#125; -n $&#123;tenant_name&#125;</span><br></pre></td></tr></table></figure><p>​</p><p>创建租户, 请参考<br><span class="exturl" data-url="aHR0cHM6Ly9vcGVuLm9jZWFuYmFzZS5jb20vZG9jcy9jb21tdW5pdHkvb2NlYW5iYXNlLWRhdGFiYXNlL1YzLjEuMC9jcmVhdGUtYS11c2VyLXRlbmFudA==">https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.0/create-a-user-tenant<i class="fa fa-external-link-alt"></i></span><br>​</p><p>在本例中:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">admin@obdriver:~$ mysql -h$&#123;obproxy_ip&#125; -P$&#123;obproxy_port&#125; -uroot</span><br><span class="line">Welcome to the MariaDB monitor.  Commands end with ; or \g.</span><br><span class="line">Your MySQL connection id is 2</span><br><span class="line">Server version: 5.6.25 OceanBase 3.1.0 (r3-b20901e8c84d3ea774beeaca963c67d7802e4b4e) (Built Aug 10 2021 07:51:04)</span><br><span class="line"></span><br><span class="line">Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.</span><br><span class="line"></span><br><span class="line">Type &#x27;help;&#x27; or &#x27;\h&#x27; for help. Type &#x27;\c&#x27; to clear the current input statement.</span><br><span class="line"></span><br><span class="line">MySQL [(none)]&gt; use oceanbase;</span><br><span class="line">Reading table information for completion of table and column names</span><br><span class="line">You can turn off this feature to get a quicker startup with -A</span><br><span class="line"></span><br><span class="line">Database changed</span><br><span class="line">MySQL [oceanbase]&gt; select * from gv$tenant;</span><br><span class="line">+-----------+-------------+-------------------+-------------------+----------------+---------------+-----------+---------------------------------------------+</span><br><span class="line">| tenant_id | tenant_name | zone_list         | primary_zone      | collation_type | info          | read_only | locality                                    |</span><br><span class="line">+-----------+-------------+-------------------+-------------------+----------------+---------------+-----------+---------------------------------------------+</span><br><span class="line">|         1 | sys         | zone1;zone2;zone3 | zone1;zone2,zone3 |              0 | system tenant |         0 | FULL&#123;1&#125;@zone1, FULL&#123;1&#125;@zone2, FULL&#123;1&#125;@zone3 |</span><br><span class="line">|      1001 | mytest      | zone1;zone2;zone3 | RANDOM            |              0 |               |         0 | FULL&#123;1&#125;@zone1, FULL&#123;1&#125;@zone2, FULL&#123;1&#125;@zone3 |</span><br><span class="line">+-----------+-------------+-------------------+-------------------+----------------+---------------+-----------+---------------------------------------------+</span><br><span class="line">2 rows in set (0.00 sec)</span><br><span class="line"></span><br><span class="line">MySQL [(none)]&gt;</span><br></pre></td></tr></table></figure><p>​</p><h1 id="企业用户最佳实践"><a href="#企业用户最佳实践" class="headerlink" title="企业用户最佳实践"></a>企业用户最佳实践</h1><h2 id="磁盘"><a href="#磁盘" class="headerlink" title="磁盘"></a>磁盘</h2><p>ob  运行日志, 事务日志, 数据文件 必须独立开, 如果没有3块盘支持, 则可以一块盘分3块分区<br>​</p><h2 id="时钟依赖"><a href="#时钟依赖" class="headerlink" title="时钟依赖"></a>时钟依赖</h2><p>OceanBase 集群中各服务器的时间需保持一致，否则会导致 OceanBase 集群无法启动，运行时也会出现故障。对于企业用户来说, 时钟同步是<strong>非常非常重要</strong>, 物理机与时钟服务器的误差在 50ms 以下可认为时钟是同步状态 , 最大容忍误差不能超过200ms. 当超过200ms, 会出现无主状况, 恢复时钟同步后, 重启observer, 可以恢复状态.<br>​</p><h2 id="网络时延"><a href="#网络时延" class="headerlink" title="网络时延"></a>网络时延</h2><p>server间的网络时延不能超过200ms，否则同步会严重滞后, 并且可能影响选举<br>网卡设置<br>建议配置2块万兆网卡，bond模式取名bond0，mode1或mode4均可以，推荐使用mode4，如果是mode4，交换机需要配置802.3ad。网卡名建议使用eth0，eth1。建议使用network服务，不要使用NetworkManager。</p><h2 id="参数设置"><a href="#参数设置" class="headerlink" title="参数设置"></a>参数设置</h2><ul><li>当系统写入tps 过高时, 超过系统支撑能力时, 为防止系统停止响应</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">alter system set writing_throttling_trigger_percentage=75 tenant=all(或者具体tenantname);</span><br></pre></td></tr></table></figure><ul><li>除非业务应用有配置重连尝试，否则建议关闭轮转合并, 切主也不能保证不杀事务</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ALTER SYSTEM SET enable_merge_by_turn = &#x27;False&#x27;;</span><br></pre></td></tr></table></figure><ul><li>内存设置<ul><li>租户的cpu和内存规格比建议不低于1:4,否则容易oom； </li><li>普通租户最小内存规格暂定5G以上；</li><li>租户内存太小时建议调大ob_sql_work_area_percentage，默认值5%，租户内存小于10G建议配20%左右；</li><li>partition 个数限制, 单机建议不要超过10万个分区, 另外partition 数量会受内存限制,  每个副本预留内存为168KB，因此10000个副本至少需要预留1.68G内存，或者说1G的租户最多能建6k左右个partition，需要根据partition数的规划设置租户内存；另外单机. </li><li>物理内存使用限制，默认为80，memstore内存可用百分比，建议服务器内存256G以上配置调整为90，256G以下保持默认50</li></ul></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ALTER SYSTEM SET memstore_limit_percentage = &#x27;90&#x27;;</span><br></pre></td></tr></table></figure><p>​</p><ul><li>slow query阈值调整 trace_log_slow_query_watermark 默认100ms，可以根据业务特点调整。如果阈值设置的太小，打印大量trace日志会影响性能.MySQL  默认值是1s. 大查询时间为10s</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">ALTER SYSTEM SET trace_log_slow_query_watermark = &#x27;1s&#x27;;</span><br><span class="line">ALTER SYSTEM SET large_query_threshold = &#x27;10s&#x27;;</span><br></pre></td></tr></table></figure><p>​</p><ul><li>cpu 并发度调整</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">-- CPU并发度参数，建议配置为4，arm系统为2</span><br><span class="line">ALTER SYSTEM SET cpu_quota_concurrency = &#x27;4&#x27;;</span><br><span class="line"></span><br><span class="line">-- 资源软负载开关，控制资源均衡水位，默认为50%，即CPU内存使用超过50%就进行unit均衡，线上建议调整为100，达到手工控制unit分布的效果</span><br><span class="line">ALTER SYSTEM SET resource_soft_limit = &#x27;100&#x27;;</span><br></pre></td></tr></table></figure><ul><li>转储合并相关</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">-- 配置转储50次</span><br><span class="line">ALTER SYSTEM SET minor_freeze_times = 50;</span><br><span class="line"></span><br><span class="line">-- 转储触发水位百分比，建议256G以上配置调整为70，256G以下调整为60</span><br><span class="line">ALTER SYSTEM SET freeze_trigger_percentage = &#x27;60&#x27;;</span><br><span class="line"></span><br><span class="line">-- 数据拷贝并发为100</span><br><span class="line">ALTER SYSTEM SET data_copy_concurrency = 100;</span><br><span class="line">-- 服务器上数据传出并发为10</span><br><span class="line">ALTER SYSTEM SET server_data_copy_out_concurrency = 10;</span><br><span class="line">-- 服务器上数据传入并发为10</span><br><span class="line">ALTER SYSTEM SET server_data_copy_in_concurrency = 10;</span><br><span class="line"></span><br><span class="line">-- 转储预热时间，默认30s，设置了会延后转储释放的时间，改成0s</span><br><span class="line">ALTER SYSTEM SET minor_warm_up_duration_time = &#x27;0s&#x27;;</span><br><span class="line">-- 配置chunk内存大小(建议保持默认值0，ob自行分配)</span><br><span class="line">ALTER SYSTEM SET memory_chunk_cache_size = 0;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">-- 最大包括版本数量，影响磁盘可用空间，默认为2，将多保留一个版本的数据在数据盘中，需调整为1</span><br><span class="line">ALTER SYSTEM SET max_kept_major_version_number = &#x27;1&#x27;;</span><br><span class="line">ALTER SYSTEM SET max_stale_time_for_weak_consistency = &#x27;2h&#x27;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>​</p><ul><li>事务相关</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ALTER SYSTEM SET clog_sync_time_warn_threshold = &#x27;1s&#x27;;</span><br><span class="line">ALTER SYSTEM SET trx_try_wait_lock_timeout = &#x27;0ms&#x27;;（默认就是 0ms，无需修改）</span><br><span class="line"></span><br><span class="line">-- 建议关闭一阶段提交，该参数值默认是false</span><br><span class="line">ALTER SYSTEM SET enable_one_phase_commit=&#x27;False&#x27;;</span><br></pre></td></tr></table></figure><ul><li>分区迁移速度控制, 若是集群负载很低，可以通过加大并发任务数加快 partition迁移速度, 调大迁移并发数</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">alter system set data_copy_concurrency=40;</span><br><span class="line">alter system set server_data_copy_out_concurrency=20;</span><br><span class="line">alter system set server_data_copy_in_concurrency=20;</span><br></pre></td></tr></table></figure><ul><li>压缩相关</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-- （默认就是 zstd_1.0，无需修改）, 不过系统支持多种压缩算法</span><br><span class="line">ALTER SYSTEM SET default_compress_func = &#x27;zstd_1.0&#x27;;</span><br></pre></td></tr></table></figure><p>​</p><ul><li>cache 刷新相关</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ALTER SYSTEM SET autoinc_cache_refresh_interval = &#x27;43200s&#x27;;</span><br></pre></td></tr></table></figure><ul><li>prepare statement, server端ps受_ob_enable_prepared_statement开关控制，除了objdbc和oci的用户可以按照文档提供的说明使用server端ps，其他情况不建议用了；</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-- Prepared Statement参数，不用java建联的配置建议设为0</span><br><span class="line">ALTER SYSTEM SET _ob_enable_prepared_statement = 0;</span><br></pre></td></tr></table></figure><ul><li>系统相关</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">ALTER SYSTEM SET server_permanent_offline_time = &#x27;7200s&#x27;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">-- （公有云建议5M，外部环境建议5M，否则建议默认值30M）</span><br><span class="line">ALTER SYSTEM SET syslog_io_bandwidth_limit = &#x27;5M&#x27;;</span><br></pre></td></tr></table></figure><ul><li>集群升级策略, 在进行版本升级，机器临时上下线，可以先进行一次转储，可以减少启动observer时候恢复时间</li><li>批量导入大量数据最佳策略, 如果集群是多租户的，如果某个租户要大批量导入数据，为避免影响其他租户, 导完数据后可以恢复以上两个参数: <ul><li>1.调整 cpu_quota_concurrency &#x3D;1 ，防止租户间cpu抢占</li><li>开启多轮转储减少合并触发，这样可以提高导入速度</li></ul></li></ul><p>​</p><ul><li>租户primary_zone配置, <ul><li>primar_zone配置到具体某个zone中, 适用场景：业务使用单表、zone_name1与应用在同机房、zone_name2和zone_name3作为从副本平时无业务流量，具体zone顺序需按机房优先级、按应用和ob的机房配置。</li></ul></li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ALTER TENANT SET PRIMARY_ZONE = &#x27;zone_name1;zone_name2,zone_name3&#x27;;</span><br></pre></td></tr></table></figure><ul><li>打散primary_zone到所有全功能zone中, 使用场景：业务使用分区表、集群中所有副本都在同一机房或不同zone机房间网络延迟在1ms内，需要所有副</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ALTER TENANT SET PRIMARY_ZONE = &#x27;zone_name1,zone_name2,zone_name3&#x27;;</span><br></pre></td></tr></table></figure><h3 id="租户设置"><a href="#租户设置" class="headerlink" title="租户设置"></a>租户设置</h3><ul><li>并发度设置</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">-- 最大并发度，默认32，有大查询业务的建议调整为128</span><br><span class="line">SET GLOBAL ob_max_parallel_degree = 128;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">/*</span><br><span class="line">parallel_max_servers 推荐设置为测试租户分配的 resource unit cpu 数的 10 倍</span><br><span class="line">如测试租户使用的 unit 配置为：create resource unit $unit_name max_cpu 26</span><br><span class="line">那么该值设置为 260</span><br><span class="line">parallel_server_target 推荐设置为 parallel_max_servers * 机器数*0.8</span><br><span class="line">那么该值为 260*3*0.8=624</span><br><span class="line">*/</span><br><span class="line">set global parallel_max_servers=260;</span><br><span class="line">set global parallel_servers_target=624;</span><br></pre></td></tr></table></figure><ul><li>回收站设置</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">-- 回收站参数，ddl执行频率过大的场景一定要关闭，避免ddl执行过多引起租户性能异常</span><br><span class="line">SET GLOBAL recyclebin = 0;</span><br><span class="line"></span><br><span class="line">-- truncate回滚参数，truncate执行频率过大的场景一定要关闭</span><br><span class="line">SET GLOBAL ob_enable_truncate_flashback = 0;</span><br></pre></td></tr></table></figure><ul><li>客户端命令长度,  OB客户端可发的命令长度受限于租户系统变量_max_allowed_packet_的限制（缺省4M),可以酌情调大；</li></ul><h3 id="obproxy配置"><a href="#obproxy配置" class="headerlink" title="obproxy配置"></a>obproxy配置</h3><ul><li>obproxy探活</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">alter proxyconfig set sock_option_flag_out = 2;  --  2代表keepalive</span><br><span class="line">alter proxyconfig set server_tcp_keepidle = 5;  --  启动keepalive探活前的idle时间，5秒。</span><br><span class="line">alter proxyconfig set server_tcp_keepintvl = 5;  -- 两个keepalive探活包之间的时间间隔，5秒</span><br><span class="line">alter proxyconfig set server_tcp_keepcnt = 2;  --  最多发送多少个keepalive包，2个。最长5+5*2=15秒发现dead_socket。</span><br><span class="line">alter proxyconfig set server_tcp_user_timeout = 5;  --  等待TCP层ACK确认消息的超时时长，5秒。</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2021/ob_offline_install/</id>
    <link href="https://ilongda.com/2021/ob_offline_install/"/>
    <published>2021-11-20T03:42:57.000Z</published>
    <summary>OceanBase 分布式三副本离线安装指南：涵盖 SSH 免密、磁盘规划、OBD 部署 OBServer/OBProxy 及 tenant 验证全流程</summary>
    <title>OceanBase离线安装</title>
    <updated>2026-06-09T08:46:25.942Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="OceanBase" scheme="https://ilongda.com/categories/OceanBase/"/>
    <category term="开发者手册" scheme="https://ilongda.com/categories/OceanBase/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%8B%E5%86%8C/"/>
    <category term="OceanBase" scheme="https://ilongda.com/tags/OceanBase/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《OceanBase开发者手册》之五 如何debug OceanBase","description":"《OceanBase开发者手册》之五 如何debug OceanBase","image":"https://ilongda.com/img/ob/clion_debug1.png","wordCount":1750,"datePublished":"2021-11-11T11:42:57.000Z","dateModified":"2024-02-02T12:53:50.816Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2021/debug_ob/"},"url":"https://ilongda.com/2021/debug_ob/","inLanguage":"zh-CN","keywords":["OceanBase"],"articleSection":["OceanBase","开发者手册"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"OceanBase","item":"https://ilongda.com/categories/OceanBase/"},{"@type":"ListItem","position":3,"name":"《OceanBase开发者手册》之五 如何debug OceanBase","item":"https://ilongda.com/2021/debug_ob/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>《OceanBase开发者手册》 主要指导开发者如何参与到OceanBase 的研发, 铺平参与OceanBase 开发的准备工作遇到的问题, 当前章节大概这几篇文章, 未来可能会增加部分文章, 目前OceanBase 源码参考OceanBase 开源官网的<span class="exturl" data-url="aHR0cHM6Ly9vcGVuLm9jZWFuYmFzZS5jb20vYXJ0aWNsZXMvODYwMDEyOQ==">《开源数据库OceanBase源码解读》 系列<i class="fa fa-external-link-alt"></i></span> :</p><ol><li>如何编译OceanBase源码</li><li>如何设置IDE开发环境</li><li>如何成为OceanBase Contributor</li><li>如何修改OceanBase文档</li><li>如何debug OceanBase</li><li>如何运行测试</li><li>如何修bug<br>​</li></ol><p>本文将介绍如何debug OceanBase, 如何debug OceanBase, 推荐几种方式:</p><ol><li>使用vscode 远程debug OceanBase</li><li>使用gdb 本地debug OceanBase</li><li>在linux 环境下, 使用CLion 本地debug OceanBase</li></ol><span id="more"></span><p>​</p><h1 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h1><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><p>debug OceanBase 有一个重要的步骤, 就是弄到oceanbase 的启动参数, 每台机器有每台机器的硬件配置, 也会导致启动参数是不一样的. 但做法基本类似. </p><ol><li>用OBD (<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vYmRlcGxveQ==">https://github.com/oceanbase/obdeploy<i class="fa fa-external-link-alt"></i></span>) 安装部署一套环境</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1. 单机部署并且是联网环境, 请参考文档https://open.oceanbase.com/quickStart</span><br><span class="line">2. 分布式环境或者离线部署, 请参考文档 https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.1/deploy-the-distributed-oceanbase-cluster</span><br></pre></td></tr></table></figure><ol start="2"><li>成功部署环境后, 编译debug 版本OceanBase 参考之前文档 《如何编译OceanBase源码》</li><li>捕获oceanbase 的启动参数 (通过‘ps -ef|grep observer’).</li><li>(可选)在分布式环境下, 用编译好的binary observer 去替换 用obd 安装部署的observer</li></ol><p>我在我的单机测试环境下, 我用OBD安装部署OceanBase后, 我的OceanBase的启动参数是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">observer -r xxx.xxx.xxx.xxx:2882:2881 -o __min_full_resource_pool_memory=268435456,enable_syslog_recycle=True,enable_syslog_wf=True,max_syslog_file_count=4,memory_limit=69G,system_memory=27G,cpu_count=19,datafile_size=1029G,clog_disk_utilization_threshold=95,clog_disk_usage_limit_percentage=98 -z zone1 -p 2881 -P 2882 -n obcluster -c 1 -d /home/xxxxxxx/observer/store -i em1 -l INFO</span><br></pre></td></tr></table></figure><h2 id="vscode-调试"><a href="#vscode-调试" class="headerlink" title="vscode 调试"></a>vscode 调试</h2><ol><li>搭建remote 链接环境</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">1.1 建立开发机到测试机的信任登录, 参考 文档 https://open.oceanbase.com/docs/community/oceanbase-database/V3.1.1/optional-set-password-free-ssh-logon</span><br><span class="line">1.2 搭建vscode 的remote debug 环境  “Remote-SSH: Connect to Host...”, 参考文章 https://blog.csdn.net/zbbzb/article/details/102957076/ 进行配置</span><br></pre></td></tr></table></figure><ol start="2"><li>remote ssh 连接远程机器</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Ctril + p</span><br><span class="line">选择Remote-SSH:Connect to Host</span><br></pre></td></tr></table></figure><ol start="3"><li><p>打开对应的源码目录</p></li><li><p>参考之前文章 介绍 《如何编译OceanBase源码》</p></li><li><p>设置debug 启动参数, 在菜单栏  “Run” –&gt; “Add Configuration”, 如果之前已经设置过, 修改启动参数就是 菜单栏 “Run” –&gt; “Open Configurations”</p></li></ol><p>我的 配置是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    // Use IntelliSense to learn about possible attributes.</span><br><span class="line">    // Hover to view descriptions of existing attributes.</span><br><span class="line">    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387</span><br><span class="line">    &quot;version&quot;: &quot;0.2.0&quot;,</span><br><span class="line">    &quot;configurations&quot;: [</span><br><span class="line">    </span><br><span class="line">        &#123;</span><br><span class="line">            &quot;name&quot;: &quot;observer&quot;,</span><br><span class="line">            &quot;type&quot;: &quot;cppdbg&quot;,</span><br><span class="line">            &quot;request&quot;: &quot;launch&quot;,</span><br><span class="line">            &quot;program&quot;: &quot;$&#123;OB_SRC_DIR&#125;/build_debug/src/observer/observer&quot;,</span><br><span class="line">            &quot;args&quot;: [&quot;-r&quot;, &quot;$&#123;IP&#125;:2882:2881&quot;, &quot;-o&quot;, &quot;__min_full_resource_pool_memory=268435456,enable_syslog_recycle=True,enable_syslog_wf=True,max_syslog_file_count=4,memory_limit=69G,system_memory=27G,cpu_count=19,datafile_size=1029G,clog_disk_utilization_threshold=95,clog_disk_usage_limit_percentage=98&quot;, &quot;-z&quot;, &quot;zone1&quot;, &quot;-p&quot;, &quot;2881&quot;, &quot;-P&quot;, &quot;2882&quot;, &quot;-n&quot;, &quot;obcluster&quot;, &quot;-c&quot;, 1, &quot;-d&quot;, &quot;/home/XXX/observer/store&quot;, &quot;-i&quot;, &quot;em1&quot;, &quot;-l&quot;, &quot;INFO&quot;],</span><br><span class="line">            &quot;stopAtEntry&quot;: true,</span><br><span class="line">            &quot;cwd&quot;: &quot;$&#123;OB_SRC_DIR&#125;&quot;,</span><br><span class="line">            &quot;environment&quot;: [],</span><br><span class="line">            &quot;externalConsole&quot;: false,</span><br><span class="line">            &quot;MIMode&quot;: &quot;gdb&quot;,</span><br><span class="line">            &quot;setupCommands&quot;: [</span><br><span class="line">                &#123;</span><br><span class="line">                    &quot;description&quot;: &quot;Enable pretty-printing for gdb&quot;,</span><br><span class="line">                    &quot;text&quot;: &quot;-enable-pretty-printing&quot;,</span><br><span class="line">                    &quot;ignoreFailures&quot;: true</span><br><span class="line">                &#125;</span><br><span class="line">            ]</span><br><span class="line">        &#125;</span><br><span class="line">    ]</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>备注: 这个里面有arg的参数来自于第一步的准备工作中获取的启动参数, 每台机器有每台机器的配置, 笔者的参数如下, 其中:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">1. $&#123;OB_SRC_DIR&#125; 为源码目录, $&#123;IP&#125; 为observer 的绑定ip</span><br><span class="line">2. 需要设置“cwd”, 为$&#123;OB_SRC_DIR&#125;</span><br><span class="line">3. 建议设置“stopAtEntry” 为true</span><br><span class="line">4. 在args 参数中, 其中 -d 设置的目录 &quot;/home/xxxxx/observer/store&quot;, 需要设置为真实的参数</span><br><span class="line">5. 在args 参数中, 其中-i 设置的设备名称 &quot;em1&quot;, 为ip 对应的设备名称</span><br></pre></td></tr></table></figure><ol start="6"><li>开始debug, 点击菜单 “Run” –&gt; “Start Debugging”.</li></ol><h2 id="直接用gdb-本地调试"><a href="#直接用gdb-本地调试" class="headerlink" title="直接用gdb 本地调试"></a>直接用gdb 本地调试</h2><ol><li><p>登录remote 机器, 并进入${OB_SRC_DIR} 源码目录</p></li><li><p>参考之前文章 介绍 《如何编译OceanBase源码》</p></li><li><p>修改 用户目录下的.gdbinit, 添加下面一行, 其中${OB_SRC_DIR}需要换成OB 源码根目录</p></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">add-auto-load-safe-path $&#123;OB_SRC_DIR&#125;/.gdbinit</span><br></pre></td></tr></table></figure><ol start="4"><li>vi ${OB_SRC_DIR}&#x2F;.gdbinit</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">file build_debug/src/observer/observer</span><br><span class="line">set args &quot;-r&quot;, &quot;XXX.XXX.XXX.XXX:2882:2881&quot;, &quot;-o&quot;, &quot;__min_full_resource_pool_memory=268435456,enable_syslog_recycle=True,enable_syslog_wf=True,max_syslog_file_count=4,memory_limit=69G,system_memory=27G,cpu_count=19,datafile_size=1029G,clog_disk_utilization_threshold=95,clog_disk_usage_limit_percentage=98&quot;, &quot;-z&quot;, &quot;zone1&quot;, &quot;-p&quot;, &quot;2881&quot;, &quot;-P&quot;, &quot;2882&quot;, &quot;-n&quot;, &quot;obcluster&quot;, &quot;-c&quot;, 1, &quot;-d&quot;, &quot;/home/longda/observer/store&quot;, &quot;-i&quot;, &quot;em1&quot;, &quot;-l&quot;, &quot;INFO&quot;</span><br><span class="line">b main</span><br><span class="line">r</span><br></pre></td></tr></table></figure><p>备注: 这个里面有args的参数来自于第一步的准备工作中获取的启动参数, 每台机器有每台机器的配置, 笔者的参数如下, 其中:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">1. $&#123;OB_SRC_DIR&#125; 为源码目录, $&#123;IP&#125; 为observer 的绑定ip</span><br><span class="line">2. 需要设置“cwd”, 为$&#123;OB_SRC_DIR&#125;</span><br><span class="line">3. 当前的工作目录必须是$&#123;OB_SRC_DIR&#125;</span><br><span class="line">4. 在args 参数中, 其中 -d 设置的目录 &quot;/home/xxxxx/observer/store&quot;, 需要设置为真实的参数</span><br><span class="line">5. 在args 参数中, 其中-i 设置的设备名称 &quot;em1&quot;, 为ip 对应的设备名称</span><br></pre></td></tr></table></figure><ol start="5"><li>推荐使用tui<br>tui是gdb自带的图形界面，比较直观，这里简单说一下切换方法和常用命令</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">1、gdb -tui + (可执行程序)  直接进入tui图形界面</span><br><span class="line"></span><br><span class="line">2、gdb进入后，使用命令focus进入tui图形界面，或者使用快捷键：Ctl+x+a (注意按键顺序，记忆：x：focus，a：another)</span><br><span class="line"></span><br><span class="line">3、在tui中使用相同的快捷键Ctl+x+a返回到gdb原生界面</span><br><span class="line"></span><br><span class="line">4、在gdb中↑和↓切换上一个命令和下一个命令，但是在tui中只是控制代码视图。想达到切换命令的目的，使用Ctl+n （记忆：next）和Ctl+p（记忆：previous），这其实就是gdb的原生快捷键</span><br></pre></td></tr></table></figure><ol start="6"><li>在源码目录下, 敲入gdb 即可启动gdb debug</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gdb </span><br></pre></td></tr></table></figure><h2 id="clion-本地调试"><a href="#clion-本地调试" class="headerlink" title="clion 本地调试"></a>clion 本地调试</h2><p>clion 看源码非常方便， symbol 跳转非常友好， 而且天然code format 支持clang-format。 不过， 我没有试过clion 远程debug， 只试过本地clion debug， 不过如果想用clion 本地debug oceanbase， 则开发机器得运行linux。<br>clion 是debug 中最舒服的方式， 但也是最复杂的方式， 要求也非常高</p><ol><li>参考之前文章 介绍 《如何编译OceanBase源码》</li><li>配置 clion的cmake</li></ol><img data-src="/img/ob/clion_debug1.png"  alt="《OceanBase开发者手册》之五 如何debug OceanBase"><p>详情步骤参考图片所示， 需要说明的是， </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&quot;Build Directory&quot; 需要设置为“build_debug”</span><br><span class="line">&quot;CMake options&quot; 需要设置为“$&#123;OB_SRC_DIR&#125; -DCMAKE_BUILD_TYPE=Debug”, 其中$&#123;OB_SRC_DIR&#125; 需要修改为真实的目录全路径</span><br></pre></td></tr></table></figure><ol start="3"><li>等待几分钟生成cmake 生成结束后， 点击菜单“Run” –》 “Edit configurations”, 也可以类似下面图片， 进行选择编译目标observer</li></ol><img data-src="/img/ob/clion_debug2.JPG"  alt="《OceanBase开发者手册》之五 如何debug OceanBase"><ol start="4"><li><p>点击菜单“Build” –》 “Build observer”， 编译observer</p></li><li><p>修改启动参数， 点击菜单“Run” –》 “Edit configurations”， 出现界面后</p></li></ol><img data-src="/img/ob/clion_debug3.JPG"  alt="《OceanBase开发者手册》之五 如何debug OceanBase"><p>在我的机器上”Program Arguements” 为</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-r $&#123;ip&#125;:2882:2881 -o __min_full_resource_pool_memory=268435456,enable_syslog_recycle=True,enable_syslog_wf=True,    max_syslog_file_count=4,memory_limit=8G,system_memory=4G,cpu_count=16,datafile_size=44G,clog_disk_utilization_threshold=95,clog_disk_usage_limit_percentage=98 -z zone1 -p 2881 -P 2882 -n obcluster -c 1 -d $&#123;data_dir&#125; -i $&#123;devname&#125; -l INFO</span><br></pre></td></tr></table></figure><p>${ip} : 为本机ip</p><p>${data_dir}: 为数据目录</p><p>${devname}： 为ip 所对应的网卡名称， 通常为eth0 或lo</p><p>在“Woring Directory”必须为${OB_SRC_DIR}</p><ol start="6"><li><p>打开文件src&#x2F;observer&#x2F;main.cpp, 在main 函数下断点</p></li><li><p>启动debug， 点击菜单”Run” –&gt; “Debug Observer”</p></li></ol>]]>
    </content>
    <id>https://ilongda.com/2021/debug_ob/</id>
    <link href="https://ilongda.com/2021/debug_ob/"/>
    <published>2021-11-11T11:42:57.000Z</published>
    <summary>《OceanBase开发者手册》之五 如何debug OceanBase</summary>
    <title>《OceanBase开发者手册》之五 如何debug OceanBase</title>
    <updated>2024-02-02T12:53:50.816Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="OceanBase" scheme="https://ilongda.com/categories/OceanBase/"/>
    <category term="开发者手册" scheme="https://ilongda.com/categories/OceanBase/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%8B%E5%86%8C/"/>
    <category term="OceanBase" scheme="https://ilongda.com/tags/OceanBase/"/>
    <category term="开发者手册" scheme="https://ilongda.com/tags/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%8B%E5%86%8C/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《OceanBase开发者手册》之四 如何修改OceanBase文档","description":"《OceanBase开发者手册》之四 如何修改OceanBase文档","image":"https://ilongda.com/img/my.jpg","wordCount":482,"datePublished":"2021-11-10T11:42:57.000Z","dateModified":"2024-02-02T13:00:56.149Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2021/modify_ob_docs/"},"url":"https://ilongda.com/2021/modify_ob_docs/","inLanguage":"zh-CN","keywords":["OceanBase","开发者手册"],"articleSection":["OceanBase","开发者手册"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"OceanBase","item":"https://ilongda.com/categories/OceanBase/"},{"@type":"ListItem","position":3,"name":"《OceanBase开发者手册》之四 如何修改OceanBase文档","item":"https://ilongda.com/2021/modify_ob_docs/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>《OceanBase开发者手册》 主要指导开发者如何参与到OceanBase 的研发, 铺平参与OceanBase 开发的准备工作遇到的问题, 当前章节大概这几篇文章, 未来可能会增加部分文章, 目前OceanBase 源码参考OceanBase 开源官网的<span class="exturl" data-url="aHR0cHM6Ly9vcGVuLm9jZWFuYmFzZS5jb20vYXJ0aWNsZXMvODYwMDEyOQ==">《开源数据库OceanBase源码解读》 系列<i class="fa fa-external-link-alt"></i></span> :</p><ol><li>如何编译OceanBase源码</li><li>如何设置IDE开发环境</li><li>如何成为OceanBase Contributor</li><li>如何修改OceanBase文档</li><li>如何debug OceanBase</li><li>如何运行测试</li><li>如何修bug<br>​</li></ol><p>在OceanBase 修改文档的过程, 和修改代码的过程完全一样, 可以参考《如何成为OceanBase Contributor》直接修改文档, 如果不做文档修改的预览, 比如直接修改少量的错别字, 可以直接修改, 但如果增加大段文字或新增文章, 建议做预览一下. 那在这种情况下, 需要看看, 修改的效果如何. 可以安装mkdocs, 对修改的效果进行预览. </p><span id="more"></span><p>​</p><h1 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h1><h1 id="Build-documentation-with-MkDocs"><a href="#Build-documentation-with-MkDocs" class="headerlink" title="Build documentation with MkDocs"></a>Build documentation with MkDocs</h1><p>OceanBase documentation is built with <span class="exturl" data-url="aHR0cHM6Ly93d3cubWtkb2NzLm9yZy8=">MkDocs<i class="fa fa-external-link-alt"></i></span>. You can check <a href="mkdocs.yml"><code>mkdocs.yml</code></a> for more information.<br>Please install MkDocs according to <span class="exturl" data-url="aHR0cHM6Ly93d3cubWtkb2NzLm9yZy91c2VyLWd1aWRlL2luc3RhbGxhdGlvbi8=">the installation documents of MkDocs<i class="fa fa-external-link-alt"></i></span></p><h2 id="Requirements"><a href="#Requirements" class="headerlink" title="Requirements"></a>Requirements</h2><p>Before installing dependencies, please make sure you have installed a recent version of Python 3 and pip.</p><p>Then you can run the following command in your terminal at current directory:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ pip install -r requirements.txt</span><br><span class="line">$ pip install mkdocs-material</span><br></pre></td></tr></table></figure><h2 id="Build-the-documentation"><a href="#Build-the-documentation" class="headerlink" title="Build the documentation"></a>Build the documentation</h2><p>You can build the documentation by running the following command:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ mkdocs build</span><br></pre></td></tr></table></figure><p>This will create a new directory to store the output files, which is <code>site/</code> by default.</p><h2 id="Start-a-server-locally"><a href="#Start-a-server-locally" class="headerlink" title="Start a server locally"></a>Start a server locally</h2><p>You can start a server locally by running the following command:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ mkdocs serve</span><br></pre></td></tr></table></figure><p>Open up <span class="exturl" data-url="aHR0cDovLzEyNy4wLjAuMTo4MDAwLw==">http://127.0.0.1:8000/<i class="fa fa-external-link-alt"></i></span> in your browser, and you’ll see the default home page.</p><h2 id="Modify-pages"><a href="#Modify-pages" class="headerlink" title="Modify pages"></a>Modify pages</h2><h3 id="Edit-a-page"><a href="#Edit-a-page" class="headerlink" title="Edit a page"></a>Edit a page</h3><p>If you want to modify the content of a page, you can edit the markdown file in <code>docs/</code> directory directly.</p><h3 id="Modify-the-layout-of-pages"><a href="#Modify-the-layout-of-pages" class="headerlink" title="Modify the layout of pages"></a>Modify the layout of pages</h3><p>To modify the layout of pages, you need to edit <code>mkdocs.yml</code>.</p><p>For configuration details, see <span class="exturl" data-url="aHR0cHM6Ly93d3cubWtkb2NzLm9yZy91c2VyLWd1aWRlL2NvbmZpZ3VyYXRpb24v">MkDocs User Guide<i class="fa fa-external-link-alt"></i></span>.</p><p>Note the following rules when editing documents:</p><ul><li>All paths in <code>nav</code> must be relative to the <code>docs_dir</code>, which is <code>docs</code> by default. So here <code>./</code> is equivalent to <a href="docs">docs</a>.</li><li>All internal links must be relative paths, as MkDocs only supports regular Markdown linking syntax.</li></ul>]]>
    </content>
    <id>https://ilongda.com/2021/modify_ob_docs/</id>
    <link href="https://ilongda.com/2021/modify_ob_docs/"/>
    <published>2021-11-10T11:42:57.000Z</published>
    <summary>《OceanBase开发者手册》之四 如何修改OceanBase文档</summary>
    <title>《OceanBase开发者手册》之四 如何修改OceanBase文档</title>
    <updated>2024-02-02T13:00:56.149Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="OceanBase" scheme="https://ilongda.com/categories/OceanBase/"/>
    <category term="开发者手册" scheme="https://ilongda.com/categories/OceanBase/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%8B%E5%86%8C/"/>
    <category term="OceanBase" scheme="https://ilongda.com/tags/OceanBase/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《OceanBase开发者手册》之三 如何成为OceanBase Contributor","description":"《OceanBase开发者手册》之三 如何成为OceanBase Contributor","image":"https://ilongda.com/img/ob/contributor1.png","wordCount":861,"datePublished":"2021-10-30T11:42:57.000Z","dateModified":"2024-02-02T12:53:08.724Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2021/contribute_to_ob/"},"url":"https://ilongda.com/2021/contribute_to_ob/","inLanguage":"zh-CN","keywords":["OceanBase"],"articleSection":["OceanBase","开发者手册"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"OceanBase","item":"https://ilongda.com/categories/OceanBase/"},{"@type":"ListItem","position":3,"name":"《OceanBase开发者手册》之三 如何成为OceanBase Contributor","item":"https://ilongda.com/2021/contribute_to_ob/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>本文将指导用户如何成为OceanBase Contributor, 即使是一个小白, 也可以成为contributor. </p><p>《OceanBase开发者手册》 主要指导开发者如何参与到OceanBase 的研发, 铺平参与OceanBase 开发的准备工作遇到的问题, 当前章节大概这几篇文章, 未来可能会增加部分文章, 目前OceanBase 源码参考OceanBase 开源官网的<span class="exturl" data-url="aHR0cHM6Ly9vcGVuLm9jZWFuYmFzZS5jb20vYXJ0aWNsZXMvODYwMDEyOQ==">《开源数据库OceanBase源码解读》 系列<i class="fa fa-external-link-alt"></i></span> :</p><ol><li>如何编译OceanBase源码</li><li>如何设置IDE开发环境</li><li>如何成为OceanBase Contributor</li><li>如何修改OceanBase文档</li><li>如何debug OceanBase</li><li>如何运行测试</li><li>如何修bug<br>​</li></ol><span id="more"></span><p>​</p><h1 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h1><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><ol><li>在<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tLw==">https://github.com<i class="fa fa-external-link-alt"></i></span> 上注册一个用户, 如果已经有了一个账户, 则跳过此步骤<ol><li>因为现在github 不允许通过用户名和密码提交代码, 需要用户自己 创建token 来提交代码, <span class="exturl" data-url="aHR0cHM6Ly9kb2NzLmdpdGh1Yi5jb20vY24vYXV0aGVudGljYXRpb24va2VlcGluZy15b3VyLWFjY291bnQtYW5kLWRhdGEtc2VjdXJlL2NyZWF0aW5nLWEtcGVyc29uYWwtYWNjZXNzLXRva2Vu">https://docs.github.com/cn/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token<i class="fa fa-external-link-alt"></i></span> ,  用新的token 来代替过去的密码来提交.</li></ol></li><li>fork <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vY2VhbmJhc2U=">https://github.com/oceanbase/oceanbase<i class="fa fa-external-link-alt"></i></span> 到自己github账户下</li></ol><img data-src="/img/ob/contributor1.png"  alt="《OceanBase开发者手册》之三 如何成为OceanBase Contributor"><p>如果已经fork 了代码, 在github 点击<br><img data-src="/img/ob/contributor2.png"  alt="《OceanBase开发者手册》之三 如何成为OceanBase Contributor"></p><ol start="3"><li>准备编译环境, 参考文档  <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vY2VhbmJhc2Uvd2lraS9ob3dfdG9fYnVpbGQ=">how-to-build<i class="fa fa-external-link-alt"></i></span></li></ol><h2 id="代码编写"><a href="#代码编写" class="headerlink" title="代码编写"></a>代码编写</h2><ol><li>下载代码到本地,</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># git clone https://github.com/$&#123;用户&#125;/oceanbase</span><br></pre></td></tr></table></figure><p>备注:  ${用户} 为用户的名字</p><ol start="2"><li>在<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vY2VhbmJhc2UvaXNzdWVz">https://github.com/oceanbase/oceanbase/issues<i class="fa fa-external-link-alt"></i></span> 上找一个简单的issue,</li></ol><p>推荐找一个拼写错误的issue, 修改这些issue 比较简单, 容易上手.<br> <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vY2VhbmJhc2UvaXNzdWVzP3E9aXM6aXNzdWUraXM6b3BlbitsYWJlbDp0eXBvcw==">https://github.com/oceanbase/oceanbase/issues?q=is%3Aissue+is%3Aopen+label%3Atypos<i class="fa fa-external-link-alt"></i></span><br>​</p><p>创建对应的分支</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"># git checkout -b issue$&#123;issue_number&#125;</span><br></pre></td></tr></table></figure><p>备注: ${issue_number} 为issue 的编号<br>​</p><ol start="3"><li>在IDE 中修改代码, 推荐使用vscode, 并且vscode 使用远程链接功能. </li><li>修改完代码后, 进行编译</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#bash build.sh debug --init --make</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>大概等待10分钟</p><ol start="5"><li>开始单元测试, 如果只是修改注释, 修改文档, 则不需要进行单元测试</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"># cd build_debug/unittest/</span><br><span class="line">#make -j 4</span><br><span class="line">#./run_tests.sh</span><br></pre></td></tr></table></figure><p>整个过程, 需要1个小时</p><h2 id="代码提交"><a href="#代码提交" class="headerlink" title="代码提交"></a>代码提交</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"># git status</span><br><span class="line"></span><br><span class="line"># 位于分支 master</span><br><span class="line"># 尚未暂存以备提交的变更：</span><br><span class="line">#   （使用 &quot;git add &lt;file&gt;...&quot; 更新要提交的内容）</span><br><span class="line">#   （使用 &quot;git checkout -- &lt;file&gt;...&quot; 丢弃工作区的改动）</span><br><span class="line">#</span><br><span class="line">#修改：      ../../src/$&#123;修改文件&#125;</span><br><span class="line">#</span><br><span class="line">修改尚未加入提交（使用 &quot;git add&quot; 和/或 &quot;git commit -a&quot;）</span><br></pre></td></tr></table></figure><p>备注: ${修改文件}为修改文件<br>然后 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git add $&#123;修改文件&#125;</span><br><span class="line">git commit -m &quot;fixed $&#123;issue_number&#125;, xxxxxxx&quot;</span><br><span class="line">git push origin issue$&#123;issue_number&#125;</span><br></pre></td></tr></table></figure><p>备注: ${issue_number}为issue的编号<br>commit的comments 需要带上”fixed ${issue_number}”, 这样可以将issue number 和pull request 关联起来<br>然后<br>​</p><p>创建pull request<br><img data-src="/img/ob/contributor3.png"  alt="《OceanBase开发者手册》之三 如何成为OceanBase Contributor"></p><p>即可,<br>​</p><p>创建pull request 后, 需要 签署CLA, 如果已经签署了, 类似这样<br><img data-src="/img/ob/contributor4.png"  alt="《OceanBase开发者手册》之三 如何成为OceanBase Contributor"><br>​</p><p>后续等待 OceanBase 的官方进行approve</p><h2 id="其他注意事项"><a href="#其他注意事项" class="headerlink" title="其他注意事项"></a>其他注意事项</h2><h3 id="注意切换分支"><a href="#注意切换分支" class="headerlink" title="注意切换分支"></a>注意切换分支</h3><p>当同时修改几个bug时， 每提交一个pull request 后， 就switch 到master上, 避免每个pull request 相互之间影响。 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout master</span><br></pre></td></tr></table></figure><h3 id="发生冲突"><a href="#发生冲突" class="headerlink" title="发生冲突"></a>发生冲突</h3><p>自己的fork 的master 分支有可能会与远程master分支出现冲突， 这个时候， 在自己的fork 分支上， 把冲突commit 给删掉， 然后merge remote 的分支</p><ol><li>删除提交记录</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git reset --soft HEAD~i</span><br></pre></td></tr></table></figure><p>i代表要恢复到多少次提交前的状态，如指定i &#x3D; 2，则恢复到最近两次提交前的版本。–soft代表只删除服务器记录，不删除本地。</p><ol start="2"><li>执行</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin master --force</span><br></pre></td></tr></table></figure><p>master代表当前分支</p>]]>
    </content>
    <id>https://ilongda.com/2021/contribute_to_ob/</id>
    <link href="https://ilongda.com/2021/contribute_to_ob/"/>
    <published>2021-10-30T11:42:57.000Z</published>
    <summary>《OceanBase开发者手册》之三 如何成为OceanBase Contributor</summary>
    <title>《OceanBase开发者手册》之三 如何成为OceanBase Contributor</title>
    <updated>2024-02-02T12:53:08.724Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="OceanBase" scheme="https://ilongda.com/categories/OceanBase/"/>
    <category term="开发者手册" scheme="https://ilongda.com/categories/OceanBase/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%8B%E5%86%8C/"/>
    <category term="OceanBase" scheme="https://ilongda.com/tags/OceanBase/"/>
    <category term="开发者手册" scheme="https://ilongda.com/tags/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%8B%E5%86%8C/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《OceanBase开发者手册》之二 如何设置IDE开发环境","description":"《OceanBase开发者手册》之二 如何设置IDE开发环境","image":"https://ilongda.com/images/developer/vscode1.png","wordCount":2008,"datePublished":"2021-10-23T11:42:57.000Z","dateModified":"2024-02-02T13:12:13.300Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2021/set_ide/"},"url":"https://ilongda.com/2021/set_ide/","inLanguage":"zh-CN","keywords":["OceanBase","开发者手册"],"articleSection":["OceanBase","开发者手册"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"OceanBase","item":"https://ilongda.com/categories/OceanBase/"},{"@type":"ListItem","position":3,"name":"《OceanBase开发者手册》之二 如何设置IDE开发环境","item":"https://ilongda.com/2021/set_ide/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>《OceanBase开发者手册》 主要指导开发者如何参与到OceanBase 的研发, 铺平参与OceanBase 开发的准备工作遇到的问题, 当前章节大概这几篇文章, 未来可能会增加部分文章, 目前OceanBase 源码参考OceanBase 开源官网的<span class="exturl" data-url="aHR0cHM6Ly9vcGVuLm9jZWFuYmFzZS5jb20vYXJ0aWNsZXMvODYwMDEyOQ==">《开源数据库OceanBase源码解读》 系列<i class="fa fa-external-link-alt"></i></span> :</p><ol><li>如何编译OceanBase源码</li><li>如何设置IDE开发环境</li><li>如何成为OceanBase Contributor</li><li>如何修改OceanBase文档</li><li>如何debug OceanBase</li><li>如何运行测试</li><li>如何修bug<br>​</li></ol><p>本文将介绍如何在开发环境中， 设置编译器， 重点设置编译器的 code format 工具 – clang-fromat。 本文内容来自内部同事分享, 该设置可以分享给所有的c&#x2F;c++ 项目中.  </b></p><p>clang-format是clang（一个面向C系语言的轻量级编译器）中一个工具，主要负责代码的格式化和排版工作，可独立于clang工作，所以经常被单独用来用作代码规范格式化，很多第三方插件也都集成了clang-fromat。clang-format与各个IDE集成. clang-format 差不多已经快成为c&#x2F;c++ 上实时的代码格式化标准. </p><span id="more"></span><p>​</p><h1 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h1><h1 id="vscode"><a href="#vscode" class="headerlink" title="vscode"></a>vscode</h1><p>既然看到这步了，我就姑且认为你已经装好了vscode，如果没有的话，可以先移步vscode官网下载</p><h2 id="STEP-1"><a href="#STEP-1" class="headerlink" title="STEP 1"></a>STEP 1</h2><p>首先确保你的开发环境下的vscode安装了C&#x2F;C++扩展;<br><img data-src="images/developer/vscode1.png" alt="《OceanBase开发者手册》之二 如何设置IDE开发环境"><br>这里要注意vscode远程和本地vscode插件不共用</p><h2 id="STEP-2"><a href="#STEP-2" class="headerlink" title="STEP 2"></a>STEP 2</h2><p>在setting中对Clang_format_style进行设置, 确认C&#x2F;C++扩展的配置Clang_format_style 设置成file （此处默认是file，如果不放心可以检查一下）;<br><img data-src="images/developer/vscode2.png" alt="《OceanBase开发者手册》之二 如何设置IDE开发环境"></p><h2 id="STEP-3"><a href="#STEP-3" class="headerlink" title="STEP 3"></a>STEP 3</h2><p>将准备好的<span class="exturl" data-url="aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL29jZWFuYmFzZS9vY2VhbmJhc2UvbWFzdGVyLy5jbGFuZy1mb3JtYXQ=">.clang-format<i class="fa fa-external-link-alt"></i></span>文件拷贝到工程目录下（部分项目工程目录下已存在）</p><h2 id="STEP-FINAL"><a href="#STEP-FINAL" class="headerlink" title="STEP FINAL"></a>STEP FINAL</h2><p>恭喜你已经完成vscode 中clang format 设置， 如果前面都完成了的话，那么在vscode中格式化代码时就会自动根据.clang-format配置格式化文件</p><h1 id="eclipse"><a href="#eclipse" class="headerlink" title="eclipse"></a>eclipse</h1><h2 id="STEP-1-1"><a href="#STEP-1-1" class="headerlink" title="STEP 1:"></a>STEP 1:</h2><p>首先需要在你的开发环境下载clang-format   （此处后面推广的时候提供wget包下载或者其他更通用的方式）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$brew install clang-format</span><br></pre></td></tr></table></figure><p>或者可以下载整个LLVM然后在目录下找到clang-format, 记录下clang-format的路径. </b><br>Linux&#x2F;Mac环境下最好拷贝至&#x2F;usr&#x2F;bin目录下，方便直接命令执行.</p><p>当执行$OBDEV_ROOT&#x2F;build.sh –init 后， 会自动下载clang-format, 你可以在这里看到它</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">find ./ -name clang-format</span><br><span class="line">./deps/3rd/usr/local/oceanbase/devtools/bin/clang-format</span><br></pre></td></tr></table></figure><h2 id="STEP-2-1"><a href="#STEP-2-1" class="headerlink" title="STEP 2:"></a>STEP 2:</h2><p>eclipse中安装插件CppStyle</p><img data-src="images/developer/eclipse1.png" alt="《OceanBase开发者手册》之二 如何设置IDE开发环境"><h2 id="STEP-3-1"><a href="#STEP-3-1" class="headerlink" title="STEP 3:"></a>STEP 3:</h2><p>配置 CppStyle中clang-format的路径：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">（如果你CppStyle插件安装成功并重启的话，就会有这项配置）</span><br><span class="line">Preferences -&gt; C/C++ -&gt; CppStyle </span><br></pre></td></tr></table></figure><p>将之前下载的clang-format路径配置到Clang-format path中去</p><h2 id="STEP-4"><a href="#STEP-4" class="headerlink" title="STEP 4:"></a>STEP 4:</h2><p>配置 Code Formatter 为CppStyle：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Preferences -&gt; C/C++ -&gt; Code Style -&gt; Formatter</span><br></pre></td></tr></table></figure><p>将里面Code Formatter的配置选中下拉框选项 CppStyle(clang-format) （如果前面流程正确走完，此处应有此选项）</p><h2 id="STEP-5"><a href="#STEP-5" class="headerlink" title="STEP 5:"></a>STEP 5:</h2><p>将准备好的<span class="exturl" data-url="aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL29jZWFuYmFzZS9vY2VhbmJhc2UvbWFzdGVyLy5jbGFuZy1mb3JtYXQ=">.clang-format<i class="fa fa-external-link-alt"></i></span>文件拷贝到工程目录下（部分项目工程目录下已存在）</p><h2 id="STEP-FINAL-1"><a href="#STEP-FINAL-1" class="headerlink" title="STEP FINAL:"></a>STEP FINAL:</h2><p>此时就完成了所有准备工作，在eclipse编写代码的时候，格式化代码的时候，就会自动引用.clang-format的配置重新编排代码</p><h1 id="CLion"><a href="#CLion" class="headerlink" title="CLion"></a>CLion</h1><p>啊，写攻略终于写到一个轻松的了，JetBrains YYDS！<br>CLion天然集成了clang-format，所以只需要确认几项配置即可</p><h2 id="STEP-1-2"><a href="#STEP-1-2" class="headerlink" title="STEP 1:"></a>STEP 1:</h2><p>随便打开某个.h&#x2F;.c&#x2F;.cpp文件，在右下角点击’4 spaces’ 然后选择Enable ClangFormat</p><img data-src="images/developer/clion.png" alt="《OceanBase开发者手册》之二 如何设置IDE开发环境"><p>如果此处没有’4 spaces’，而是直接就是ClangFormat，那么就是CLion自动识别到存在.clang-format文件，帮忙配置好了</p><h2 id="STEP-2-2"><a href="#STEP-2-2" class="headerlink" title="STEP 2:"></a>STEP 2:</h2><p>确认’Enable ClangFormat’配置是否打开（这个CLion也是默认开启的）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Preferences -&gt; Editor -&gt; Code Style</span><br></pre></td></tr></table></figure><img data-src="images/developer/clion1.png" alt="《OceanBase开发者手册》之二 如何设置IDE开发环境"><h2 id="STEP-3-2"><a href="#STEP-3-2" class="headerlink" title="STEP 3:"></a>STEP 3:</h2><p>将准备好的<span class="exturl" data-url="aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL29jZWFuYmFzZS9vY2VhbmJhc2UvbWFzdGVyLy5jbGFuZy1mb3JtYXQ=">.clang-format<i class="fa fa-external-link-alt"></i></span>文件拷贝到工程目录下（部分项目工程目录下已存在）</p><h2 id="STEP-FINAL-2"><a href="#STEP-FINAL-2" class="headerlink" title="STEP FINAL:"></a>STEP FINAL:</h2><p>CLion就可以直接使用clang-format进行格式化了，当执行Reformat Code时就会自动编排代码</p><h1 id="VIM"><a href="#VIM" class="headerlink" title="VIM"></a>VIM</h1><h2 id="STEP-1-3"><a href="#STEP-1-3" class="headerlink" title="STEP 1:"></a>STEP 1:</h2><p>首先需要<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2xsdm0vbGx2bS1wcm9qZWN0L2Jsb2IvbGx2bW9yZy0xMi4wLjEvY2xhbmcvdG9vbHMvY2xhbmctZm9ybWF0L2NsYW5nLWZvcm1hdC5weQ==">clang-format.py<i class="fa fa-external-link-alt"></i></span>文件我们可以直接去github上面把对应文件拷贝下来;</p><h2 id="STEP-2-3"><a href="#STEP-2-3" class="headerlink" title="STEP 2:"></a>STEP 2:</h2><p>然后配置当前用户的.vimrc文件（如果没有的话，在当前用户的根目录下创建.vimrc文件即可）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">map &lt;C-K&gt; :pyf &lt;path-to-this-file&gt;/clang-format.py&lt;cr&gt;</span><br><span class="line">imap &lt;C-K&gt; &lt;c-o&gt;:pyf &lt;path-to-this-file&gt;/clang-format.py&lt;cr&gt;</span><br><span class="line"></span><br><span class="line">function! Formatonsave()</span><br><span class="line">  let l:formatdiff = 1</span><br><span class="line">  pyf &lt;path-to-this-file&gt;/clang-format.py</span><br><span class="line">endfunction</span><br><span class="line">autocmd BufWritePre *.h,*.cc,*.cpp call Formatonsave()</span><br></pre></td></tr></table></figure><p>代码块中的 ‘<path-to-this-file>‘ 替换成之前拷贝下来的clang-format.py的路径，然后保存并重启终端</p><p>PS：前面两行是增加主动触发clang-format功能</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">normal模式下，ctrl+k将格式化一行代码</span><br><span class="line">visual模式下，ctrl+k将格式化选中代码</span><br><span class="line">insert模式下，ctrl+k将格式化一行代码</span><br></pre></td></tr></table></figure><p>最后一段的function则是当使用vim保存当前.h&#x2F;.cc&#x2F;.cpp文件时，会自动将文件所有内容格式化</p><h2 id="STEP-3-3"><a href="#STEP-3-3" class="headerlink" title="STEP 3:"></a>STEP 3:</h2><p>将准备好的<span class="exturl" data-url="aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL29jZWFuYmFzZS9vY2VhbmJhc2UvbWFzdGVyLy5jbGFuZy1mb3JtYXQ=">.clang-format<i class="fa fa-external-link-alt"></i></span>文件拷贝到工程目录下（部分项目工程目录下已存在）</p><h2 id="STEP-FINAL："><a href="#STEP-FINAL：" class="headerlink" title="STEP FINAL："></a>STEP FINAL：</h2><p>完成前面的工作，vim就成功集成了clang-format，只不过需要注意的是，使用的时候先需要cd到工程路径下，并确保工程路径下已有.clang-format文件，然后在该路径下，使用vim去修改文件即可（在使用vim时就不要切换当前目录啦）</p><h1 id="EMACS"><a href="#EMACS" class="headerlink" title="EMACS"></a>EMACS</h1><p>EMACS貌似很强大的一个编辑器（都不确定是否应该称之为编辑器），但是对新人来说学习成本略高，折腾了一个晚上才勉强搞定😅<br>如果看这一块的话，我就默认大家已经了解了emacs的一些基本操作，展开讲可以单开一个系列的语雀了，所以我这里只放出和clang-format集成相关的操作了，其他的不做过多讲解<br>STEP 1:<br>首先需要在你的开发环境下载clang-format   （此处后面推广的时候提供wget包下载或者其他更通用的方式）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install clang-format</span><br></pre></td></tr></table></figure><p>或者可以下载整个LLVM然后在目录下找到clang-format<br>Linux环境下需要拷贝至&#x2F;usr&#x2F;bin目录下<br>MAC环境也需要配置到对应PATH路径中</p><p>当执行$OBDEV_ROOT&#x2F;build.sh –init 后， 会自动下载clang-format, 你可以在这里看到它</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">find ./ -name clang-format</span><br><span class="line">./deps/3rd/usr/local/oceanbase/devtools/bin/clang-format</span><br></pre></td></tr></table></figure><h2 id="STEP-2-4"><a href="#STEP-2-4" class="headerlink" title="STEP 2:"></a>STEP 2:</h2><p>然后需要使用package-install安装clang-format包，如果没有的话，可以修改package sources，这里展示一下我使用的sources配置：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">(&quot;melpa&quot; . &quot;http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/&quot;)</span><br><span class="line">(&quot;org-cn&quot; . &quot;http://mirrors.tuna.tsinghua.edu.cn/elpa/org/&quot;)</span><br><span class="line">(&quot;gnu&quot; . &quot;http://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/&quot;)</span><br></pre></td></tr></table></figure><h2 id="STEP-3-4"><a href="#STEP-3-4" class="headerlink" title="STEP 3:"></a>STEP 3:</h2><p>安装完成后，找到安装的插件位置，我的是mac系统，安装位置在用户根目录下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.emacs.d/elpa/clang-format-20191106.950</span><br></pre></td></tr></table></figure><p>找到该路径下的clang-format.el文件，讲该文件的路径配置到.emacs配置文件中去（配置路径自行根据实际情况调整）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(load &quot;/Users/xxx/.emacs.d/elpa/clang-format-20191106.950/clang-format.el&quot;)</span><br></pre></td></tr></table></figure><p>PS：如果是mac系统的话比较麻烦，mac系统使用GUI打开emacs时，shell里面配置的环境变量是不会自动带过来的，这个时候需要在.emacs中额外配置一些内容：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">(defun set-exec-path-from-shell-PATH ()</span><br><span class="line">  &quot;Set up Emacs&#x27; `exec-path&#x27; and PATH environment variable to match</span><br><span class="line">that used by the user&#x27;s shell.</span><br><span class="line"></span><br><span class="line">This is particularly useful under Mac OS X and macOS, where GUI</span><br><span class="line">apps are not started from a shell.&quot;</span><br><span class="line">  (interactive)</span><br><span class="line">  (let ((path-from-shell (replace-regexp-in-string</span><br><span class="line">        &quot;[ \t\n]*$&quot; &quot;&quot; (shell-command-to-string</span><br><span class="line">            &quot;$SHELL --login -c &#x27;echo $PATH&#x27;&quot;</span><br><span class="line">                ))))</span><br><span class="line">    (setenv &quot;PATH&quot; path-from-shell)</span><br><span class="line">    (setq exec-path (split-string path-from-shell path-separator))))</span><br><span class="line"></span><br><span class="line">(set-exec-path-from-shell-PATH)</span><br></pre></td></tr></table></figure><h2 id="STEP-4-1"><a href="#STEP-4-1" class="headerlink" title="STEP 4:"></a>STEP 4:</h2><p>将准备好的<span class="exturl" data-url="aHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL29jZWFuYmFzZS9vY2VhbmJhc2UvbWFzdGVyLy5jbGFuZy1mb3JtYXQ=">.clang-format<i class="fa fa-external-link-alt"></i></span>文件拷贝到工程目录下（部分项目工程目录下已存在）</p><h2 id="STEP-FINAL-3"><a href="#STEP-FINAL-3" class="headerlink" title="STEP FINAL:"></a>STEP FINAL:</h2><p>现在就已经完成了clang-format的集成工作，在开发中，只需要在文件编辑界面执行M-x clang-format-buffer 即可。此时clang-format会自动向本级及父级目录找到.clang-format文件，并根据文件配置格式化代码</p>]]>
    </content>
    <id>https://ilongda.com/2021/set_ide/</id>
    <link href="https://ilongda.com/2021/set_ide/"/>
    <published>2021-10-23T11:42:57.000Z</published>
    <summary>《OceanBase开发者手册》之二 如何设置IDE开发环境</summary>
    <title>《OceanBase开发者手册》之二 如何设置IDE开发环境</title>
    <updated>2024-02-02T13:12:13.300Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="OceanBase" scheme="https://ilongda.com/categories/OceanBase/"/>
    <category term="开发者手册" scheme="https://ilongda.com/categories/OceanBase/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%8B%E5%86%8C/"/>
    <category term="OceanBase" scheme="https://ilongda.com/tags/OceanBase/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《OceanBase开发者手册》之一 如何编译OceanBase源码","description":"《OceanBase开发者手册》之一 如何编译OceanBase源码","image":"https://ilongda.com/img/my.jpg","wordCount":329,"datePublished":"2021-10-16T11:42:57.000Z","dateModified":"2024-02-02T12:52:46.988Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2021/build_ob/"},"url":"https://ilongda.com/2021/build_ob/","inLanguage":"zh-CN","keywords":["OceanBase"],"articleSection":["OceanBase","开发者手册"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"OceanBase","item":"https://ilongda.com/categories/OceanBase/"},{"@type":"ListItem","position":3,"name":"《OceanBase开发者手册》之一 如何编译OceanBase源码","item":"https://ilongda.com/2021/build_ob/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>本文将指导用户如何编译OceanBase, 本文大部分内容来自 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL29jZWFuYmFzZS9vY2VhbmJhc2U=">https://github.com/oceanbase/oceanbase<i class="fa fa-external-link-alt"></i></span> .  </p><p>《OceanBase开发者手册》 主要指导开发者如何参与到OceanBase 的研发, 铺平参与OceanBase 开发的准备工作遇到的问题, 当前章节大概这几篇文章, 未来可能会增加部分文章, 目前OceanBase 源码参考OceanBase 开源官网的<span class="exturl" data-url="aHR0cHM6Ly9vcGVuLm9jZWFuYmFzZS5jb20vYXJ0aWNsZXMvODYwMDEyOQ==">《开源数据库OceanBase源码解读》 系列<i class="fa fa-external-link-alt"></i></span> :</p><ol><li>如何编译OceanBase源码</li><li>如何设置IDE开发环境</li><li>如何成为OceanBase Contributor</li><li>如何修改OceanBase文档</li><li>如何debug OceanBase</li><li>如何运行测试</li><li>如何修bug<br>​</li></ol><span id="more"></span><p>​</p><h1 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h1><h2 id="OS-compatibility-list"><a href="#OS-compatibility-list" class="headerlink" title="OS compatibility list"></a>OS compatibility list</h2><table><thead><tr><th>OS</th><th>Ver.</th><th>Arch</th><th>Compilable</th><th>Package Deployable</th><th>Compiled Binary Deployable</th><th>Mysqltest Passed</th></tr></thead><tbody><tr><td>Alibaba Cloud Linux</td><td>2.1903</td><td>x86_64</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>CentOS</td><td>7.2, 8.3</td><td>x86_64</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Debian</td><td>9.8, 10.9</td><td>x86_64</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Fedora</td><td>33</td><td>x86_64</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>MacOS</td><td>any</td><td>x86_64</td><td>❌</td><td>❌</td><td>❌</td><td>❌</td></tr><tr><td>openSUSE</td><td>15.2</td><td>x86_64</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>OpenAnolis</td><td>8.2</td><td>x86_64</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>SUSE</td><td>15.2</td><td>x86_64</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>Ubuntu</td><td>16.04, 18.04, 20.04</td><td>x86_64</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>UOS</td><td>20</td><td>x86_64</td><td>✅</td><td>✅</td><td>✅</td><td>✅</td></tr></tbody></table><h2 id="How-to-build"><a href="#How-to-build" class="headerlink" title="How to build"></a>How to build</h2><p>This document will show how to build oceanbase. </p><h3 id="Preparation"><a href="#Preparation" class="headerlink" title="Preparation"></a>Preparation</h3><p>Before building, you need to confirm that your device has installed the necessary software.</p><h4 id="Redhat-based-including-CentOS-Fedora-OpenAnolis-RedHat-UOS-etc"><a href="#Redhat-based-including-CentOS-Fedora-OpenAnolis-RedHat-UOS-etc" class="headerlink" title="Redhat based (, including CentOS, Fedora, OpenAnolis, RedHat, UOS, etc.)"></a>Redhat based (, including CentOS, Fedora, OpenAnolis, RedHat, UOS, etc.)</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install git wget rpm* cpio make glibc-devel glibc-headers binutils</span><br></pre></td></tr></table></figure><h4 id="Debian-based-including-Debian-Ubuntu-etc"><a href="#Debian-based-including-Debian-Ubuntu-etc" class="headerlink" title="Debian based (, including Debian, Ubuntu, etc.)"></a>Debian based (, including Debian, Ubuntu, etc.)</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install git wget rpm rpm2cpio cpio make build-essential binutils</span><br></pre></td></tr></table></figure><h4 id="SUSE-based-including-SUSE-openSUSE-etc"><a href="#SUSE-based-including-SUSE-openSUSE-etc" class="headerlink" title="SUSE based (, including SUSE, openSUSE, etc.)"></a>SUSE based (, including SUSE, openSUSE, etc.)</h4><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">zypper install git wget rpm cpio make glibc-devel binutils</span><br></pre></td></tr></table></figure><h3 id="debug-mode"><a href="#debug-mode" class="headerlink" title="debug mode"></a>debug mode</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bash build.sh debug --init --make</span><br></pre></td></tr></table></figure><h3 id="release-mode"><a href="#release-mode" class="headerlink" title="release mode"></a>release mode</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bash build.sh release --init --make</span><br></pre></td></tr></table></figure><h3 id="RPM-packages"><a href="#RPM-packages" class="headerlink" title="RPM packages"></a>RPM packages</h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bash build.sh rpm --init &amp;&amp; <span class="built_in">cd</span> build_rpm &amp;&amp; make -j16 rpm</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2021/build_ob/</id>
    <link href="https://ilongda.com/2021/build_ob/"/>
    <published>2021-10-16T11:42:57.000Z</published>
    <summary>《OceanBase开发者手册》之一 如何编译OceanBase源码</summary>
    <title>《OceanBase开发者手册》之一 如何编译OceanBase源码</title>
    <updated>2024-02-02T12:52:46.988Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="旅游" scheme="https://ilongda.com/categories/%E6%97%85%E6%B8%B8/"/>
    <category term="旅游" scheme="https://ilongda.com/tags/%E6%97%85%E6%B8%B8/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"漫步九寨沟","description":"九寨沟旅游游记：国庆自驾成都至九寨堵途经验，推荐公交线路剑崖至诺日朗、五彩池与树正群海等步行赏景路线，更多细节与示例见正文。","image":"https://ilongda.com/img/jiuzhaigou/jiuzhai1.jpg","wordCount":804,"datePublished":"2021-10-10T03:42:57.000Z","dateModified":"2026-06-09T08:46:25.936Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2021/jiuzhaigou/"},"url":"https://ilongda.com/2021/jiuzhaigou/","inLanguage":"zh-CN","keywords":["旅游"],"articleSection":["旅游"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"旅游","item":"https://ilongda.com/categories/旅游/"},{"@type":"ListItem","position":3,"name":"漫步九寨沟","item":"https://ilongda.com/2021/jiuzhaigou/"}]}</script><h1 id="题外话"><a href="#题外话" class="headerlink" title="题外话"></a>题外话</h1><p>在大学的时候, 就常常听到朋友各种介绍九寨沟, 记得大学毕业那年, 同寝室的室友玩了一次毕业旅行, 去了趟九寨沟, 表达了对九寨沟略有失望, 这也让我对九寨沟一直没有强烈的诉求, 也没有说专程去四川玩一下九寨沟, 不过,因为老婆这次非常想去四川玩, 就提了一嘴是不是可以顺便去看看九寨沟, 现在回想起来, 幸好提了一嘴, 否则去了四川没有去九寨沟, 真的就少了点什么. </p><p>引用一段九寨沟的介绍</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">九寨沟位于四川省西北部岷山山脉南段的阿坝藏族羌族自治州九寨沟县漳扎镇境内，地处岷山南段弓杆岭的东北侧。距离成都市400多千米，系长江水系嘉陵江上游白水江源头的一条大支沟。 九寨沟自然保护区地势南高北低，山谷深切，高差悬殊。北缘九寨沟口海拔仅2000米，中部峰岭均在4000米以上，南缘达4500米以上，主沟长30多公里。</span><br><span class="line">九寨沟是世界自然遗产、国家重点风景名胜区、国家AAAAA级旅游景区、国家级自然保护区、国家地质公园、世界生物圈保护区网络，也是中国第一个以保护自然风景为主要目的的自然保护区。</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="攻略"><a href="#攻略" class="headerlink" title="攻略"></a>攻略</h2><p>去九寨沟, 其实不需要什么攻略, 建议就2点: </p><ol><li>记得提前在网上预定门门票; </li><li>建议直接飞到离九寨沟最近的机场, 然后乘车, 建议是包车, 自己租车会比较累.</li></ol><p>我们是先飞到成都, 然后开车到九寨沟, 正好是10.1 长假期间, 我们从成都开到九寨沟, 从早上8点出发, 中间吃饭休息几次, 一直堵一直堵, 直到晚上10点才到了酒店, 400公里, 在车上的时间超过了12个小时, 以后打死都不会在这种高速免费节假日的第一天或最后一天上高速. </p><img data-src="/img/jiuzhaigou/jiuzhai1.jpg"  alt="漫步九寨沟"><img data-src="/img/jiuzhaigou/19.jpg"  alt="漫步九寨沟"><span id="more"></span><h1 id="行程"><a href="#行程" class="headerlink" title="行程"></a>行程</h1><p>推荐行程路线是在入口做公交, 一直坐到剑崖, 然后慢慢往下走, 如果下一个站点远就做车, 一路玩到诺日朗瀑布, 然后换公交去五彩池, 看完五彩池, 然后再坐车回到诺日朗, 在沿途玩耍犀牛海, 树正瀑布, 卧龙湾, 双龙海等等. </p><img data-src="/img/jiuzhaigou/map.jpg"  alt="漫步九寨沟"><p>最前面的天鹅海, 芳草海, 箭竹海, 一路风景美不胜收, 边走边拍<br><img data-src="/img/jiuzhaigou/1.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/2.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/3.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/4.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/5.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/6.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/7.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/8.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/9.jpg"  alt="漫步九寨沟"></p><img data-src="/img/jiuzhaigou/12.jpg"  alt="漫步九寨沟"><p>金铃海<br><img data-src="/img/jiuzhaigou/10.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/11.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/16.jpg"  alt="漫步九寨沟"></p><p>熊猫海瀑布<br><img data-src="/img/jiuzhaigou/14.jpg"  alt="漫步九寨沟"></p><p>镜海<br><img data-src="/img/jiuzhaigou/18.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/15.jpg"  alt="漫步九寨沟"></p><p>诺日朗瀑布<br><img data-src="/img/jiuzhaigou/17.jpg"  alt="漫步九寨沟"></p><p>长海, 到长海的时候, 正天降冰雹, 花生大小的冰雹砸下来, 噼里啪啦的作响<br><img data-src="/img/jiuzhaigou/21.jpg"  alt="漫步九寨沟"></p><p>著名的五色池, 5色池还是颜色非常丰富<br><img data-src="/img/jiuzhaigou/20.jpg"  alt="漫步九寨沟"><br><img data-src="/img/jiuzhaigou/19.jpg"  alt="漫步九寨沟"></p><p>后续沿途步行了3小时, 因为是从山上往下走, 也没有那么累, 所以还好, 沿途欣赏了犀牛海, 老虎海, 树正群海, 树正瀑布, 卧龙海, 双龙海, 最后</p><img data-src="/img/jiuzhaigou/22.jpg"  alt="漫步九寨沟"><img data-src="/img/jiuzhaigou/23.jpg"  alt="漫步九寨沟"><img data-src="/img/jiuzhaigou/24.jpg"  alt="漫步九寨沟"><img data-src="/img/jiuzhaigou/25.jpg"  alt="漫步九寨沟">最后一个景点, 芦苇海<img data-src="/img/jiuzhaigou/26.jpg"  alt="漫步九寨沟">]]>
    </content>
    <id>https://ilongda.com/2021/jiuzhaigou/</id>
    <link href="https://ilongda.com/2021/jiuzhaigou/"/>
    <published>2021-10-10T03:42:57.000Z</published>
    <summary>九寨沟旅游游记：国庆自驾成都至九寨堵途经验，推荐公交线路剑崖至诺日朗、五彩池与树正群海等步行赏景路线，更多细节与示例见正文。</summary>
    <title>漫步九寨沟</title>
    <updated>2026-06-09T08:46:25.936Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具总结","description":"Linux 系统监控工具总览：汇总 sar、pidstat、iostat、perf、vmstat 及 Java 内存/GC 调优等常用工具索引","image":"https://ilongda.com/img/my.jpg","wordCount":118,"datePublished":"2021-01-14T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.964Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2021/docs/tools/monitor_tools/general/"},"url":"https://ilongda.com/2021/docs/tools/monitor_tools/general/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具总结","item":"https://ilongda.com/2021/docs/tools/monitor_tools/general/"}]}</script><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>将过去使用的几个监控工具分享一下， 也方便自己以后查找。 </p><ul><li><a href="/knowledge/tools/monitor_tools/sar.html">sar， Linux 上最为全面的系统性能分析工具之一</a></br></li><li><a href="/knowledge/tools/monitor_tools/pidstat.html">pidstat， 系统资源监控工具之一</a></br></li><li><a href="/knowledge/tools/monitor_tools/iostat.html">iostat， IO性能分析工具之一</a></br></li><li><a href="/knowledge/tools/monitor_tools/network.html">network 监控与分析工具</a></br></li><li><a href="/knowledge/tools/monitor_tools/perf.html">perf， 性能分析工具之一</a></br></li><li><a href="/knowledge/tools/monitor_tools/trace.html">trace系统</a></br></li><li><a href="/knowledge/tools/monitor_tools/vmstat.html">vmstat， 内存监控工具之一</a></br></li><li><a href="/knowledge/tools/monitor_tools/memory.html">内存监控与分析工具</a></br></li><li><a href="/knowledge/tools/monitor_tools/java_gc.html">对java gc 性能调优</a></br></li><li><a href="/knowledge/tools/monitor_tools/java_mem.html">java memory 监控与分析</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2021/docs/tools/monitor_tools/general/</id>
    <link href="https://ilongda.com/2021/docs/tools/monitor_tools/general/"/>
    <published>2021-01-14T11:42:57.000Z</published>
    <summary>Linux 系统监控工具总览：汇总 sar、pidstat、iostat、perf、vmstat 及 Java 内存/GC 调优等常用工具索引</summary>
    <title>监控工具总结</title>
    <updated>2026-06-09T08:46:25.964Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具总结","description":"Linux 系统监控工具总览：汇总 sar、pidstat、iostat、perf、vmstat 及 Java 内存/GC 调优等常用工具索引","image":"https://ilongda.com/img/my.jpg","wordCount":118,"datePublished":"2021-01-14T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.964Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2021/docs/tools/monitor_tools/index/"},"url":"https://ilongda.com/2021/docs/tools/monitor_tools/index/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具总结","item":"https://ilongda.com/2021/docs/tools/monitor_tools/index/"}]}</script><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>将过去使用的几个监控工具分享一下， 也方便自己以后查找。 </p><ul><li><a href="/knowledge/tools/monitor_tools/sar.html">sar， Linux 上最为全面的系统性能分析工具之一</a></br></li><li><a href="/knowledge/tools/monitor_tools/pidstat.html">pidstat， 系统资源监控工具之一</a></br></li><li><a href="/knowledge/tools/monitor_tools/iostat.html">iostat， IO性能分析工具之一</a></br></li><li><a href="/knowledge/tools/monitor_tools/network.html">network 监控与分析工具</a></br></li><li><a href="/knowledge/tools/monitor_tools/perf.html">perf， 性能分析工具之一</a></br></li><li><a href="/knowledge/tools/monitor_tools/trace.html">trace系统</a></br></li><li><a href="/knowledge/tools/monitor_tools/vmstat.html">vmstat， 内存监控工具之一</a></br></li><li><a href="/knowledge/tools/monitor_tools/memory.html">内存监控与分析工具</a></br></li><li><a href="/knowledge/tools/monitor_tools/java_gc.html">对java gc 性能调优</a></br></li><li><a href="/knowledge/tools/monitor_tools/java_mem.html">java memory 监控与分析</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2021/docs/tools/monitor_tools/index/</id>
    <link href="https://ilongda.com/2021/docs/tools/monitor_tools/index/"/>
    <published>2021-01-14T11:42:57.000Z</published>
    <summary>Linux 系统监控工具总览：汇总 sar、pidstat、iostat、perf、vmstat 及 Java 内存/GC 调优等常用工具索引</summary>
    <title>监控工具总结</title>
    <updated>2026-06-09T08:46:25.964Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"paper推荐","description":"数据库与大数据论文阅读笔记索引：收录 Redshift、Snowflake、Volcano/Cascades 优化器及 Dataflow 等经典论文","image":"https://ilongda.com/img/my.jpg","wordCount":292,"datePublished":"2020-11-14T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.962Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/docs/paper/general/"},"url":"https://ilongda.com/2020/docs/paper/general/","inLanguage":"zh-CN","keywords":["论文"],"articleSection":["论文"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"paper推荐","item":"https://ilongda.com/2020/docs/paper/general/"}]}</script><p>因为楼主有个不好的习惯， 论文看完后， 过了一段时间就忘了讲什么，另外英语并不是很好，英文论文常常理解抓不住重点，因为习惯做一个笔记， 方便以后进行会议， 也方便理解论文。<br>后期会不定时进行更新。有一些论文是之前留下的笔记，做了一些梳理。 </p><p>有一些论文，还没有读完， 读完的，会有link</p><h1 id="Database"><a href="#Database" class="headerlink" title="Database"></a>Database</h1><h2 id="System"><a href="#System" class="headerlink" title="System"></a>System</h2><ul><li><a href="/knowledge/paper/libr.html">《FusionInsight LibrA Huawei’s Enterprise Cloud Data Analytics Platform》</a></li><li><a href="/knowledge/paper/redshift.html">《Amazon Redshift and the Case for Simpler Data Warehouses》</a></li><li><a href="/knowledge/paper/snowflake.html">《The Snowflake Elastic Data Warehouse》</a></li><li><a href="/knowledge/paper/f1.html">《F1 Query: Declarative Querying at Scale》</a></li></ul><h2 id="Optimizer"><a href="#Optimizer" class="headerlink" title="Optimizer"></a>Optimizer</h2><ul><li>《Access Path Selection in a Relational Database Management System》 </li><li><a href="/knowledge/paper/The_Volcano_Optimizer_Generator.html">《The Volcano Optimizer Generator: Extensibility and Efficient Search》</a></li><li><a href="/knowledge/paper/Volcano.html">《Volcano-An Extensible and Parallel Query Evaluation System》</a></li><li><a href="/knowledge/paper/cascade.html">《The Cascades Framework for Query Optimization》</a></li><li><a href="/knowledge/paper/ms-sql-server-pdw.html">《Query Optimization in Microsoft SQL Server PDW》</a></li><li>《Inside The SQL Server Query Optimizer》</li><li>《Incorporating Partitioning and Parallel Plans into the SCOPE Optimizer》</li><li>《Orca: A Modular Query Optimizer Architecture for Big Data》</li><li>《How Good Are Query Optimizers, Really?》</li><li>《An overview of query optimization in relational systems.》</li><li>《Query optimization through the looking glass, and what we found running the Join Order Benchmark.》</li><li>《Adaptive statistics in Oracle 12c.》</li><li>《Exploiting Statistics on Query Expressions for Optimization》</li></ul><h1 id="BigData"><a href="#BigData" class="headerlink" title="BigData"></a>BigData</h1><ul><li><a href="/knowledge/paper/dataflow.html">《The Dataflow Model: A Practical Approach to Balancing Correctness, Latency, and Cost in Massive-Scale, Unbounded, Out-of-Order Data Processing》</a></li><li><a href="/knowledge/paper/heron.html">《Hero stream process stream》 </a></li><li><a href="/knowledge/paper/millwheel.html">《MillWheel: Fault-Tolerant Stream Processing at Internet Scale》</a></li><li><a href="/knowledge/paper/screamscope.html">《StreamScope: Continuous Reliable Distributed Processing of Big Data Streams》</a></li></ul>]]>
    </content>
    <id>https://ilongda.com/2020/docs/paper/general/</id>
    <link href="https://ilongda.com/2020/docs/paper/general/"/>
    <published>2020-11-14T11:42:57.000Z</published>
    <summary>数据库与大数据论文阅读笔记索引：收录 Redshift、Snowflake、Volcano/Cascades 优化器及 Dataflow 等经典论文</summary>
    <title>paper推荐</title>
    <updated>2026-06-09T08:46:25.962Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"paper推荐","description":"数据库与大数据论文阅读笔记索引：收录 Redshift、Snowflake、Volcano/Cascades 优化器及 Dataflow 等经典论文","image":"https://ilongda.com/img/my.jpg","wordCount":292,"datePublished":"2020-11-14T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.962Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/docs/paper/index/"},"url":"https://ilongda.com/2020/docs/paper/index/","inLanguage":"zh-CN","keywords":["论文"],"articleSection":["论文"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"paper推荐","item":"https://ilongda.com/2020/docs/paper/index/"}]}</script><p>因为楼主有个不好的习惯， 论文看完后， 过了一段时间就忘了讲什么，另外英语并不是很好，英文论文常常理解抓不住重点，因为习惯做一个笔记， 方便以后进行会议， 也方便理解论文。<br>后期会不定时进行更新。有一些论文是之前留下的笔记，做了一些梳理。 </p><p>有一些论文，还没有读完， 读完的，会有link</p><h1 id="Database"><a href="#Database" class="headerlink" title="Database"></a>Database</h1><h2 id="System"><a href="#System" class="headerlink" title="System"></a>System</h2><ul><li><a href="/knowledge/paper/libr.html">《FusionInsight LibrA Huawei’s Enterprise Cloud Data Analytics Platform》</a></li><li><a href="/knowledge/paper/redshift.html">《Amazon Redshift and the Case for Simpler Data Warehouses》</a></li><li><a href="/knowledge/paper/snowflake.html">《The Snowflake Elastic Data Warehouse》</a></li><li><a href="/knowledge/paper/f1.html">《F1 Query: Declarative Querying at Scale》</a></li></ul><h2 id="Optimizer"><a href="#Optimizer" class="headerlink" title="Optimizer"></a>Optimizer</h2><ul><li>《Access Path Selection in a Relational Database Management System》 </li><li><a href="/knowledge/paper/The_Volcano_Optimizer_Generator.html">《The Volcano Optimizer Generator: Extensibility and Efficient Search》</a></li><li><a href="/knowledge/paper/Volcano.html">《Volcano-An Extensible and Parallel Query Evaluation System》</a></li><li><a href="/knowledge/paper/cascade.html">《The Cascades Framework for Query Optimization》</a></li><li><a href="/knowledge/paper/ms-sql-server-pdw.html">《Query Optimization in Microsoft SQL Server PDW》</a></li><li>《Inside The SQL Server Query Optimizer》</li><li>《Incorporating Partitioning and Parallel Plans into the SCOPE Optimizer》</li><li>《Orca: A Modular Query Optimizer Architecture for Big Data》</li><li>《How Good Are Query Optimizers, Really?》</li><li>《An overview of query optimization in relational systems.》</li><li>《Query optimization through the looking glass, and what we found running the Join Order Benchmark.》</li><li>《Adaptive statistics in Oracle 12c.》</li><li>《Exploiting Statistics on Query Expressions for Optimization》</li></ul><h1 id="BigData"><a href="#BigData" class="headerlink" title="BigData"></a>BigData</h1><ul><li><a href="/knowledge/paper/dataflow.html">《The Dataflow Model: A Practical Approach to Balancing Correctness, Latency, and Cost in Massive-Scale, Unbounded, Out-of-Order Data Processing》</a></li><li><a href="/knowledge/paper/heron.html">《Hero stream process stream》 </a></li><li><a href="/knowledge/paper/millwheel.html">《MillWheel: Fault-Tolerant Stream Processing at Internet Scale》</a></li><li><a href="/knowledge/paper/screamscope.html">《StreamScope: Continuous Reliable Distributed Processing of Big Data Streams》</a></li></ul>]]>
    </content>
    <id>https://ilongda.com/2020/docs/paper/index/</id>
    <link href="https://ilongda.com/2020/docs/paper/index/"/>
    <published>2020-11-14T11:42:57.000Z</published>
    <summary>数据库与大数据论文阅读笔记索引：收录 Redshift、Snowflake、Volcano/Cascades 优化器及 Dataflow 等经典论文</summary>
    <title>paper推荐</title>
    <updated>2026-06-09T08:46:25.962Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="性能测试" scheme="https://ilongda.com/categories/Database/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="性能测试" scheme="https://ilongda.com/tags/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"磁盘性能测试","description":"使用 sysbench 对比阿里云 ESSD、SSD 云盘与本地 NVMe 磁盘 I/O 性能，介绍测试脚本参数及不同存储类型的实测差异","image":"https://ilongda.com/img/my.jpg","wordCount":490,"datePublished":"2020-07-12T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.932Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/fileio/"},"url":"https://ilongda.com/2020/fileio/","inLanguage":"zh-CN","keywords":["Database","性能测试"],"articleSection":["Database","性能测试"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"磁盘性能测试","item":"https://ilongda.com/2020/fileio/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>心血来潮，想测试一下阿里云的essd 和阿里云的本地ssd 性能， 不巧的是阿里云ecs 的存储， 有几种类型， essd， ssd 云盘， 高效云盘 和本地盘nvme 盘<br>最终测试下来确实nvme 盘的性能最好。</p><p>测试工具： 本文用sysbench 进行测试， 以前读书的时候使用过iometer 进行测试， sysbench 测试参数更多， 对iops 的测试效果更丰富， iometer 偏重吞吐量的测试。 </p><span id="more"></span><h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p>参考之前的博文， 学会如何安装sysbench</p><h1 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h1><p>测试脚本</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line">#!/bin/bash</span><br><span class="line"></span><br><span class="line">SYSBENCH_FILE_TOTAL_SIZE=16G</span><br><span class="line">SYSBENCH_FILE_NUM=16</span><br><span class="line">SYSBENCH_NUM_THREADS=16</span><br><span class="line">FSYNC=off</span><br><span class="line">SYSBENCH_BLOCK_SIZE=4096</span><br><span class="line">SYSBENCH_TIME=60</span><br><span class="line"></span><br><span class="line">#  --file-num=N                  number of files to create [128]</span><br><span class="line">#  --file-block-size=N           block size to use in all IO operations [16384]</span><br><span class="line">#  --file-total-size=SIZE        total size of files to create [2G]</span><br><span class="line">#  --file-test-mode=STRING       test mode &#123;seqwr, seqrewr, seqrd, rndrd, rndwr, rndrw&#125;</span><br><span class="line">#  --file-io-mode=STRING         file operations mode &#123;sync,async,mmap&#125; [sync]</span><br><span class="line">#  --file-extra-flags=[LIST,...] list of additional flags to use to open files &#123;sync,dsync,direct&#125; []</span><br><span class="line">#  --file-fsync-freq=N           do fsync() after this number of requests (0 - don&#x27;t use fsync()) [100]</span><br><span class="line">#  --file-fsync-all[=on|off]     do fsync() after each write operation [off]</span><br><span class="line">#  --file-fsync-end[=on|off]     do fsync() at the end of test [on]</span><br><span class="line">#  --file-fsync-mode=STRING      which method to use for synchronization &#123;fsync, fdatasync&#125; [fsync]</span><br><span class="line">#  --file-merged-requests=N      merge at most this number of IO requests if possible (0 - don&#x27;t merge) [0]</span><br><span class="line">#  --file-rw-ratio=N             reads/writes ratio for combined test [1.5]</span><br><span class="line"></span><br><span class="line">testmodes=(</span><br><span class="line">    &quot;seqwr&quot; </span><br><span class="line">    &quot;seqrewr&quot; </span><br><span class="line">    &quot;seqrd&quot;</span><br><span class="line">    &quot;rndrd&quot;</span><br><span class="line">    &quot;rndwr&quot; </span><br><span class="line">    &quot;rndrw&quot;</span><br><span class="line">    )</span><br><span class="line">for testmode in &quot;$&#123;testmodes[@]&#125;&quot;</span><br><span class="line">do</span><br><span class="line">    directios=(</span><br><span class="line">    &quot;&quot;</span><br><span class="line">    &quot;sync&quot;</span><br><span class="line">    &quot;direct&quot;</span><br><span class="line">    &quot;dsync&quot;</span><br><span class="line">    )</span><br><span class="line">    for directio in &quot;$&#123;directios[@]&#125;&quot;</span><br><span class="line">    do</span><br><span class="line">        date</span><br><span class="line">        echo 1 &gt; /proc/sys/vm/drop_caches</span><br><span class="line">        echo &quot;begin to run $testmode   $directio&quot;</span><br><span class="line">        sysbench fileio  --file-num=$SYSBENCH_FILE_NUM --file-block-size=$SYSBENCH_BLOCK_SIZE --file-total-size=$SYSBENCH_FILE_TOTAL_SIZE --file-test-mode=$testmode --file-io-mode=sync --file-extra-flags=$directio --file-fsync-all=$FSYNC --file-fsync-mode=fsync --file-fsync-freq=0 --file-merged-requests=0 --threads=$SYSBENCH_NUM_THREADS prepare</span><br><span class="line">        sysbench fileio --file-num=$SYSBENCH_FILE_NUM --file-block-size=$SYSBENCH_BLOCK_SIZE --file-total-size=$SYSBENCH_FILE_TOTAL_SIZE --file-test-mode=$testmode --file-io-mode=sync --file-extra-flags=$directio --file-fsync-all=$FSYNC --file-fsync-mode=fsync --file-fsync-freq=0 --file-merged-requests=0  --report-interval=10  --threads=$SYSBENCH_NUM_THREADS --time=$SYSBENCH_TIME run</span><br><span class="line">        sysbench fileio  --file-num=$SYSBENCH_FILE_NUM --file-block-size=$SYSBENCH_BLOCK_SIZE --file-total-size=$SYSBENCH_FILE_TOTAL_SIZE --file-test-mode=$testmode --file-io-mode=sync --file-extra-flags=$directio --file-fsync-all=$FSYNC --file-fsync-mode=fsync --file-fsync-freq=0 --file-merged-requests=0   cleanup</span><br><span class="line">        date</span><br><span class="line">        esynccho &quot;Finish one loop test&quot;</span><br><span class="line">    done</span><br><span class="line">done</span><br><span class="line"></span><br><span class="line">rm -rf test_file.*</span><br><span class="line"></span><br><span class="line">echo &quot;Finish all test&quot;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2020/fileio/</id>
    <link href="https://ilongda.com/2020/fileio/"/>
    <published>2020-07-12T11:42:57.000Z</published>
    <summary>使用 sysbench 对比阿里云 ESSD、SSD 云盘与本地 NVMe 磁盘 I/O 性能，介绍测试脚本参数及不同存储类型的实测差异</summary>
    <title>磁盘性能测试</title>
    <updated>2026-06-09T08:46:25.932Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="性能测试" scheme="https://ilongda.com/categories/Database/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="性能测试" scheme="https://ilongda.com/tags/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"DB性能测试-常用3套件-手把手一步一步跑tpcc","description":"性能测试 -- DB性能测试-常用3套件-手把手一步一步跑tpcc","image":"https://ilongda.com/img/tpcc.png","wordCount":2901,"datePublished":"2020-07-05T11:42:57.000Z","dateModified":"2024-02-02T13:13:42.020Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/tpcc/"},"url":"https://ilongda.com/2020/tpcc/","inLanguage":"zh-CN","keywords":["Database","性能测试"],"articleSection":["Database","性能测试"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"DB性能测试-常用3套件-手把手一步一步跑tpcc","item":"https://ilongda.com/2020/tpcc/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>把过去的写的一篇笔记分享一下， 数据库最常用的测试三套件， sysbench – oltp 测试， tpch – olap 测试， tpcc – 事务性能测试。<br>本文手把手 一步一步 run tpcc</p><p>整个过程， 分为</p><ul><li>介绍</li><li>准备工作</li><li>编译</li><li>测试</li><li>疑难杂症</li></ul><span id="more"></span><h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p><span class="exturl" data-url="aHR0cDovL3d3dy50cGMub3JnL3RwY2Mv">http://www.tpc.org/tpcc/<i class="fa fa-external-link-alt"></i></span><br>TPCC有不同的运行方式，用来测试数据库的压力工具，模拟一个电商的业务，主要的业务有新增订单，库存查询，发货，支付等模块的测试。 详情可以参考 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2RvbWluby1zdWNjL3RwY2MtaGJhc2Uvd2lraS8lRTQlQjglQUQlRTYlOTYlODctVFBDLUMlRTclQUUlODAlRTQlQkIlOEI=">https://github.com/domino-succ/tpcc-hbase/wiki/中文-TPC-C简介<i class="fa fa-external-link-alt"></i></span></p><h2 id="模型简介"><a href="#模型简介" class="headerlink" title="模型简介"></a>模型简介</h2><p>在测试开始前，TPC-C Benchmark规定了数据库的初始状态，也就是数据库中数据生成的规则，其中ITEM表中固定包含10万种商品，仓库的数量可进行调整，假设WAREHOUSE表中有W条记录，那么：</p><ul><li>STOCK表中应有W×10万条记录(每个仓库对应10万种商品的库存数据)；</li><li>DISTRICT表中应有W×10条记录(每个仓库为10个地区提供服务)；</li><li>CUSTOMER表中应有W×10×3000条记录(每个地区有3000个客户)；</li><li>HISTORY表中应有W×10×3000条记录(每个客户一条交易历史)；</li><li>ORDER表中应有W×10×3000条记录(每个地区3000个订单)，并且最后生成的900个订单被添加到NEW-ORDER表中，每个订单随机生成5~15条ORDER-LINE记录。</li></ul><p>在测试过程中，每一个地区（DISTRICT）都有一个对应的终端（Terminal），模拟为用户提供服务。在每个终端的生命周期内，要循环往复地执行各类事务，每个事务的流程如图所示，当终端执行完一个事务的周期后，就进入下一个事务的周期，如下图所示。<br><img data-src="/img/tpcc.png"  alt="DB性能测试-常用3套件-手把手一步一步跑tpcc"></p><p>客户下单后，包含若干个订单明细（ORDER-LINE）的订单（ORDER）被生成，并被加入新订单（NEW-ORDER）列表。<br>客户对订单支付还会产生交易历史（HISTORY）。<br>每个订单(ORDER) 平均包含10条订单项(ORDER-LINE), 其中1% 需要从远程仓库中获取.<br>这些就是TPC-C模型中的9个数据表。其中，仓库的数量W可以根据系统的实际情况进行调整，以使系统性能测试结果达到最佳。</p><h2 id="Metrics"><a href="#Metrics" class="headerlink" title="Metrics"></a>Metrics</h2><p>TPCC用tpmC值（Transactions per Minute）来衡量系统最大有效吞吐量. 其中Transactions以NewOrder Transaction为准，即最终衡量单位为每分钟处理的订单数。</p><h2 id="事务类型"><a href="#事务类型" class="headerlink" title="事务类型"></a>事务类型</h2><p>该benchmark包含5类事务</p><ul><li>NewOrder: 新订单的生成从某一仓库随机选取5-15件商品, 创建新订单. 其中1%的事务需要回滚(即err).  一般来说新订单请求不可能超出全部事务请求的45% |</li><li>Payment : 订单付款更新客户账户余额, 反映其支付情况. 占比 43% </li><li>OrderStatus : 最近订单查询 随机显示一个用户显示其最有益条订单, 显示该订单内的每个商品状态. 占比4% </li><li>Delivery  : 配送, 模拟批处理交易更新该订单用户的余额, 把发货单从neworder中删除. 占比4% </li><li>StockLevel  : 库存缺货状态分析 , 占比4%</li></ul><h2 id="要求"><a href="#要求" class="headerlink" title="要求"></a>要求</h2><p>然后，终端模拟用户输入事务所需的参数，并等待一个输入时间（Keying Time）；等待结束后，事务执行正式开始，执行结束后记录事务的实际执行时间（txnRT），TPC-C对每类事务的执行时间都有一个最低要求，分别是：</p><ul><li>至少90%的NewOrder事务执行时间要低于5秒，</li><li>至少90%的Payment事务执行时间要低于5秒，</li><li>至少90%的OrderStatus事务执行时间要低于5秒，</li><li>至少90%的Payment事务执行时间要低于5秒，</li><li>至少90%的StockLevel事务执行时间要低于20秒；</li></ul><p>最后，终端模拟用户对结果的查看以及思考，等待一个思考时间（Thinking Time）；在思考时间结束后，进入下一个事务周期。 在整个测试过程结束后，用处理过的新订单事务总数量除以整个测试运行的分钟数并取整，就得到了tpmC的值。</p><h1 id="测试工具调研"><a href="#测试工具调研" class="headerlink" title="测试工具调研"></a>测试工具调研</h1><p>TPC-C测试常用的测试工具包括tpcc-mysql，benchmark-sql，Hammer DB，DBT2，sqlbench。这些工具对TPC-C标准的支持程度不尽相同。经过调研发现从DBT2改进来的开源的sqlbench工具是最接近标准要求的测试工具。因此我们首先对比不同工具对标准的支持情况，然后介绍使用sqlbench进行TPC-C测试的步骤。</p><h2 id="tpcc-mysql简介"><a href="#tpcc-mysql简介" class="headerlink" title="tpcc-mysql简介"></a>tpcc-mysql简介</h2><p><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL1BlcmNvbmEtTGFiL3RwY2MtbXlzcWw=">TPCC-MYSQL<i class="fa fa-external-link-alt"></i></span> 是percona基于TPC-C(下面简写成TPCC)衍生出来的产品，专用于MySQL基准测试。用来测试数据库的压力工具，模拟一个电商的业务，主要的业务有新增订单，库存查询，发货，支付等模块的测试。使用说明明文档（<span class="exturl" data-url="aHR0cHM6Ly93d3cucGVyY29uYS5jb20vYmxvZy8yMDEzLzA3LzAxL3RwY2MtbXlzcWwtc2ltcGxlLXVzYWdlLXN0ZXBzLWFuZC1ob3ctdG8tYnVpbGQtZ3JhcGhzLXdpdGgtZ251cGxvdC8lRUYlQkMlODklRTMlODAlODI=">https://www.percona.com/blog/2013/07/01/tpcc-mysql-simple-usage-steps-and-how-to-build-graphs-with-gnuplot/）。<i class="fa fa-external-link-alt"></i></span></p><h2 id="benchmark-sql简介"><a href="#benchmark-sql简介" class="headerlink" title="benchmark-sql简介"></a>benchmark-sql简介</h2><p><span class="exturl" data-url="aHR0cHM6Ly9zb3VyY2Vmb3JnZS5uZXQvcHJvamVjdHMvYmVuY2htYXJrc3FsLw==">benchmark-sql<i class="fa fa-external-link-alt"></i></span>是java语言实现TPCC工具，使用JDBC接口，支持Oracle、PostgreSQL、firebird和MySQL。</p><h2 id="HammerDB简介"><a href="#HammerDB简介" class="headerlink" title="HammerDB简介"></a>HammerDB简介</h2><p><span class="exturl" data-url="aHR0cDovL3d3dy5oYW1tZXJkYi5jb20vZG9jdW1lbnQuaHRtbA==">HammerDB<i class="fa fa-external-link-alt"></i></span>是Tcl语言实现TPCC&#x2F;TPCH工具，使用存储过程和Tcl包，支持Oracle、SQL Server、DB2、MySQL、PostgreSQL、Redis、Trafodion。支持Windows图形界面以及Tcl脚本脚本，功能比较完善。</p><h2 id="DBT2简介"><a href="#DBT2简介" class="headerlink" title="DBT2简介"></a>DBT2简介</h2><p><span class="exturl" data-url="aHR0cDovL29zZGxkYnQuc291cmNlZm9yZ2UubmV0Lw==">Databases Test 2<i class="fa fa-external-link-alt"></i></span>是由Open Source Development Lab开发的用于测试数据库性能的测试工具，虽然没有完全实现TPCC但是基本上也是模拟了OLTP的应用场景的，测试结果包括每秒处理的事务数、CPU使用率、IO以及内存的使用情况</p><h2 id="sqlbench简介"><a href="#sqlbench简介" class="headerlink" title="sqlbench简介"></a>sqlbench简介</h2><p><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL3N3aWRhL3NxbGJlbmNo">sqlbench<i class="fa fa-external-link-alt"></i></span>源自DBT2，作者阿里巴巴 荣生(diancheng.wdc)。原来的DBT2把整个测试过程分成client和 driver 两个应用程序，每个terminal需要 2个线程。如果测试的warehouse较多需要占用机器大量资源。而sqlbench对这方面做了优化，合并了这两个应用程序，同时优 化了线程的使用，使用1个线程处理多个terminal，大大减少了机器资源的使用。使一台机器可以运行更多的warehouse。另外 DBT2外部依赖较多，如对R环境的依赖，sqlbench去掉了不必要的外部依赖，目前sqlbench只依赖测试数据库的客户端库。</p><h2 id="数据库支持"><a href="#数据库支持" class="headerlink" title="数据库支持"></a>数据库支持</h2><table><thead><tr><th>sqlbench</th><th>MySQL, PostgreSQL</th></tr></thead><tbody><tr><td>tpcc-mysql</td><td>MySQL</td></tr><tr><td>benchmark-sql</td><td>PostgreSQL, EnterpriseDB and Oracle, MySQL</td></tr><tr><td>HammerDB</td><td>Oracle Database, SQL Server, IBM Db2, MySQL, MariaDB, PostgreSQL and Redis</td></tr><tr><td>DBT2</td><td>MySQL, PostgreSQL</td></tr></tbody></table><h2 id="对sql执行方式的支持"><a href="#对sql执行方式的支持" class="headerlink" title="对sql执行方式的支持"></a>对sql执行方式的支持</h2><table><thead><tr><th>sqlbench</th><th>plain SQL，prepared statement，stored procedure</th></tr></thead><tbody><tr><td>tpcc-mysql</td><td>Prepared statement</td></tr><tr><td>benchmarksql</td><td>Prepared statement</td></tr><tr><td>HammerDB</td><td>Stored Procedure</td></tr><tr><td>DBT2</td><td>Plain SQL，prepared statement，stored procedure</td></tr></tbody></table><h2 id="对key-in-time和think-time的支持"><a href="#对key-in-time和think-time的支持" class="headerlink" title="对key-in time和think time的支持"></a>对key-in time和think time的支持</h2><p>TPC-C标准规定了每一中交易中的模拟用户输入和对输出结果的思考时间，这使得同一个terminal发起的SQL事务是非常低频率的操作。TPC-C标准通过这两个延迟时间限制了同一个terminal在给定的时间内能完成的交易数量，与此同时标准规定用一时间最多只有10个terminals访问同一个warehouse。经过计算1个warehouse能提供的最多TpmC为12.86。</p><table><thead><tr><th>sqlbench</th><th>YES</th></tr></thead><tbody><tr><td>tpcc-mysql</td><td>NO</td></tr><tr><td>benchmark-sql</td><td>NO</td></tr><tr><td>HammerDB</td><td>NO</td></tr><tr><td>DBT2</td><td>YES</td></tr></tbody></table><h2 id="对terminal和database-connection的解耦"><a href="#对terminal和database-connection的解耦" class="headerlink" title="对terminal和database connection的解耦"></a>对terminal和database connection的解耦</h2><p>TPC-C规定了terminal到事务处理引擎中间必须通过网络连接，但是这里所列出的所有工具都不支持这种架构。DBT2和sqlbench虽然不支持这种三层结构，但是支持terminal处理和DB连接用不同的线程分开，其他几个工具没有terminal的概念，直接通过db connection thread完成交易。</p><p><img data-src="https://intranetproxy.alipay.com/skylark/lark/0/2019/png/105349/1573401426816-e09d8730-610a-48a9-b18e-d8fff1fba88b.png#align=left&display=inline&height=383&name=image.png&originHeight=766&originWidth=1520&size=201206&status=done&style=none&width=760" alt="image.png"></p><table><thead><tr><th>sqlbench</th><th>Terminal和DB conneciton解耦，可以单独配置数量。Termial处理线程支持复用，缺省配置每个线程支持50个terminal。</th></tr></thead><tbody><tr><td>tpcc-mysql</td><td>没有terminal，只能配置db connection数量</td></tr><tr><td>benchmark-sql</td><td>没有terminal，只能配置db connection数量</td></tr><tr><td>HammerDB</td><td>没有terminal，只能配置db connection数量</td></tr><tr><td>DBT2</td><td>Terminal和DB conneciton解耦，可以单独配置数量</td></tr></tbody></table><h2 id="TPC-C标准中Home-Warehouse支持"><a href="#TPC-C标准中Home-Warehouse支持" class="headerlink" title="TPC-C标准中Home Warehouse支持"></a>TPC-C标准中Home Warehouse支持</h2><p>标准要求每一个terminal在整个测试运行周期内保持warehouse ID不变，即home warehouse不变。</p><table><thead><tr><th>sqlbench</th><th>支持terminal home warehouse</th></tr></thead><tbody><tr><td>tpcc-mysql</td><td>不支持，warehouse每个交易随机生成</td></tr><tr><td>benchmark-sql</td><td>不支持，warehouse每个交易随机生成</td></tr><tr><td>HammerDB</td><td>支持terminal home warehouse</td></tr><tr><td>DBT2</td><td>支持terminal home warehouse</td></tr></tbody></table><h2 id="Terminal上的可视信息交互"><a href="#Terminal上的可视信息交互" class="headerlink" title="Terminal上的可视信息交互"></a>Terminal上的可视信息交互</h2><p>标准定义了用户从terminal输入数据，然后交易结果的细节数据需要返回给terminal用户用于展示，这会带来额外的时延。所有工具都没有对terminal信息交互的支持。</p><h1 id="sqlbench"><a href="#sqlbench" class="headerlink" title="sqlbench"></a>sqlbench</h1><h2 id="prepare"><a href="#prepare" class="headerlink" title="prepare"></a>prepare</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install -y autoconf automake mysql mysql-devel postgresql-devel</span><br></pre></td></tr></table></figure><h2 id="compile"><a href="#compile" class="headerlink" title="compile"></a>compile</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/swida/sqlbench.git</span><br><span class="line">cd sqlbench</span><br><span class="line">aclocal</span><br><span class="line">autoconf</span><br><span class="line">autoheader</span><br><span class="line">automake --add-missing</span><br><span class="line">./configure --with-postgresql=yes --with-mysql=yes </span><br><span class="line">make &amp;&amp; make install</span><br></pre></td></tr></table></figure><h2 id="load-数据"><a href="#load-数据" class="headerlink" title="load 数据"></a>load 数据</h2><p>从 <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2xvbmdkYWZlbmcvdGVzdC90cmVlL21hc3Rlci9zaGVsbC90cGNjL3NxbGJlbmNo">https://github.com/longdafeng/test/tree/master/shell/tpcc/sqlbench<i class="fa fa-external-link-alt"></i></span> 上把load.sh 脚本下载下来</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">cd sqlbench</span><br><span class="line">cd src/scripts</span><br><span class="line">wget https://raw.githubusercontent.com/longdafeng/test/master/shell/tpcc/sqlbench/load.sh</span><br><span class="line">nohup ./load.sh ipxxxx portxxx userxxx passwordxxx dbnamexxx warehousexxx &gt; load.log 2&gt;&amp;1 &amp;</span><br><span class="line">tail -f load.log </span><br></pre></td></tr></table></figure><ol><li>必须使用ip， 建议不要使用域名， 因为使用域名，经常建立不了到数据库的链接</li><li>脚本是放到${sqlbench_source_dir}&#x2F;src&#x2F;scripts 下</li></ol><ul><li>ipxxxx     数据库的ip</li><li>portxxx    数据库的端口号</li><li>userxxx    数据库的用户名</li><li>password   数据库的密码</li><li>dbnamexxx  数据库的库名字</li><li>warehousexxx warehouse 的个数 ， 比如100</li></ul><h2 id="运行测试"><a href="#运行测试" class="headerlink" title="运行测试"></a>运行测试</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">cd sqlbench</span><br><span class="line"># MySQL:</span><br><span class="line">nohup ./src/core/sqlbench -t mysql --dbname=tpcc --user=user --password=password --host=127.0.0.1 --port=3306 -w100 -c32 -l7200 -r1200 --sqlapi=storeproc &gt;run.log 2&gt;&amp;1 &amp;</span><br><span class="line">tail -f run.log</span><br></pre></td></tr></table></figure><p>这里:</p><p>• -t 指定PostgreSQL数据库 –dbname和–host为数据库的连接参数，其他没指定的使用默认参数<br>• -w 指定测试数据为100个warehouse<br>• -l 共运行测试时间为7200秒<br>• -r 其中ramp up的时间为1200秒<br>• -c 共使用32个数据库连接<br>sqlbench其他的常用参数包括：<br>• –no-thinktime 默认的TPC-C测试是有keying time和thinking time的，模拟真正的用户场景，可通过指定这个参数将相应 的时间设置成0，不控制时间间隔，产生最大压力<br>• –sqlapi 选择SQL执行的方式，可选：<br>• simple 为普通SQL方式<br>• extended 使用prepare&#x2F;bind&#x2F;execute方式，该方式先生成查询计划缓存起来，后面直接执行，效率更高<br>• storeproc 使用存储过程，这种方式与比extended相比还节省了与数据库服务器通信的开销<br>• -s与-e指定开始和结束的warehouse数，在更多warehouse时，可以使用这2个选项分配warehouse，分成多个sqlbench压测同 一个数据库<br>• –altered默认情况sqlbench下根据TPC-C标准生成terminal数（每个terminal代表一个user，每个warehouse 10个terminal， 也可以使用–tpw改变），这个参数直接指定了terminal个数，被这些warehouse平均分。<br>• –sleep指定每创建一个线程后sleep的时间，默认为1s<br>• -o 用来指定output目录，用来存储错误日志及测试结果文件<br>sqlbench的其他参数是用来定制TPC-C标准的各个部分的，包括keying time、thinking time的时间，各个事务所占比率，各个 表的数据量等，默认值都是遵从TPC-C标准的。</p><p>比如常用</p><ol><li>没有think time控制</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./src/core/sqlbench -t mysql --dbname=tpcc --user=user --password=password --host=$HOST --port=$PORT -w$WH -c$CONN -l7200 -r1200 --sqlapi=storeproc --no-thinktime --sleep=10</span><br></pre></td></tr></table></figure><ol start="2"><li>有think time控制</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">./src/core/sqlbench -t mysql --dbname=tpcc --user=user --password=password --host=$HOST --port=$PORT -w$WH -c$CONN -l7200 -r1200 --sqlapi=storeproc --sleep=10</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="生成测试报告"><a href="#生成测试报告" class="headerlink" title="生成测试报告"></a>生成测试报告</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">src/utils/post_process -l mix.log</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2020/tpcc/</id>
    <link href="https://ilongda.com/2020/tpcc/"/>
    <published>2020-07-05T11:42:57.000Z</published>
    <summary>性能测试 -- DB性能测试-常用3套件-手把手一步一步跑tpcc</summary>
    <title>DB性能测试-常用3套件-手把手一步一步跑tpcc</title>
    <updated>2024-02-02T13:13:42.020Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="性能测试" scheme="https://ilongda.com/categories/Database/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="性能测试" scheme="https://ilongda.com/tags/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"DB性能测试-常用3套件-手把手一步一步跑sysbench","description":"性能测试 -- DB性能测试-常用3套件-手把手一步一步跑sysbench","image":"https://ilongda.com/img/my.jpg","wordCount":1651,"datePublished":"2020-06-28T11:42:57.000Z","dateModified":"2024-02-02T13:13:08.759Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/sysbench/"},"url":"https://ilongda.com/2020/sysbench/","inLanguage":"zh-CN","keywords":["Database","性能测试"],"articleSection":["Database","性能测试"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"DB性能测试-常用3套件-手把手一步一步跑sysbench","item":"https://ilongda.com/2020/sysbench/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>把过去的写的一篇笔记分享一下， 数据库最常用的测试三套件， sysbench – oltp 测试， tpch – olap 测试， tpcc – 事务性能测试。<br>本文手把手 一步一步 run sysbench</p><p>整个过程， 分为</p><ul><li>介绍</li><li>准备工作</li><li>编译</li><li>测试</li><li>疑难杂症</li></ul><span id="more"></span><h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p>下面引用老外的一段话来介绍一下sysbench</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">sysbench is a scriptable multi-threaded benchmark tool based on LuaJIT. It is most frequently used for database benchmarks, but can also be used to create arbitrarily complex workloads that do not involve a database server.</span><br><span class="line"></span><br><span class="line">sysbench comes with the following bundled benchmarks:</span><br><span class="line"></span><br><span class="line">    * oltp_*.lua: a collection of OLTP-like database benchmarks</span><br><span class="line">    * fileio: a filesystem-level benchmark</span><br><span class="line">    * cpu: a simple CPU benchmark</span><br><span class="line">    * memory: a memory access benchmark</span><br><span class="line">    * threads: a thread-based scheduler benchmark</span><br><span class="line">    * mutex: a POSIX mutex benchmark</span><br></pre></td></tr></table></figure><p>今天我们最主要使用的oltp 系列测试</p><h1 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h1><p>安装相应的包</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">yum -y install gcc gcc-c++ autoconf automake make libtool bzr mysql-devel git mysql</span><br><span class="line">yum -y install make automake libtool pkgconfig libaio-devel</span><br><span class="line">yum -y install openssl-devel</span><br></pre></td></tr></table></figure><p>执行如下命令配置Sysbench client，使内核可以使用所有的CPU核处理数据包（默认设置为使用2个核），同时减少CPU核之间的上下文切换。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">sudo sh -c &#x27;for x in /sys/class/net/eth0/queues/rx-*; do echo ffffffff&gt;$x/rps_cpus; done&#x27;</span><br><span class="line">sudo sh -c &quot;echo 32768 &gt; /proc/sys/net/core/rps_sock_flow_entries&quot;</span><br><span class="line">sudo sh -c &quot;echo 4096 &gt; /sys/class/net/eth0/queues/rx-0/rps_flow_cnt&quot;</span><br><span class="line">sudo sh -c &quot;echo 4096 &gt; /sys/class/net/eth0/queues/rx-1/rps_flow_cnt&quot;</span><br></pre></td></tr></table></figure><p>备注： 说明 ffffffff表示使用32个核。请根据实际配置修改，例如ECS为8核，则输入ff。</p><h1 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/akopytov/sysbench.git</span><br><span class="line">##从Git中下载sysbench</span><br><span class="line"></span><br><span class="line">cd sysbench</span><br><span class="line">##打开sysbench目录</span><br><span class="line"></span><br><span class="line">git checkout 1.0.18</span><br><span class="line">##切换到sysbench 1.0.18版本， 也可以不用切换到1.0.18 版本上，直接使用master</span><br><span class="line"></span><br><span class="line">./autogen.sh</span><br><span class="line">##运行autogen.sh</span><br><span class="line"></span><br><span class="line">./configure --prefix=/usr --mandir=/usr/share/man</span><br><span class="line"></span><br><span class="line">make</span><br><span class="line">##编译</span><br><span class="line"></span><br><span class="line">make install</span><br></pre></td></tr></table></figure><h1 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h1><p>sysbench 的测试 通常类似这样,  分为3个阶段， 一个prepare， 一个run， 一个cleanup</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">sysbench --db-driver=mysql --mysql-host=XXX --mysql-port=XXX --mysql-user=XXX --mysql-password=XXX --mysql-db=sbtest --table_size=25000 --tables=250 --events=0 --time=600  oltp_write_only prepare</span><br><span class="line">##准备数据</span><br><span class="line"></span><br><span class="line">sysbench --db-driver=mysql --mysql-host=XXX --mysql-port=XXX --mysql-user=XXX --mysql-password=XXX --mysql-db=sbtest --table_size=25000 --tables=250 --events=0 --time=600   --threads=XXX --percentile=95 --report-interval=1 oltp_write_only run</span><br><span class="line">##运行workload</span><br><span class="line"></span><br><span class="line">sysbench --db-driver=mysql --mysql-host=XXX --mysql-port=XXX --mysql-user=XXX --mysql-password=XXX --mysql-db=sbtest --table_size=25000 --tables=250 --events=0 --time=600   --threads=XXX --percentile=95  oltp_write_only cleanup</span><br><span class="line">##清理</span><br></pre></td></tr></table></figure><p>命令解释</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">--mysql-host       IP</span><br><span class="line">--mysql-port       端口号</span><br><span class="line">--mysql-db         希望链接的数据库</span><br><span class="line">--mysql-user       用户名</span><br><span class="line">--mysql-password   密码</span><br><span class="line">--table_size       每张表初始化的数据数量</span><br><span class="line">--tables           初始化表的数量</span><br><span class="line">--threads          启动的线程</span><br><span class="line">--time             运行时间设为0表示不限制时间</span><br><span class="line">--report-interval  运行期间日志，单位为秒</span><br><span class="line">--events           最大请求数量，定义数量后可以不需要--time选项</span><br><span class="line">--rand-type        访问数据时使用的随机生成函数 可以选择&quot;special&quot;， &quot;uniform&quot;， &quot;gaussian&quot;， &quot;pareto&quot;， 默认special， 早期时uniform</span><br><span class="line">--skip_trx=on      在只读测试中可以选择打开或关闭事务， 默认是打开</span><br></pre></td></tr></table></figure><p>作者写了一个自动测试的脚本， 读者可以从<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2xvbmdkYWZlbmcvdGVzdC90cmVlL21hc3Rlci9zaGVsbC9zeXNiZW5jaA==">https://github.com/longdafeng/test/tree/master/shell/sysbench<i class="fa fa-external-link-alt"></i></span>  将脚本下载下来<br>记得将 脚本放到 sysbench 源码目录下src&#x2F;lua 下面</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">cd sysbench</span><br><span class="line">cd src/lua</span><br><span class="line">wget https://raw.githubusercontent.com/longdafeng/test/master/shell/sysbench/start-sysbench.sh </span><br><span class="line">wget https://raw.githubusercontent.com/longdafeng/test/master/shell/sysbench/start.sh</span><br><span class="line">nohup ./start.sh hostxxx portxxx userxxx passwordxxx dbxxx &gt; run.log 2&gt;&amp;1 &amp;</span><br><span class="line">tail -f run.log</span><br></pre></td></tr></table></figure><p>其中hostxxx, portxxx, userxxx, passwordxxx, dbxxx 修改为真实mysql的参数<br>这个脚本， 是设置不同大表大小， 设置不同随机参数， 设置不同的线程数， 设置是否打开或关闭事务， 依次运行oltp_read_only, oltp_write_only, oltp_read_write 3个集成测试，<br>在src&#x2F;lua 目录下， 还有很多单项测试, 比如insert, point_select, update_index, update_non_index, select_random_points, select_random_ranges 分别是针对不同的场景的测试</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[root@kudu lua]# ls *.lua</span><br><span class="line">bulk_insert.lua  oltp_common.lua  oltp_insert.lua        oltp_read_only.lua   oltp_update_index.lua      oltp_write_only.lua  select_random_points.lua</span><br><span class="line">empty-test.lua   oltp_delete.lua  oltp_point_select.lua  oltp_read_write.lua  oltp_update_non_index.lua  prime-test.lua       select_random_ranges.lua</span><br></pre></td></tr></table></figure><h1 id="疑难杂症"><a href="#疑难杂症" class="headerlink" title="疑难杂症"></a>疑难杂症</h1><h2 id="性能差"><a href="#性能差" class="headerlink" title="性能差"></a>性能差</h2><p>sysbench 是对cpu&#x2F;memory&#x2F;网络非常敏感的测试， 经常遇到 很多客户在测试的过程中，发现性能和预期的差距比较大， 深究后，发现， sysbench的肉机（客户端） 和目标测试数据不在同一个局域网或者一个vpc 内（云上客户）， 对于很多云数据库， 肉机和目标mysql 必须在同一个region， 并且是在同一个vpc， 并且在链接字符串的时候，必须使用私有的链接地址，不能使用公网的链接地址， 公网的链接地址会经过很多跳。</p><p>假设语句是：<br>sysbench oltp_read_write.lua –mysql-host&#x3D;127.0.0.1 –mysql-port&#x3D;3306 –mysql-db&#x3D;sbtest –mysql-user&#x3D;root –mysql-password&#x3D;123456 –table_size&#x3D;200000000 –tables&#x3D;1 –threads&#x3D;500 –events&#x3D;500000 –report-interval&#x3D;10 –time&#x3D;0</p><h2 id="no-such-built-in-test-file-or-module"><a href="#no-such-built-in-test-file-or-module" class="headerlink" title="no such built-in test, file or module"></a>no such built-in test, file or module</h2><p>如果执行的时候提示FATAL: Cannot find benchmark ‘oltp_read_write.lua’: no such built-in test, file or module</p><p>切换到sysbench的源码目录（sysbench.tar.gz解压路径）<br>find .&#x2F; -name oltp_read_write.lua<br>.&#x2F;src&#x2F;lua&#x2F;oltp_read_write.lua<br>接着切换到src&#x2F;lua 目录再执行语句</p><h2 id="“Can-not-connect-to-MySQL-server-Too-many-connections”"><a href="#“Can-not-connect-to-MySQL-server-Too-many-connections”" class="headerlink" title="“Can not connect to MySQL server. Too many connections”"></a>“Can not connect to MySQL server. Too many connections”</h2><p>#如果执行的时候命令行提示“Can not connect to MySQL server. Too many connections”-mysql 1040错误：<br>shell&gt;mysql -uroot -p****<br>mysql&gt;show variables like ‘max_connections’;(查看当前的最大连接数)<br>mysql&gt;set global max_connections&#x3D;1000;(设置最大连接数为1000，可以再次查看是否设置成功)<br>mysql&gt;show variables like ‘max_connections’;(查看当前的最大连接数)<br>mysql&gt;exit</p><h2 id="sysbench-无法运行"><a href="#sysbench-无法运行" class="headerlink" title="sysbench 无法运行"></a>sysbench 无法运行</h2> <figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ldd /usr/bin/sysbench</span><br></pre></td></tr></table></figure><p>正常情况下， sysbench 依赖的所有library 应该都可以resolve 掉， 如果有的时候，没有找到依赖的library， 最常见的是没有找到mysql的library<br>需要安装</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum -y install mysql-devel mysql</span><br></pre></td></tr></table></figure><p>然后再重新编译sysben的源码</p><p>如果问题依旧存在<br>可以尝试， 手动创建连接</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">去根目录找一下：</span><br><span class="line">find / -name &quot;*mysqlclient_r*&quot;</span><br><span class="line">/usr/lib64/mysql/libmysqlclient_r.so.18</span><br><span class="line">/usr/lib64/mysql/libmysqlclient_r.so.18.1.0</span><br><span class="line">库文件是有的，不过带个数字后缀，给库文件建立软链接：</span><br><span class="line">ln -s /usr/lib64/mysql/libmysqlclient_r.so.18 /usr/lib64/mysql/libmysqlclient_r.so</span><br></pre></td></tr></table></figure><p>如果问题还是不能解决， 最后的办法就是从github 上下载mysql的源码， 先对mysql 源码进行编译，安装，然后再重新编译sysbench</p><pre><code># --with-mysql-includes选项指定mysql的include文件夹，里面是一些.h的头文件，比如mysql.h，未安装mysql-community-devel-version是没有include文件夹的# with-mysql-libs选项指定mysql的一些lib，里面是一些.a文件和.so文件，比如libmysqlclient.a，libmysqlclient.so# 比如./configure --prefix=/usr   --with-mysql-includes=/usr/include/mysql --with-mysql-libs=/usr/lib64/mysql</code></pre>]]>
    </content>
    <id>https://ilongda.com/2020/sysbench/</id>
    <link href="https://ilongda.com/2020/sysbench/"/>
    <published>2020-06-28T11:42:57.000Z</published>
    <summary>性能测试 -- DB性能测试-常用3套件-手把手一步一步跑sysbench</summary>
    <title>DB性能测试-常用3套件-手把手一步一步跑sysbench</title>
    <updated>2024-02-02T13:13:08.759Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="性能测试" scheme="https://ilongda.com/categories/Database/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="性能测试" scheme="https://ilongda.com/tags/%E6%80%A7%E8%83%BD%E6%B5%8B%E8%AF%95/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"DB性能测试-常用3套件-手把手一步一步跑TPCH","description":"性能测试 -- DB性能测试-常用3套件-手把手一步一步跑TPCH","image":"http://static-aliyun-doc.oss-cn-hangzhou.aliyuncs.com/assets/img/zh-CN/7761866951/p70167.png","wordCount":1946,"datePublished":"2020-06-22T11:42:57.000Z","dateModified":"2024-02-02T13:13:53.602Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/TPCH/"},"url":"https://ilongda.com/2020/TPCH/","inLanguage":"zh-CN","keywords":["Database","性能测试"],"articleSection":["Database","性能测试"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"DB性能测试-常用3套件-手把手一步一步跑TPCH","item":"https://ilongda.com/2020/TPCH/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>把过去的写的一篇笔记分享一下， 数据库最常用的测试三套件， sysbench – oltp 测试， tpch – olap 测试， tpcc – 事务性能测试。<br>本文手把手 一步一步 run TPCH, 即使从来没有跑过数据库的，也可以直接上手运行TPCH, 本文以运行TPCH on MySQL,  如果读者想要运行tpch 到postgres 或者其他的数据， 可以先参考本博文，然后基于本博文，再到github上搜索相应数据库的TPCH 库 即可。</p><p>整个过程， 分为</p><ul><li>介绍</li><li>编译</li><li>数据生成</li><li>数据加载</li><li>性能测试</li><li>表结构介绍</li></ul><span id="more"></span><h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p>TPC现有的测试标准为：TPC-E、TPC-C、TPC-H、TPC-App。根据这4个测试基准，目前TPC主要包括的4个技术小组委员会：TPC-E 技术小组委员会、TPC-C 技术小组委员会、TPC-H技术小组委员会、TPC-App技术小组委员会。前期TPC使用过但目前已经停止使用的测试标准有：TPC-A、TPC-B（数据库处理能力测试标准）、TPC-D、TPC-R（决策支持系统测试标准，类TPC-H）、TPC-W（Web处理能力测试标准）。</p><p>TPC-H（商业智能计算测试） 是美国交易处理效能委员会(TPC,Transaction Processing Performance Council) 组织制定的用来模拟决策支持类应用的一个测试集.目前,在学术界和工业界普遍采用它来评价决策支持技术方面应用的性能. 这种商业测试可以全方位评测系统的整体商业计算综合能力，对厂商的要求更高，同时也具有普遍的商业实用意义，目前在银行信贷分析和信用卡分析、电信运营分析、税收分析、烟草行业决策分析中都有广泛的应用。</p><p>TPC-H 基准测试是由 TPC-D(由 TPC 组织于 1994 年指定的标准,用于决策支持系统方面的测试基准)发展而来的.TPC-H 用 3NF 实现了一个数据仓库,共包含 8 个基本关系,其数据量可以设定从 1G<del>3T 不等。TPC-H 基准测试包括 22 个查询(Q1</del>Q22),其主要评价指标是各个查询的响应时间,即从提交查询到结果返回所需时间.TPC-H 基准测试的度量单位是每小时执行的查询数( QphH@size)，其中 H 表示每小时系统执行复杂查询的平均次数，size 表示数据库规模的大小,它能够反映出系统在处理查询时的能力.TPC-H 是根据真实的生产运行环境来建模的,这使得它可以评估一些其他测试所不能评估的关键性能参数.总而言之,TPC 组织颁布的TPC-H 标准满足了数据仓库领域的测试需求,并且促使各个厂商以及研究机构将该项技术推向极限。</p><p>详细可以参考 <span class="exturl" data-url="aHR0cDovL3d3dy50cGMub3JnL3RwY19kb2N1bWVudHNfY3VycmVudF92ZXJzaW9ucy9wZGYvdHBjLWhfdjIuMTcuMy5wZGY=">tpch_reference<i class="fa fa-external-link-alt"></i></span></p><h1 id="编译"><a href="#编译" class="headerlink" title="编译"></a>编译</h1><p>下载源码包<span class="exturl" data-url="aHR0cDovL3d3dy50cGMub3JnL3RwY19kb2N1bWVudHNfY3VycmVudF92ZXJzaW9ucy9jdXJyZW50X3NwZWNpZmljYXRpb25zNS5hc3A=">tpch<i class="fa fa-external-link-alt"></i></span><br><img data-src="http://static-aliyun-doc.oss-cn-hangzhou.aliyuncs.com/assets/img/zh-CN/7761866951/p70167.png" alt="tpch_download"></p><ol><li>打开dbgen目录。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cd dbgen</span><br></pre></td></tr></table></figure><ol start="2"><li>复制makefile文件。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cp makefile.suite Makefile</span><br></pre></td></tr></table></figure><ol start="3"><li>修改Makefile文件中的CC、DATABASE、MACHINE、WORKLOAD等参数定义。<br>打开Makefile文件。<br>修改CC、DATABASE、MACHINE、WORKLOAD参数的定义。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">################</span><br><span class="line">## CHANGE NAME OF ANSI COMPILER HERE</span><br><span class="line">################</span><br><span class="line">CC      = gcc</span><br><span class="line"># Current values for DATABASE are: INFORMIX, DB2, ORACLE,</span><br><span class="line">#                                  SQLSERVER, SYBASE, TDAT (Teradata)</span><br><span class="line"># Current values for MACHINE are:  ATT, DOS, HP, IBM, ICL, MVS,</span><br><span class="line">#                                  SGI, SUN, U2200, VMS, LINUX, WIN32</span><br><span class="line"># Current values for WORKLOAD are:  TPCH</span><br><span class="line">DATABASE= MYSQL</span><br><span class="line">MACHINE = LINUX</span><br><span class="line">WORKLOAD = TPCH</span><br></pre></td></tr></table></figure><p>按ECS键，然后输入:wq退出并保存。<br>4. 修改tpcd.h文件，并添加新的宏定义。<br>打开tpcd.h文件。<br>vim tpcd.h<br>添加如下宏定义。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">#ifdef MYSQL</span><br><span class="line">#define GEN_QUERY_PLAN &quot;&quot;</span><br><span class="line">#define START_TRAN &quot;START TRANSACTION&quot;</span><br><span class="line">#define END_TRAN &quot;COMMIT&quot;</span><br><span class="line">#define SET_OUTPUT &quot;&quot;</span><br><span class="line">#define SET_ROWCOUNT &quot;limit %d;\n&quot;</span><br><span class="line">#define SET_DBASE &quot;use %s;\n&quot;</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure><p>按ECS键，然后输入:wq退出并保存。<br>5.  对文件进行编译。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">make</span><br></pre></td></tr></table></figure><p>编译完成后该目录下会生成两个可执行文件：</p><ul><li>dbgen：数据生成工具。在使用InfiniDB官方测试脚本进行测试时，需要用该工具生成tpch相关表数据。</li><li>qgen：SQL生成工具。生成初始化测试查询，由于不同的seed生成的查询不同，为了结果的可重复性，请使用附件提供的22个查询。</li></ul><h1 id="生成数据"><a href="#生成数据" class="headerlink" title="生成数据"></a>生成数据</h1><h2 id="生成测试数据"><a href="#生成测试数据" class="headerlink" title="生成测试数据"></a>生成测试数据</h2><p>可以生成tpch 10g 也可以100g， 甚至1TB, 本例以100g 为例， 100g 的记录数在6亿条左右， 和普通一家中小型公司的大表规格差不多</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">./dbgen -s 100</span><br><span class="line">mkdir tpch100</span><br><span class="line">mv *.tbl tpch100</span><br></pre></td></tr></table></figure><h2 id="生成查询sql"><a href="#生成查询sql" class="headerlink" title="生成查询sql"></a>生成查询sql</h2><ol><li>将qgen与dists.dss复制到queries目录下。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cp qgen queries</span><br><span class="line">cp dists.dss queries</span><br></pre></td></tr></table></figure><ol start="2"><li>使用以下脚本生成查询。<br>在queries 目录下，创建脚本gen.sh</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">#!/usr/bin/bash</span><br><span class="line">for i in &#123;1..22&#125;</span><br><span class="line">do  </span><br><span class="line">  ./qgen -d $i -s 100 &gt; db&quot;$i&quot;.sql</span><br><span class="line">done</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./gen.sh</span><br></pre></td></tr></table></figure><ol start="3"><li>查询sql 进行调整</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">dos2unix *</span><br></pre></td></tr></table></figure><p>去掉生成文件中的”limit -1”, 去掉day 后面的(3), 以q1 为例， sql 如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">-- using default substitutions</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">select</span><br><span class="line">        l_returnflag,</span><br><span class="line">        l_linestatus,</span><br><span class="line">        sum(l_quantity) as sum_qty,</span><br><span class="line">        sum(l_extendedprice) as sum_base_price,</span><br><span class="line">        sum(l_extendedprice * (1 - l_discount)) as sum_disc_price,</span><br><span class="line">        sum(l_extendedprice * (1 - l_discount) * (1 + l_tax)) as sum_charge,</span><br><span class="line">        avg(l_quantity) as avg_qty,</span><br><span class="line">        avg(l_extendedprice) as avg_price,</span><br><span class="line">        avg(l_discount) as avg_disc,</span><br><span class="line">        count(*) as count_order</span><br><span class="line">from</span><br><span class="line">        lineitem</span><br><span class="line">where</span><br><span class="line">        l_shipdate &lt;= date &#x27;1998-12-01&#x27; - interval &#x27;90&#x27; day (3)   --- 把(3) 去掉</span><br><span class="line">group by</span><br><span class="line">        l_returnflag,</span><br><span class="line">        l_linestatus</span><br><span class="line">order by</span><br><span class="line">        l_returnflag,</span><br><span class="line">        l_linestatus;</span><br><span class="line">limit -1;              ---  去掉这行</span><br></pre></td></tr></table></figure><h1 id="加载数据"><a href="#加载数据" class="headerlink" title="加载数据"></a>加载数据</h1><ol><li>下载加载脚本</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mkdir load</span><br><span class="line">cd load</span><br><span class="line">wget https://raw.githubusercontent.com/longdafeng/test/master/shell/tpch/load.sh ./</span><br><span class="line">wget https://raw.githubusercontent.com/longdafeng/test/master/shell/tpch/polar.index.sh ./</span><br><span class="line">chmod +x *</span><br><span class="line">cp ../dss.ri  ../dss.ddl ./</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>因为这个测试里面带了polardb 的创建index 脚本， 读者如果不使用polardb，则可以不用下载polar.index.sh， 并修改load.sh 文件，去掉设置index 的步骤</p><ol start="2"><li>修改dss.ri 脚本<br>dss.ri 主要是设置primary key 和foreign key</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">-- Sccsid:     @(#)dss.ri       2.1.8.1</span><br><span class="line">-- TPCD Benchmark Version 8.0</span><br><span class="line"></span><br><span class="line">CONNECT TO TPCD;           </span><br><span class="line"></span><br><span class="line">--ALTER TABLE TPCD.REGION DROP PRIMARY KEY;</span><br><span class="line">--ALTER TABLE TPCD.NATION DROP PRIMARY KEY;</span><br><span class="line">--ALTER TABLE TPCD.PART DROP PRIMARY KEY;</span><br><span class="line">--ALTER TABLE TPCD.SUPPLIER DROP PRIMARY KEY;</span><br><span class="line">--ALTER TABLE TPCD.PARTSUPP DROP PRIMARY KEY;</span><br><span class="line">--ALTER TABLE TPCD.ORDERS DROP PRIMARY KEY;</span><br><span class="line">--ALTER TABLE TPCD.LINEITEM DROP PRIMARY KEY;</span><br><span class="line">--ALTER TABLE TPCD.CUSTOMER DROP PRIMARY KEY;</span><br></pre></td></tr></table></figure><p>把 其中”CONNECT TO TPCD;  “删除掉， 把所有的”TPCD.” 给去除掉</p><p>如果不想修改dss.ri, 可以直接下载现成的<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2xvbmdkYWZlbmcvdGVzdC90cmVlL21hc3Rlci9zaGVsbC90cGNo">dss.ri<i class="fa fa-external-link-alt"></i></span></p><ol start="3"><li>按照mysql 客户端</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install mysql -y</span><br></pre></td></tr></table></figure><ol start="4"><li>开始加载</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">nohup./load.sh hostxxx portxxx userxxx passwordxxx dbxxx &gt; load.log 2&gt;&amp;1 &amp;</span><br><span class="line">tail -f load.log</span><br></pre></td></tr></table></figure><p>其中hostxxx 为db 地址<br>portxxx 为db 端口<br>userxxx 为用户名   — 需要提前创建好用户名， 对于云上用户， 还需要设置白名单， 把机器ip 白名单设置进去<br>passwordxxx 为用户密码<br>dbxxx 为 要创建的数据库名字，   因为脚本会自动加载在“生成数据” 一节中创建的目录（我们例子中是tpch100），因此数据库名也必须是上一节生成数据创建的目录</p><h1 id="开始测试"><a href="#开始测试" class="headerlink" title="开始测试"></a>开始测试</h1><p>下载 测试脚本  <span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2xvbmdkYWZlbmcvdGVzdC90cmVlL21hc3Rlci9weXRob24vdHBjaA==">https://github.com/longdafeng/test/tree/master/python/tpch<i class="fa fa-external-link-alt"></i></span> </p><p>配置配置文件example.cfg</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line"></span><br><span class="line">    &quot;host&quot;:&quot;xxxx&quot;                 // 数据库机器名</span><br><span class="line">    &quot;port&quot;:&quot;3306&quot;                 // 数据库端口号</span><br><span class="line">    &quot;username&quot;:&quot;xxxxx&quot;            // 数据库用户名</span><br><span class="line">    &quot;password&quot;:&quot;xxxx&quot;             // 数据库密码</span><br><span class="line">    &quot;database&quot;:&quot;xxxxx&quot;            // 数据库 库名</span><br><span class="line">    //input dir</span><br><span class="line">    &quot;input_dir&quot;:&quot;mysql&quot;           // 查询sql 存放的目录，如果是测试mysql 系列，则这里是mysql， 如果是pg，则需要生成pg的查询sql</span><br><span class="line">    </span><br><span class="line">    //output_dir</span><br><span class="line">    &quot;output_dir&quot;:&quot;polardb80&quot;      // 打印日志的目录</span><br><span class="line">    </span><br><span class="line">    //mysql_setting, set mysql variable</span><br><span class="line">    //&quot;mysql_setting&quot;: &quot;set max_parallel_degree=32;&quot;</span><br><span class="line">    &quot;mysql_setting&quot;: &quot;&quot;</span><br><span class="line"></span><br><span class="line">    //query per sql times</span><br><span class="line">    &quot;times_per_sql&quot;:&quot;1&quot;            // 每条sql 执行的次数， 会取平均值</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>运行脚本</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">nohup ./tpch.py -f example.cfg &gt; run.log 2&gt;&amp;1 &amp;</span><br><span class="line">tail -f run.log</span><br></pre></td></tr></table></figure><p>最后进入配置文件“output_dir” 目录下，查看result 文件即可</p>]]>
    </content>
    <id>https://ilongda.com/2020/TPCH/</id>
    <link href="https://ilongda.com/2020/TPCH/"/>
    <published>2020-06-22T11:42:57.000Z</published>
    <summary>性能测试 -- DB性能测试-常用3套件-手把手一步一步跑TPCH</summary>
    <title>DB性能测试-常用3套件-手把手一步一步跑TPCH</title>
    <updated>2024-02-02T13:13:53.602Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"filesort 详细解析","description":"MySQL filesort 排序机制笔记：对比 original 与 modified 算法、max_sort_length 含义及 sort/addon 字段组织方式","image":"https://ilongda.com/img/my.jpg","wordCount":10760,"datePublished":"2020-06-21T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.939Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/mysql-filesort/"},"url":"https://ilongda.com/2020/mysql-filesort/","inLanguage":"zh-CN","keywords":["Database","MySQL"],"articleSection":["Database","MySQL"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"filesort 详细解析","item":"https://ilongda.com/2020/mysql-filesort/"}]}</script><p>这篇文章介绍的非常好， 所以给大家推荐一下<br>【转载】<span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L244OExwbw==">https://blog.csdn.net/n88Lpo<i class="fa fa-external-link-alt"></i></span></p><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>排序（filesort）作为DBA绕不开的话题，也经常有朋友讨论它，比如常见的问题如下：</p><ul><li>排序的时候，用于排序的数据会不会如Innodb一样压缩空字符存储，比如varchar(30)，我只是存储了1个字符是否会压缩，还是按照30个字符计算？</li><li>max_length_for_sort_data&#x2F;max_sort_length 到底是什么含义？</li><li>original filesort algorithm（回表排序） 和 modified filesort algorithm（不回表排序） 的根本区别是什么？</li><li>为什么使用到排序的时候慢查询中的Rows_examined会更大，计算方式到底是什么样的？<br>在MySQL通常有如下算法来完成排序：</li><li>内存排序（优先队列 order by limit 返回少量行常用，提高排序效率，但是注意order by limit n,m 如果n过大可能会涉及到排序算法的切换）</li><li>内存排序（快速排序）</li><li>外部排序（归并排序）<br>但是由于能力有限本文不解释这些算法，并且本文不考虑优先队列算法的分支逻辑，只以快速排序和归并排序作为基础进行流程剖析。</li></ul><p>我们在执行计划中如果出现filesort字样通常代表使用到了排序，但是执行计划中看不出来下面问题：</p><ul><li>是否使用了临时文件。</li><li>是否使用了优先队列。</li><li>是original filesort algorithm（回表排序）还是modified filesort algorithm（不回表排序）。<br>如何查看将在后面进行描述。本文还会给出大量的排序接口供敢兴趣的朋友使用，也给自己留下笔记。</li></ul><span id="more"></span><h1 id="十四、全文总结"><a href="#十四、全文总结" class="headerlink" title="十四、全文总结"></a>十四、全文总结</h1><p>提前将总结列在这里，方便读者快速浏览， 本文写了很多，这里需要做一个详细的总结：<br>总结1 ：排序中一行记录如何组织？</p><ul><li><p>一行排序记录，由sort字段+addon字段 组成，其中sort字段为order by 后面的字段，而addon字段为需要访问的字段，比如‘select a1,a2,a3 from test order by a2,a3’，其中sort字段为‘a2,a3’，addon字段为‘a1,a2,a3’。sort字段中的可变长度字段不能打包（pack）压缩，比如varchar，使用的是定义的大小计算空间，注意这是排序使用空间较大的一个重要因素。</p></li><li><p>如果在计算sort字段空间的时候，某个字段的空间大小大于了max_sort_length大小则按照max_sort_length指定的大小计算。</p></li><li><p>一行排序记录，如果sort字段+addon字段 的长度大于了max_length_for_sort_data的大小，那么addon字段将不会存储，而使用sort字段+ref字段代替，ref字段为主键或者ROWID，这个时候就会使用original filesort algorithm（回表排序）的方式了。</p></li><li><p>如果addon字段包含可变字段比如varchar字段，则会使用打包（pack）技术进行压缩，节省空间。<br>可以参考第3、第4、第5、第6、第8节。<br>总结2：排序使用什么样的方法进行？</p></li><li></li></ul><p>original filesort algorithm（回表排序）</p><p>如果使用的是sort字段+ref字段进行排序，那么必须要回表获取需要的数据，如果排序使用了临时文件（也就是说使用外部归并排序，排序量较大）则会使用批量回表，批量回表会涉及到read_rnd_buffer_size参数指定的内存大小，主要用于排序和结果返回。如果排序没有使用临时文件（内存排序就可以完成，排序量较小）则采用单行回表。</p><p>*<br>modified filesort algorithm（不回表排序）</p><p>如果使用的是sort字段+addon字段进行排序，那么使用不回表排序，所有需要的字段均在排序过程中进行存储，addon字段中的可变长度字段可以进行打包（pack）压缩节省空间。其次sort字段和addon字段中可能有重复的字段，比如例2中，sort字段为a2、a3，addon字段为a1、a2、a3，这是排序使用空间较大的另外一个原因。<br>在OPTIMIZER_TRACE中可以查看到使用了那种方法，参考12节。<br>总结3：每次排序一定会分配sort_buffer_size参数指定的内存大小吗？</p><p>不是这样的，MySQL会做一个初步的计算，通过比较Innodb中聚集索引可能存储的行上限和sort_buffer_size参数指定大小内存可以容纳的行上限，获取它们小值进行确认最终内存分配的大小，目的在于节省内存空间。<br>在OPTIMIZER_TRACE中可以看到使用的内存大小，参考第8、第12节。<br>总结4：关于OPTIMIZER_TRACE中的examined_rows和慢查询中的Rows_examined有什么区别？</p><ul><li>慢查询中的Rows_examined包含了重复计数，重复的部分为where条件过滤后做了排序的部分。</li><li>OPTIMIZER_TRACE中的examined_rows不包含重复计数，为实际Innodb层扫描的行数。<br>可以参考11节。<br>总结5：外部排序临时文件的使用是什么样的？</li></ul><p>实际上一个语句的临时文件不止一个，但是它们都以MY开头，并且都放到了tmpdir目录下，lsof可以看到这种文件。</p><ul><li>临时文件1：用于存储内存排序的结果，以chunk为单位，一个chunk的大小就是sort buffer的大小。</li><li>临时文件2：以前面的临时文件1为基础，做归并排序。</li><li>临时文件3：将最后的归并排序结果存储，去掉sort字段，只保留addon字段（需要访问的字段）或者ref字段（ROWID或者主键），因此它一般会比前面2个临时文件小。<br>但是它们不会同时存在，要么 临时文件1和临时文件2存在，要么 临时文件2和临时文件3存在。对于临时文件的使用可以查看Sort_merge_passes，本值多少会侧面反应出外部排序量的大小。<br>可以参考第10节。<br>总结6：排序使用了哪种算法？</li></ul><p>虽然本文不涉及算法，但是内部排序有2种算法需要知道：</p><ul><li>内存排序（优先队列 order by limit 返回少量行常用，提高排序效率，但是注意order by limit n,m 如果n过大可能会涉及到排序算法的切换）</li><li>内存排序（快速排序）<br>在通过OPTIMIZER_TRACE可以查看是否使用使用了优先队列算法，参考12节。<br>总结7：“Creating sort index”到底是什么状态？</li></ul><p>我们前面讲的全部排序流程都会包含在这个状态下，包括：</p><ul><li>获取排序需要的数据（比如例子中全表扫描从Innodb层获取数据）</li><li>根据where条件过滤数据</li><li>内存排序</li><li>外部排序<br>总结8：如何避免临时文件过大的情况？</li></ul><p>首先应该考虑是否可以使用索引来避免排序，如果不能则需要考虑下面的要点：</p><ul><li>order by 后面的字段满足需求即可，尽可能的少。</li><li>order by 后面涉及的字段尽量为固定长度的字段类型，而不是可变字段类型如varchar。因为sort字段不能压缩。</li><li>不要过大的定义可变字段长度，应该合理定义，例如varchar（10）能够满足需求不要使用varchar（50），这些空间虽然在Innodb层存储会压缩，但是MySQL层确可能使用全长度（比如sort字段）。</li><li>在查询中尽量不要用（select *） 而使用需要查询的字段，这将会减少addon字段的个数，在我另外一个文章还讲述了（select *）的其他的缺点参考：<span class="exturl" data-url="aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC9jZTA2M2UyMDI0YWQ=">https://www.jianshu.com/p/ce063e2024ad<i class="fa fa-external-link-alt"></i></span></li></ul><h1 id="一、从一个问题出发"><a href="#一、从一个问题出发" class="headerlink" title="一、从一个问题出发"></a>一、从一个问题出发</h1><p>这是最近一个朋友遇到的案例，大概意思就是说我的表在Innodb中只有30G左右，为什么使用如下语句进行排序操作后临时文件居然达到了200多G，当然语句很变态，我们可以先不问为什么会有这样的语句，我们只需要研究原理即可，在本文的第13节会进行原因解释和问题重现。<br>临时文件如下：</p><p>下面是这些案例信息：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br></pre></td><td class="code"><pre><span class="line">show create table  t\G</span><br><span class="line">*************************** 1. row ***************************</span><br><span class="line">       Table: t</span><br><span class="line">Create Table: CREATE TABLE `t` (</span><br><span class="line">  `ID` bigint(20) NOT NULL COMMENT &#x27;ID&#x27;,</span><br><span class="line">  `UNLOAD_TASK_NO` varchar(50) NOT NULL ,</span><br><span class="line">  `FORKLIFT_TICKETS_COUNT` bigint(20) DEFAULT NULL COMMENT &#x27;叉车票数&#x27;,</span><br><span class="line">  `MANAGE_STATUS` varchar(20) DEFAULT NULL COMMENT &#x27;管理状态&#x27;,</span><br><span class="line">  `TRAY_BINDING_TASK_NO` varchar(50) NOT NULL ,</span><br><span class="line">  `STATISTIC_STATUS` varchar(50) NOT NULL ,</span><br><span class="line">  `CREATE_NO` varchar(50) DEFAULT NULL ,</span><br><span class="line">  `UPDATE_NO` varchar(50) DEFAULT NULL ,</span><br><span class="line">  `CREATE_NAME` varchar(200) DEFAULT NULL COMMENT &#x27;创建人名称&#x27;,</span><br><span class="line">  `UPDATE_NAME` varchar(200) DEFAULT NULL COMMENT &#x27;更新人名称&#x27;,</span><br><span class="line">  `CREATE_ORG_CODE` varchar(200) DEFAULT NULL COMMENT &#x27;创建组织编号&#x27;,</span><br><span class="line">  `UPDATE_ORG_CODE` varchar(200) DEFAULT NULL COMMENT &#x27;更新组织编号&#x27;,</span><br><span class="line">  `CREATE_ORG_NAME` varchar(1000) DEFAULT NULL COMMENT &#x27;创建组织名称&#x27;,</span><br><span class="line">  `UPDATE_ORG_NAME` varchar(1000) DEFAULT NULL COMMENT &#x27;更新组织名称&#x27;,</span><br><span class="line">  `CREATE_TIME` datetime DEFAULT NULL COMMENT &#x27;创建时间&#x27;,</span><br><span class="line">  `UPDATE_TIME` datetime DEFAULT NULL COMMENT &#x27;更新时间&#x27;,</span><br><span class="line">  `DATA_STATUS` varchar(50) DEFAULT NULL COMMENT &#x27;数据状态&#x27;,</span><br><span class="line">  `OPERATION_DEVICE` varchar(200) DEFAULT NULL COMMENT &#x27;操作设备&#x27;,</span><br><span class="line">  `OPERATION_DEVICE_CODE` varchar(200) DEFAULT NULL COMMENT &#x27;操作设备编码&#x27;,</span><br><span class="line">  `OPERATION_CODE` varchar(50) DEFAULT NULL COMMENT &#x27;操作码&#x27;,</span><br><span class="line">  `OPERATION_ASSIST_CODE` varchar(50) DEFAULT NULL COMMENT &#x27;辅助操作码&#x27;,</span><br><span class="line">  `CONTROL_STATUS` varchar(50) DEFAULT NULL COMMENT &#x27;控制状态&#x27;,</span><br><span class="line">  `OPERATOR_NO` varchar(50) DEFAULT NULL COMMENT &#x27;操作人工号&#x27;,</span><br><span class="line">  `OPERATOR_NAME` varchar(200) DEFAULT NULL COMMENT &#x27;操作人名称&#x27;,</span><br><span class="line">  `OPERATION_ORG_CODE` varchar(50) DEFAULT NULL COMMENT &#x27;操作部门编号&#x27;,</span><br><span class="line">  `OPERATION_ORG_NAME` varchar(200) DEFAULT NULL COMMENT &#x27;操作部门名称&#x27;,</span><br><span class="line">  `OPERATION_TIME` datetime DEFAULT NULL COMMENT &#x27;操作时间&#x27;,</span><br><span class="line">  `OPERATOR_DEPT_NO` varchar(50) NOT NULL COMMENT &#x27;操作人所属部门编号&#x27;,</span><br><span class="line">  `OPERATOR_DEPT_NAME` varchar(200) NOT NULL COMMENT &#x27;操作人所属部门名称&#x27;,</span><br><span class="line">  `FORKLIFT_DRIVER_NAME` varchar(200) DEFAULT NULL ,</span><br><span class="line">  `FORKLIFT_DRIVER_NO` varchar(50) DEFAULT NULL ,</span><br><span class="line">  `FORKLIFT_DRIVER_DEPT_NAME` varchar(200) DEFAULT NULL ,</span><br><span class="line">  `FORKLIFT_DRIVER_DEPT_NO` varchar(50) DEFAULT NULL ,</span><br><span class="line">  `FORKLIFT_SCAN_TIME` datetime DEFAULT NULL ,</span><br><span class="line">  `OUT_FIELD_CODE` varchar(200) DEFAULT NULL,</span><br><span class="line">  PRIMARY KEY (`ID`),</span><br><span class="line">  KEY `IDX_TRAY_BINDING_TASK_NO` (`TRAY_BINDING_TASK_NO`),</span><br><span class="line">  KEY `IDX_OPERATION_ORG_CODE` (`OPERATION_ORG_CODE`),</span><br><span class="line">  KEY `IDX_OPERATION_TIME` (`OPERATION_TIME`)</span><br><span class="line">) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=COMPRESSED KEY_BLOCK_SIZE=8</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">desc </span><br><span class="line">SELECT </span><br><span class="line">    ID,</span><br><span class="line">    UNLOAD_TASK_NO,</span><br><span class="line">    FORKLIFT_TICKETS_COUNT,</span><br><span class="line">    MANAGE_STATUS,</span><br><span class="line">    TRAY_BINDING_TASK_NO,</span><br><span class="line">    STATISTIC_STATUS,</span><br><span class="line">    CREATE_NO,</span><br><span class="line">    UPDATE_NO,</span><br><span class="line">    CREATE_NAME,</span><br><span class="line">    UPDATE_NAME,</span><br><span class="line">    CREATE_ORG_CODE,</span><br><span class="line">    UPDATE_ORG_CODE,</span><br><span class="line">    CREATE_ORG_NAME,</span><br><span class="line">    UPDATE_ORG_NAME,</span><br><span class="line">    CREATE_TIME,</span><br><span class="line">    UPDATE_TIME,</span><br><span class="line">    DATA_STATUS,</span><br><span class="line">    OPERATION_DEVICE,</span><br><span class="line">    OPERATION_DEVICE_CODE,</span><br><span class="line">    OPERATION_CODE,</span><br><span class="line">    OPERATION_ASSIST_CODE,</span><br><span class="line">    CONTROL_STATUS,</span><br><span class="line">    OPERATOR_NO,</span><br><span class="line">    OPERATOR_NAME,</span><br><span class="line">    OPERATION_ORG_CODE,</span><br><span class="line">    OPERATION_ORG_NAME,</span><br><span class="line">    OPERATION_TIME,</span><br><span class="line">    OPERATOR_DEPT_NO,</span><br><span class="line">    OPERATOR_DEPT_NAME,</span><br><span class="line">    FORKLIFT_DRIVER_NAME,</span><br><span class="line">    FORKLIFT_DRIVER_NO,</span><br><span class="line">    FORKLIFT_DRIVER_DEPT_NAME,</span><br><span class="line">    FORKLIFT_DRIVER_DEPT_NO,</span><br><span class="line">    FORKLIFT_SCAN_TIME,</span><br><span class="line">    OUT_FIELD_CODE</span><br><span class="line">FROM</span><br><span class="line">    t</span><br><span class="line">GROUP BY id , UNLOAD_TASK_NO , FORKLIFT_TICKETS_COUNT , </span><br><span class="line">MANAGE_STATUS , TRAY_BINDING_TASK_NO , STATISTIC_STATUS , </span><br><span class="line">CREATE_NO , UPDATE_NO , CREATE_NAME , UPDATE_NAME , </span><br><span class="line">CREATE_ORG_CODE , UPDATE_ORG_CODE , CREATE_ORG_NAME , </span><br><span class="line">UPDATE_ORG_NAME , CREATE_TIME , UPDATE_TIME , DATA_STATUS , </span><br><span class="line">OPERATION_DEVICE , OPERATION_DEVICE_CODE , OPERATION_CODE , </span><br><span class="line">OPERATION_ASSIST_CODE , CONTROL_STATUS , OPERATOR_NO ,</span><br><span class="line">OPERATOR_NAME , OPERATION_ORG_CODE , OPERATION_ORG_NAME , </span><br><span class="line">OPERATION_TIME , OPERATOR_DEPT_NO , OPERATOR_DEPT_NAME , </span><br><span class="line">FORKLIFT_DRIVER_NAME , FORKLIFT_DRIVER_NO , </span><br><span class="line">FORKLIFT_DRIVER_DEPT_NAME , FORKLIFT_DRIVER_DEPT_NO ,</span><br><span class="line">FORKLIFT_SCAN_TIME , OUT_FIELD_CODE;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">+----+-------------+-------------------------+------------+------+---------------+------+---------+------+---------+----------+----------------+</span><br><span class="line">| id | select_type | table                   | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra          |</span><br><span class="line">+----+-------------+-------------------------+------------+------+---------------+------+---------+------+---------+----------+----------------+</span><br><span class="line">|  1 | SIMPLE      | t | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 5381145 |   100.00 | Using filesort |</span><br><span class="line">+----+-------------+-------------------------+------------+------+---------------+------+---------+------+---------+----------+----------------+</span><br><span class="line">1 row in set, 1 warning (0.00 sec)</span><br></pre></td></tr></table></figure><p>也许你会怀疑这个语句有什么用，我们先不考虑功能，我们只考虑为什么它会生成200G的临时文件这个问题。<br>接下来我将分阶段进行排序的流程解析，注意了整个排序的流程均处于状态‘Creating sort index’下面，我们以filesort函数接口为开始进行分析。</p><h1 id="二、测试案例"><a href="#二、测试案例" class="headerlink" title="二、测试案例"></a>二、测试案例</h1><p>为了更好的说明后面的流程我们使用2个除了字段长度不同，其他完全一样的表来说明，但是需要注意这两个表数据量很少，不会出现外部排序，如果涉及外部排序的时候我们需要假设它们数据量很大。其次这里根据original filesort algorithm和modified filesort algorithm进行划分，但是这两种方法还没讲述，不用太多理会。</p><ul><li>original filesort algorithm（回表排序）</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; show create table tests1 \G</span><br><span class="line">*************************** 1. row ***************************</span><br><span class="line">       Table: tests1</span><br><span class="line">Create Table: CREATE TABLE `tests1` (</span><br><span class="line">  `a1` varchar(300) DEFAULT NULL,</span><br><span class="line">  `a2` varchar(300) DEFAULT NULL,</span><br><span class="line">  `a3` varchar(300) DEFAULT NULL</span><br><span class="line">) ENGINE=InnoDB DEFAULT CHARSET=utf8</span><br><span class="line">1 row in set (0.00 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; select * from tests1;</span><br><span class="line">+------+------+------+</span><br><span class="line">| a1   | a2   | a3   |</span><br><span class="line">+------+------+------+</span><br><span class="line">| a    | a    | a    |</span><br><span class="line">| a    | b    | b    |</span><br><span class="line">| a    | c    | c    |</span><br><span class="line">| b    | d    | d    |</span><br><span class="line">| b    | e    | e    |</span><br><span class="line">| b    | f    | f    |</span><br><span class="line">| c    | g    | g    |</span><br><span class="line">| c    | h    | h    |</span><br><span class="line">+------+------+------+</span><br><span class="line">8 rows in set (0.00 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; desc select * from tests1 where a1=&#x27;b&#x27; order by a2,a3;</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                       |</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">|  1 | SIMPLE      | tests1 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    8 |    12.50 | Using where; Using filesort |</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">1 row in set, 1 warning (0.00 sec)</span><br></pre></td></tr></table></figure><ul><li>modified filesort algorithm（不回表排序）</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; desc select * from tests2 where a1=&#x27;b&#x27; order by a2,a3;</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                       |</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">|  1 | SIMPLE      | tests2 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    8 |    12.50 | Using where; Using filesort |</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">1 row in set, 1 warning (0.00 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; show create table tests2 \G</span><br><span class="line">*************************** 1. row ***************************</span><br><span class="line">       Table: tests2</span><br><span class="line">Create Table: CREATE TABLE `tests2` (</span><br><span class="line">  `a1` varchar(20) DEFAULT NULL,</span><br><span class="line">  `a2` varchar(20) DEFAULT NULL,</span><br><span class="line">  `a3` varchar(20) DEFAULT NULL</span><br><span class="line">) ENGINE=InnoDB DEFAULT CHARSET=utf8</span><br><span class="line">1 row in set (0.00 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; select * from tests2;</span><br><span class="line">+------+------+------+</span><br><span class="line">| a1   | a2   | a3   |</span><br><span class="line">+------+------+------+</span><br><span class="line">| a    | a    | a    |</span><br><span class="line">| a    | b    | b    |</span><br><span class="line">| a    | c    | c    |</span><br><span class="line">| b    | d    | d    |</span><br><span class="line">| b    | e    | e    |</span><br><span class="line">| b    | f    | f    |</span><br><span class="line">| c    | g    | g    |</span><br><span class="line">| c    | h    | h    |</span><br><span class="line">+------+------+------+</span><br><span class="line">8 rows in set (0.00 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; desc select * from tests2 where a1=&#x27;b&#x27; order by a2,a3;</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                       |</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">|  1 | SIMPLE      | tests2 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    8 |    12.50 | Using where; Using filesort |</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">1 row in set, 1 warning (0.01 sec)</span><br></pre></td></tr></table></figure><p>整个流程我们从filesort 函数接口开始讨论。下面第3到第10节为排序的主要流程。</p><h1 id="三、阶段1-确认排序字段及顺序"><a href="#三、阶段1-确认排序字段及顺序" class="headerlink" title="三、阶段1 确认排序字段及顺序"></a>三、阶段1 确认排序字段及顺序</h1><p>这里主要将排序顺序存入到Filesort 类的 sortorder中，比如我们例子中的order by a2,a3就是a2和a3列，主要接口为Filesort::make_sortorder，我们按照源码描述为sort字段（源码中为sort_length），显然我们在排序的时候除了sort字段以外，还应该包含额外的字段，到底包含哪些字段就与方法 original filesort algorithm（回表排序） 和 modified filesort algorithm（不回表排序）有关了，下面进行讨论。</p><h1 id="四、阶段2-计算sort字段的长度"><a href="#四、阶段2-计算sort字段的长度" class="headerlink" title="四、阶段2 计算sort字段的长度"></a>四、阶段2 计算sort字段的长度</h1><p>这里主要调用使用sortlength函数，这一步将会带入max_sort_length参数的设置进行判断，默认情况下max_sort_length 为1024字节。<br>这一步大概步骤为：</p><ol><li><p>循环每一个sort字段</p></li><li><p>计算每一个sort字段的长度：公式为 ≈ 定义长度 * 2</p></li></ol><p>比如这里例子中我定义了a1 varchar(300)，那么它的计算长度 ≈ 300 * 2（600），为什么是*2呢，这应该是和Unicode编码有关，这一步可以参考函数my_strnxfrmlen_utf8。同时需要注意这里是约等于，因为源码中还是其他的考虑，比如字符是否为空，但是占用不多不考虑了。<br>3. 带入max_sort_length参数进行计算</p><p>好了有了上面一个sort字段的长度，那么这里就和max_sort_length进行比较，如果这个这个sort字段大于max_sort_length的值，那么以max_sort_length设置为准，这步代码如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">set_if_smaller(sortorder-&gt;length, thd-&gt;variables.max_sort_length);</span><br></pre></td></tr></table></figure><p>因此，如果sort字段的某个字段的超过了max_sort_length设置，那么排序可能不那么精确了。<br>到了这里每个sort字段的长度以及sort字段的总长度已经计算出来，比如前面给的两个不同列子中：</p><ul><li>（a2 varchar(300) a3 varchar(300) order by a2,a3）：每个sort字段约为300*2字节，两个字段的总长度约为1200字节。</li><li>（a2 varchar(20) a3 varchar(20) order by a2,a3）：每个sort字段约为20*2字节，两个字段的总长度约为80字节。<br>并且值得注意的是，这里是按照定义大小，如varchar(300) ，以300个字符来计算长度的，而不是我们通常看到的Innodb中实际占用的字符数量。这是排序使用空间大于Innodb实际数据文件大小的一个原因。</li></ul><p>下面我们以（a2 varchar(300) a3 varchar(300) order by a2,a3）为例实际看看debug的结果如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">(gdb) p sortorder-&gt;field-&gt;field_name</span><br><span class="line">$4 = 0x7ffe7800fadf &quot;a3&quot;</span><br><span class="line">(gdb) p sortorder-&gt;length</span><br><span class="line">$5 = 600</span><br><span class="line">(gdb) p  total_length</span><br><span class="line">$6 = 1202（这里a2,a3 可以为NULL各自加了1个字节）</span><br><span class="line">(gdb) </span><br></pre></td></tr></table></figure><p>可以看出没有问题。<br>4. 循环结束，计算出sort字段的总长度。</p><p>后面我们会看到sort字段不能使用压缩（pack）技术。</p><h1 id="五、阶段3-计算额外字段的空间"><a href="#五、阶段3-计算额外字段的空间" class="headerlink" title="五、阶段3 计算额外字段的空间"></a>五、阶段3 计算额外字段的空间</h1><p>对于排序而言，我们很清楚除了sort字段以外，通常我们需要的是实际的数据，那么无外乎两种方式如下：</p><ul><li>original filesort algorithm：只存储rowid或者主键做为额外的字段，然后进行回表抽取数据。我们按照源码的描述，将这种关联回表的字段叫做ref字段（源码中变量叫做ref_length）。</li><li>modified filesort algorithm：将处于read_set（需要读取的字段）全部放到额外字段中，这样不需要回表读取数据了。我们按照源码的描述，将这些额外存储的字段叫做addon字段（源码中变量叫做addon_length）。<br>这里一步就是要来判断到底使用那种算法，其主要标准就是参数max_length_for_sort_data，其默认大小为1024字节，但是后面会看到这里的计算为（sort字段长度+addon字段的总和）是否超过了max_length_for_sort_data。其次如果使用了modified filesort algorithm算法，那么将会对addon字段的每个字段做一个pack（打包），主要目的在于压缩那些为空的字节，节省空间。<br>这一步的主要入口函数为Filesort::get_addon_fields下面是步骤解析。</li></ul><ol><li><p>循环本表全部字段</p></li><li><p>根据read_set过滤出不需要存储的字段</p></li></ol><p>这里如果不需要访问到的字段自然不会包含在其中，下面这段源码过滤代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">if (!bitmap_is_set(read_set, field-&gt;field_index)) //是否在read set中</span><br><span class="line">      continue;</span><br></pre></td></tr></table></figure><ol start="3"><li>获取字段的长度</li></ol><p>这里就是实际的长度了比如我们的a1 varchar(300)，且字符集为UTF8，那么其长度≈ 300*3 （900）。<br>4. 获取可以pack（打包）字段的长度</p><p>和上面不同，对于int这些固定长度类型的字段，只有可变长度的类型的字段才需要进行打包技术。<br>5. 循环结束，获取addon字段的总长度，获取可以pack（打包）字段的总长度</p><p>循环结束后可以获取addon字段的总长度，但是需要注意addon字段和sort字段可能包含重复的字段，比如例2中sort字段为a2、a3，addon字段为a1、a2、a3。<br>如果满足如下条件：<br>addon字段的总长度+sort字段的总长度 &gt; max_length_for_sort_data<br>那么将使用original filesort algorithm（回表排序）的方式，否则使用modified filesort algorithm的方式进行。下面是这一句代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">  if (total_length + sortlength &gt; max_length_for_sort_data) //如果长度大于了max_length_for_sort_data 则退出了</span><br><span class="line">  &#123;</span><br><span class="line">    DBUG_ASSERT(addon_fields == NULL);</span><br><span class="line">    return NULL;</span><br><span class="line">//返回NULL值 不打包了 使用 original filesort algorithm（回表排序）</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><p>我们在回到第2节例子中的第1个案例，因为我们对a1,a2,a3都是需要访问的，且他们的大小均为varchar(300) UTF8，那么addon字段长度大约为300 * 3 * 3&#x3D;2700字节 ，其次我们前面计算了sort字段大约为1202字节，因此 2700+1202 是远远大于max_length_for_sort_data的默认设置1024字节的，因此会使用original filesort algorithm方式进行排序。</p><p>如果是第2节例子中的第2个案例呢，显然要小很多了（每个字段varchar（20）），大约就是20 * 3 * 3（addon字段）+82（sort字段） 它是小于1024字节的，因此会使用modified filesort algorithm的排序方式，并且这些addon字段基本都可以使用打包（pack）技术，来节省空间。但是需要注意的是无论如何（sort字段）是不能进行打包（pack）的，而固定长度类型不需要打包（pack）压缩空间。</p><h1 id="六、阶段4-确认每行的长度"><a href="#六、阶段4-确认每行的长度" class="headerlink" title="六、阶段4 确认每行的长度"></a>六、阶段4 确认每行的长度</h1><p>有了上面的就计算后每一行的长度（如果可以打包是打包前的长度），下面就是这个计算过程。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">if (using_addon_fields()) </span><br><span class="line">//如果使用了 打包技术  检测 addon_fields 数组是否存在  使用modified filesort algorithm算法 不回表排序</span><br><span class="line">  &#123;</span><br><span class="line">    res_length= addon_length; //总的长度  3个 varchar(300) uft8 为 3*300*3</span><br><span class="line">  &#125;</span><br><span class="line">  else //使用original filesort algorithm算法</span><br><span class="line">  &#123;</span><br><span class="line">    res_length= ref_length;   //rowid(主键长度) </span><br><span class="line">    /* </span><br><span class="line">      The reference to the record is considered </span><br><span class="line">      as an additional sorted field</span><br><span class="line">    */</span><br><span class="line">    sort_length+= ref_length;  //实际上就是rowid(主键) +排序字段长度  回表排序</span><br><span class="line">  &#125;</span><br><span class="line">  /*</span><br><span class="line">    Add hash at the end of sort key to order cut values correctly.</span><br><span class="line">    Needed for GROUPing, rather than for ORDERing.</span><br><span class="line">  */</span><br><span class="line">  if (use_hash)</span><br><span class="line">    sort_length+= sizeof(ulonglong);</span><br><span class="line"></span><br><span class="line">  rec_length= sort_length + addon_length; </span><br><span class="line">//modified filesort algorithm sort_length 为排序键长度 addon_lenth 为访问字段长度，original filesort algorithm rowid(主键) +排序字段长度 ，因为addon_length为0</span><br></pre></td></tr></table></figure><p>好了我们稍微总结一下：</p><ul><li>original filesort algorithm：每行长度为sort字段的总长度+ref字段长度（主键或者rowid）。</li><li>modified filesort algorithm：每行的长度为sort字段的总长度+addon字段的长度（需要访问的字段总长度）。<br>当然到底使用那种算法参考上一节。但是要注意了对于varchar这种可变长度是以定义的大小为准了，比如UTF8 varchar（300）就是300*3&#x3D; 900 而不是实际存储的大小，而固定长度没有变化。</li></ul><p>好了，还是回头看看第2节的两个例子，分别计算它们的行长度：</p><ul><li>例子1：根据我们的计算，它将使用original filesort algorithm排序方式，最终的计算行长度应该为（sort字段长度+rowid长度）及 ≈ 1202+6 字节，下面是debug的结果：</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(gdb) p rec_length</span><br><span class="line">$1 = 1208</span><br></pre></td></tr></table></figure><ul><li>例子2：根据我们的计算，它将使用modified filesort algorithm排序方式，最终计算行长度应该为（sort字段长度+addon字段长度）及 ≈ 82 + 20 * 3 * 3 （结果为262），注意这里是约等于没有计算非空等因素和可变长度因素，下面是debug的结果：</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">(gdb) p rec_length</span><br><span class="line">$2 = 266</span><br></pre></td></tr></table></figure><p>可以看出误差不大。</p><h1 id="七、阶段5-确认最大内存分配"><a href="#七、阶段5-确认最大内存分配" class="headerlink" title="七、阶段5 确认最大内存分配"></a>七、阶段5 确认最大内存分配</h1><p>这里的分配内存就是参数sort_buffer_size大小有关了。但是是不是每次都会分配至少sort_buffer_size大小的内存的呢？其实不是，MySQL会判断是否表很小的情况，也就是做一个简单的运算，目的在于节省内存的开销，这里我们将来描述。</p><ol><li>大概计算出Innodb层主键叶子结点的行数</li></ol><p>这一步主要通过（聚集索引叶子结点的空间大小&#x2F;聚集索引每行大小 * 2）计算出一个行的上限，调入函数ha_innobase::estimate_rows_upper_bound，源码如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> num_rows= table-&gt;file-&gt;estimate_rows_upper_bound(); </span><br><span class="line">//上限来自于Innodb 叶子聚集索引叶子结点/聚集索引长度 *2</span><br></pre></td></tr></table></figure><p>然后将结果存储起来，如果表很小那么这个值会非常小。<br>2.根据前面计算的每行长度计算出sort buffer可以容下的最大行数</p><p>这一步将计算sort buffer可以容纳的最大行数如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ha_rows keys= memory_available / (param.rec_length + sizeof(char*));</span><br><span class="line">//可以排序的 行数 sort buffer 中最大 可以排序的行数</span><br></pre></td></tr></table></figure><p>3.对比两者的最小值，作为分配内存的标准</p><p>然后对比两个值以小值为准，如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">param.max_keys_per_buffer= (uint) min(num_rows &gt; 0 ? num_rows : 1, keys);</span><br><span class="line">//存储行数上限 和 可以排序 行数的 小值</span><br></pre></td></tr></table></figure><p>4.根据结果分配内存</p><p>分配如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">table_sort.alloc_sort_buffer(param.max_keys_per_buffer, param.rec_length);</span><br></pre></td></tr></table></figure><p>也就是根据总的计算出的行长度和计算出的行数进行分配。</p><h1 id="八、阶段6-读取数据，进行内存排序"><a href="#八、阶段6-读取数据，进行内存排序" class="headerlink" title="八、阶段6 读取数据，进行内存排序"></a>八、阶段6 读取数据，进行内存排序</h1><p>到这里准备工作已经完成了，接下就是以行为单位读取数据了，然后对过滤掉where条件的剩下的数据进行排序。如果需要排序的数据很多，那么等排序内存写满后会进行内存排序，然后将排序的内容写入到排序临时文件中，等待下一步做外部的归并排序。作为归并排序而言，每一个归并的文件片段必须是排序好的，否则归并排序是不能完成的，因此写满排序内存后需要做内存排序。如果写不满呢，那么做一次内存排序就好了。下面我们来看看这个过程，整个过程集中在find_all_keys函数中。</p><ol><li>读取需要的数据</li></ol><p>实际上在这一步之前还会做read_set的更改，因为对于original filesort algorithm（回表排序）的算法来讲不会读取全部需要的字段，为了简单起见不做描述了。</p><p>这一步就是读取一行数据了，这里会进入Innodb层读取数据，具体流程不做解释了，下面是这一行代码：<br>error&#x3D; file-&gt;ha_rnd_next(sort_form-&gt;record[0]); &#x2F;&#x2F;读取一行数据<br>2. 将Rows_examined 加1</p><p>这里这个指标对应的就是慢查中的Rows_examined了，这个指标在有排序的情况下会出现重复计算的情况，但是这里还是正确的，重复的部分后面再说。<br>3. 过滤掉where条件</p><p>这里将会过滤掉where条件中不满足条件的行，代码如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">if (!error &amp;&amp; !qep_tab-&gt;skip_record(thd, &amp;skip_record) &amp;&amp; !skip_record) </span><br><span class="line">//这里做where过滤条件 的比较 </span><br></pre></td></tr></table></figure><ol start="4"><li>将行数据写入到sort buffer中</li></ol><p>这一步将会把数据写入到sort buffer中，需要注意这里不涉及排序操作，只是存储数据到内存中。其中分为了2部分：</p><ul><li>写入sort字段。如果是original filesort algorithm那么rowid（主键）也包含在其中了。</li><li>写入addon字段，这是modified filesort algorithm才会有的，在写入之前还会调用Field::pack对可以打包（pack）的字段进行压缩操作。对于varchar字段的打包函数就是Field_varstring::pack，简单的说存储的是实际的大小，而非定义的大小。<br>整个过程位于find_all_keys-&gt;Sort_param::make_sortkey 函数中。这一步还涉及到了我们非常关心的一个问题，到底排序的数据如何存储的问题，需要仔细阅读。</li></ul><p>下面我们就debug一下第2节中两个例子的不同存储方式。既然要去看内存中的数据，我们只要看它最终拷贝的内存数据是什么就好了，那么真相将会大白，我们只需要将断点放到find_all_keys函数上，做完一行数据的Sort_param::make_sortkey操作后看内存就行了，如下：</p><ul><li>例子1（字段都是varchar（300））：它将使用original filesort algorithm（回表排序）的方式，最终应该存储的是sort字段（a2，a3）+rowid。</li></ul><p>排序的结果如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; select * from test.tests1 where a1=&#x27;b&#x27; order by a2,a3;</span><br><span class="line">+------+------+------+</span><br><span class="line">| a1   | a2   | a3   |</span><br><span class="line">+------+------+------+</span><br><span class="line">| b    | d    | d    |</span><br><span class="line">| b    | e    | e    |</span><br><span class="line">| b    | f    | f    |</span><br><span class="line">+------+------+------+</span><br><span class="line">3 rows in set (9.06 sec)</span><br></pre></td></tr></table></figure><p>我们以第二行为查看目标<br>由于篇幅的关系，我展示其中的一部分，因为这里大约有1200多个字节，如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">(gdb) x/1300bx start_of_rec</span><br><span class="line">0x7ffe7ca79998: 0x01    0x00    0x45    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">0x7ffe7ca799a0: 0x20    0x00    0x20    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">0x7ffe7ca799a8: 0x20    0x00    0x20    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">0x7ffe7ca799b0: 0x20    0x00    0x20    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">0x7ffe7ca799b8: 0x20    0x00    0x20    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">0x7ffe7ca799c0: 0x20    0x00    0x20    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">0x7ffe7ca799c8: 0x20    0x00    0x20    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">...</span><br></pre></td></tr></table></figure><p>这后面还有大量的0X20 0X00<br>我们看到了大量的0X20 0X00，这正是占位符号，实际有用的数据也就只有0x45 0x00这两个字节了，而0x45正是我们的大写字母E，也就是数据中的e，这和比较字符集有关。这里的0X20 0X00占用了大量的空间，我们最初计算sort 字段大约为1200字节，实际上只有少量的几个字节有用。<br>这里对于sort字段而言，比实际存储的数据大得多。</p><ul><li>例子2（字段都是varchar（20））：它将使用modified filesort algorithm，最终应该存储的是sort字段（a2，a3）+addon字段（需要的字段，这里就是a1，a2，a3）<br>排序的结果如下：</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; select * from test.tests2 where a1=&#x27;b&#x27; order by a2,a3;</span><br><span class="line">+------+------+------+</span><br><span class="line">| a1   | a2   | a3   |</span><br><span class="line">+------+------+------+</span><br><span class="line">| b    | d    | d    |</span><br><span class="line">| b    | e    | e    |</span><br><span class="line">| b    | f    | f    |</span><br><span class="line">+------+------+------+</span><br></pre></td></tr></table></figure><p>我们以第一行为查看目标<br>这里数据不大，通过压缩后只有91个字节了，我们整体查看如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">(gdb) p rec_sz</span><br><span class="line">$6 = 91</span><br><span class="line">(gdb) x/91x start_of_rec </span><br><span class="line">0x7ffe7c991bc0: 0x01    0x00    0x44    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">0x7ffe7c991bc8: 0x20    0x00    0x20    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">0x7ffe7c991bd0: 0x20    0x00    0x20    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">0x7ffe7c991bd8: 0x20    0x00    0x20    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">0x7ffe7c991be0: 0x20    0x00    0x20    0x00    0x20    0x00    0x20    0x00</span><br><span class="line">0x7ffe7c991be8: 0x20    0x01    0x00    0x44    0x00    0x20    0x00    0x20</span><br><span class="line">0x7ffe7c991bf0: 0x00    0x20    0x00    0x20    0x00    0x20    0x00    0x20</span><br><span class="line">0x7ffe7c991bf8: 0x00    0x20    0x00    0x20    0x00    0x20    0x00    0x20</span><br><span class="line">0x7ffe7c991c00: 0x00    0x20    0x00    0x20    0x00    0x20    0x00    0x20</span><br><span class="line">0x7ffe7c991c08: 0x00    0x20    0x00    0x20    0x00    0x20    0x00    0x20</span><br><span class="line">0x7ffe7c991c10: 0x00    0x20    0x07    0x00    0x00    0x01    0x62    0x01</span><br><span class="line">0x7ffe7c991c18: 0x64    0x01    0x64</span><br></pre></td></tr></table></figure><p>这就是整行记录了，我们发现对于sort字段而言没有压缩，依旧是0x20 0x00占位，而对于addon字段（需要的字段，这里就是a1，a2，a3）而言，这里小了很多，因为做了打包（pack）即：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">0x01 0x62：数据b</span><br><span class="line"></span><br><span class="line">0x01 0x64：数据d</span><br><span class="line"></span><br><span class="line">0x01 0x64：数据d</span><br></pre></td></tr></table></figure><p>而0x01应该就是长度了。<br>不管怎么说，对于sort字段而言依旧比实际存储的数据大很多。<br>5. 如果sort buffer存满，对sort buffer中的数据进行排序，然后写入到临时文件</p><p>如果需要排序的数据量很大的话，那么sort buffer肯定是不能容下的，因此如果写满后就进行一次内存排序操作，然后将排序好的数据写入到外部排序文件中去，这叫做一个chunk。外部文件的位置由tmpdir参数指定，名字以MY开头，注意外部排序通常需要2个临时文件，这里是第1个用于存储内存排序结果的临时文件，以chunk的方式写入。如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">if (fs_info-&gt;isfull()) //如果sort buffer满了  并且sort buffer已经排序完成</span><br><span class="line">        &#123;</span><br><span class="line">          if (write_keys(param, fs_info, idx, chunk_file, tempfile)) //写入到物理文件 完成内存排序   如果内存不会满这里不会做 会在create_sort_index 中排序完成</span><br><span class="line">          &#123;</span><br><span class="line">            num_records= HA_POS_ERROR;</span><br><span class="line">            goto cleanup;</span><br><span class="line">          &#125;</span><br><span class="line">          idx= 0;</span><br><span class="line">          indexpos++;</span><br><span class="line">        &#125;    </span><br></pre></td></tr></table></figure><p>最终会调入write_keys函数进行排序和写入外部排序文件，这里核心就是先排序，然后循环每条排序文件写入到外部排序文件。下面我来验证一下写入临时文件的长度，我将第2节中的例子2数据扩大了N倍后，让其使用外部文件排序，下面是验证结果，断点write_keys即可：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1161        if (my_b_write(tempfile, record, rec_length))</span><br><span class="line">(gdb) p rec_length</span><br><span class="line">$8 = 91</span><br></pre></td></tr></table></figure><p>可以每行的长度还是91字节（打包压缩后），和前面看到的长度一致，说明这些数据会完完整整的写入到外部排序文件，这显然会比我们想象的大得多。<br>好了到这里数据已经找出来了，如果超过sort buffer的大小，外部排序需要的结果已经存储在临时文件1了，并且它是分片（chunk）存储到临时文件的，它以MY开头。</p><h1 id="九、阶段7-排序方式总结输出"><a href="#九、阶段7-排序方式总结输出" class="headerlink" title="九、阶段7 排序方式总结输出"></a>九、阶段7 排序方式总结输出</h1><p>这里对上面的排序过程做了一个阶段性的总结，代码如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Opt_trace_object(trace, &quot;filesort_summary&quot;)</span><br><span class="line">    .add(&quot;rows&quot;, num_rows)</span><br><span class="line">    .add(&quot;examined_rows&quot;, param.examined_rows)</span><br><span class="line">    .add(&quot;number_of_tmp_files&quot;, num_chunks)</span><br><span class="line">    .add(&quot;sort_buffer_size&quot;, table_sort.sort_buffer_size())</span><br><span class="line">    .add_alnum(&quot;sort_mode&quot;,</span><br><span class="line">               param.using_packed_addons() ?</span><br><span class="line">               &quot;&lt;sort_key, packed_additional_fields&gt;&quot; :</span><br><span class="line">               param.using_addon_fields() ?</span><br><span class="line">               &quot;&lt;sort_key, additional_fields&gt;&quot; : &quot;&lt;sort_key, rowid&gt;&quot;); </span><br></pre></td></tr></table></figure><p>我们解析一下：</p><ul><li>rows：排序的行数，也就是应用where过滤条件后剩下的行数。</li><li>examined_rows：Innodb层扫描的行数，注意这不是慢查询中的Rows_examined，这里是准确的结果，没有重复计数。</li><li>number_of_tmp_files：外部排序时，用于保存结果的临时文件的chunk数量，每次sort buffer满排序后写入到一个chunk，但是所有chunk共同存在于一个临时文件中。</li><li>sort_buffer_size：内部排序使用的内存大小，并不一定是sort_buffer_size参数指定的大小。</li><li>sort_mode：这里解释如下</li></ul><ol><li>sort_key, packed_additional_fields：使用了modified filesort algorithm（不回表排序） ，并且有打包（pack）的字段，通常为可变字段比如varchar。</li><li>sort_key, additional_fields：使用了modified filesort algorithm（不回表排序），但是没有需要打包（pack）的字段，比如都是固定长度字段。</li><li>sort_key, rowid：使用了original filesort algorithm（回表排序）。</li></ol><h1 id="十、阶段8-进行最终排序"><a href="#十、阶段8-进行最终排序" class="headerlink" title="十、阶段8 进行最终排序"></a>十、阶段8 进行最终排序</h1><p>这里涉及2个部分如下：</p><ul><li>如果sort buffer不满，则这里开始进行排序，调入函数save_index。</li><li>如果sort buffer满了，则进行归并排序，调入函数merge_many_buff-&gt;merge_buffers，最后调入merge_index完成归并排序。<br>对于归并排序来讲，这里可能会生成另外2个临时文件用于存储最终排序的结果，它们依然以MY开头，且依然是存储在tmpdir参数指定的位置。因此在外部排序中将可能会生成3个临时文件，总结如下：</li><li>临时文件1：用于存储内存排序的结果，以chunk为单位，一个chunk的大小就是sort buffer的大小。</li><li>临时文件2：以前面的临时文件1为基础，做归并排序。</li><li>临时文件3：将最后的归并排序结果存储，去掉sort字段，只保留addon字段（需要访问的字段）或者ref字段（ROWID或者主键），因此它一般会比前面2个临时文件小。<br>但是它们不会同时存在，要么 临时文件1和临时文件2存在，要么 临时文件2和临时文件3存在。<br>这个很容易验证，将断点放到merge_buffers和merge_index上就可以验证了，如下：<br>临时文件1和临时文件2同时存在：</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[root@gp1 test]# lsof|grep tmp/MY</span><br><span class="line">mysqld     8769     mysql   70u      REG              252,3   79167488    2249135 /mysqldata/mysql3340/tmp/MYt1QIvr (deleted)</span><br><span class="line">mysqld     8769     mysql   71u      REG              252,3   58327040    2249242 /mysqldata/mysql3340/tmp/MY4CrO4m (deleted)</span><br></pre></td></tr></table></figure><p>临时文件2和临时文件3共同存在：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[root@gp1 test]# lsof|grep tmp/MY</span><br><span class="line">mysqld     8769     mysql   70u      REG              252,3     360448    2249135 /mysqldata/mysql3340/tmp/MYg109Wp (deleted)</span><br><span class="line">mysqld     8769     mysql   71u      REG              252,3   79167488    2249242 /mysqldata/mysql3340/tmp/MY4CrO4m (deleted)</span><br></pre></td></tr></table></figure><p>但是由于能力有限对于归并排序的具体过程我并没有仔细学习了，这里给一个大概的接口。注意这里每次调用merge_buffers将会增加Sort_merge_passes 1次，应该是归并的次数，这个值增量的大小可以侧面反映出外部排序使用临时文件的大小。</p><h1 id="十一、排序的其他问题"><a href="#十一、排序的其他问题" class="headerlink" title="十一、排序的其他问题"></a>十一、排序的其他问题</h1><p>这里将描述2个额外的排序问题。<br>1、original filesort algorithm（回表排序）的回表</p><p>最后对于original filesort algorithm（回表排序）排序方式而言，可能还需要做一个回表获取数据的操作，这一步可能会用到参数read_rnd_buffer_size定义的内存大小。<br>比如我们第2节中第1个例子将会使用到original filesort algorithm（回表排序），但是对于回表操作有如下标准：</p><ul><li>如果没有使用到外部排序临时文件则说明排序量不大，则使用普通的回表方式，调入函数rr_from_pointers，也就是单行回表方式。</li><li>如果使用到了使用到外部排序临时文件则说明排序量较大，需要使用到批量回表方式，这个时候大概的步骤就是读取rowid（主键）排序，然后批量回表，这将会在read_rnd_buffer_size指定的内存中完成，调入函数rr_from_cache。这也是一种优化方式，因为回表一般是散列的，代价很大。<br>2、关于排序中Rows_examined的计算</li></ul><p>首先这个值我说的是慢查询的中的Rows_examined，在排序中会出现重复计数的可能，前面第8节已经说明了一下，这个值在第8节还是正确的，但是最后符合where条件的数据在返回的时候还会调用函数evaluate_join_record，结果Rows_examined会增加符合where条件的行数。还是以我们第2节的两个例子为例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; select * from test.tests1 where a1=&#x27;b&#x27; order by a2,a3;</span><br><span class="line">+------+------+------+</span><br><span class="line">| a1   | a2   | a3   |</span><br><span class="line">+------+------+------+</span><br><span class="line">| b    | d    | d    |</span><br><span class="line">| b    | e    | e    |</span><br><span class="line">| b    | f    | f    |</span><br><span class="line">+------+------+------+</span><br><span class="line">3 rows in set (5.11 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; select * from test.tests2 where a1=&#x27;b&#x27; order by a2,a3;</span><br><span class="line">+------+------+------+</span><br><span class="line">| a1   | a2   | a3   |</span><br><span class="line">+------+------+------+</span><br><span class="line">| b    | d    | d    |</span><br><span class="line">| b    | e    | e    |</span><br><span class="line">| b    | f    | f    |</span><br><span class="line">+------+------+------+</span><br><span class="line">3 rows in set (5.28 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; desc select * from tests2 where a1=&#x27;b&#x27; order by a2,a3;</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                       |</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">|  1 | SIMPLE      | tests2 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    8 |    12.50 | Using where; Using filesort |</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">1 row in set, 1 warning (0.00 sec)</span><br><span class="line"></span><br><span class="line">8 rows in set (0.00 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; desc select * from tests2 where a1=&#x27;b&#x27; order by a2,a3;</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">| id | select_type | table  | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra                       |</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">|  1 | SIMPLE      | tests2 | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    8 |    12.50 | Using where; Using filesort |</span><br><span class="line">+----+-------------+--------+------------+------+---------------+------+---------+------+------+----------+-----------------------------+</span><br><span class="line">1 row in set, 1 warning (0.01 sec)</span><br></pre></td></tr></table></figure><p>慢查询如下，不要纠结时间（因为我故意debug停止了一会），我们只关注Rows_examined，如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"># Time: 2019-12-23T12:03:26.108529+08:00</span><br><span class="line"># User@Host: root[root] @ localhost []  Id:     4</span><br><span class="line"># Schema:   Last_errno: 0  Killed: 0</span><br><span class="line"># Query_time: 5.118098  Lock_time: 0.000716  Rows_sent: 3  Rows_examined: 11  Rows_affected: 0</span><br><span class="line"># Bytes_sent: 184</span><br><span class="line">SET timestamp=1577073806;</span><br><span class="line">select * from test.tests1 where a1=&#x27;b&#x27; order by a2,a3;</span><br><span class="line"># Time: 2019-12-23T12:03:36.138274+08:00</span><br><span class="line"># User@Host: root[root] @ localhost []  Id:     4</span><br><span class="line"># Schema:   Last_errno: 0  Killed: 0</span><br><span class="line"># Query_time: 5.285573  Lock_time: 0.000640  Rows_sent: 3  Rows_examined: 11  Rows_affected: 0</span><br><span class="line"># Bytes_sent: 184</span><br><span class="line">SET timestamp=1577073816;</span><br><span class="line">select * from test.tests2 where a1=&#x27;b&#x27; order by a2,a3;</span><br></pre></td></tr></table></figure><p>我们可以看到Rows_examined都是11，为什么是11呢？显然我们要扫描总的行数为8（这里是全表扫描，表总共8行数据），然后过滤后需要排序的结果为3条数据，这3条数据会重复计数一次。因此就是8+3&#x3D;11，也就是说有3条数据重复计数了。</p><h1 id="十二、通过OPTIMIZER-TRACE查看排序结果"><a href="#十二、通过OPTIMIZER-TRACE查看排序结果" class="headerlink" title="十二、通过OPTIMIZER_TRACE查看排序结果"></a>十二、通过OPTIMIZER_TRACE查看排序结果</h1><p>要使用OPTIMIZER_TRACE只需要“SET optimizer_trace&#x3D;”enabled&#x3D;on”;”，跑完语句后查看information_schema.OPTIMIZER_TRACE即可。</p><p>前面第9节我们解释了排序方式总结输出的含义，这里我们来看看具体的结果，我们还是以第2节的2个例子为例：</p><ul><li>例1：</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&quot;filesort_priority_queue_optimization&quot;: &#123;</span><br><span class="line">  &quot;usable&quot;: false,</span><br><span class="line">  &quot;cause&quot;: &quot;not applicable (no LIMIT)&quot;</span><br><span class="line">&#125;,</span><br><span class="line">&quot;filesort_execution&quot;: [</span><br><span class="line">],</span><br><span class="line">&quot;filesort_summary&quot;: &#123;</span><br><span class="line">  &quot;rows&quot;: 3,</span><br><span class="line">  &quot;examined_rows&quot;: 8,</span><br><span class="line">  &quot;number_of_tmp_files&quot;: 0,</span><br><span class="line">  &quot;sort_buffer_size&quot;: 1285312,</span><br><span class="line">  &quot;sort_mode&quot;: &quot;&lt;sort_key, rowid&gt;&quot;</span><br></pre></td></tr></table></figure><ul><li>例2：</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">&quot;filesort_priority_queue_optimization&quot;: &#123;</span><br><span class="line">  &quot;usable&quot;: false,</span><br><span class="line">  &quot;cause&quot;: &quot;not applicable (no LIMIT)&quot;</span><br><span class="line">&#125;,</span><br><span class="line">&quot;filesort_execution&quot;: [</span><br><span class="line">],</span><br><span class="line">&quot;filesort_summary&quot;: &#123;</span><br><span class="line">  &quot;rows&quot;: 3,</span><br><span class="line">  &quot;examined_rows&quot;: 8,</span><br><span class="line">  &quot;number_of_tmp_files&quot;: 0,</span><br><span class="line">  &quot;sort_buffer_size&quot;: 322920,</span><br><span class="line">  &quot;sort_mode&quot;: &quot;&lt;sort_key, packed_additional_fields&gt;&quot;</span><br></pre></td></tr></table></figure><p>现在我们清楚了，这些总结实际上是在执行阶段生成的，需要注意几点如下：</p><ul><li>这里的examined_rows和慢查询中的Rows_examined不一样，因为这里不会有重复计数，是准确的。</li><li>这里还会说明是否使用了优先队列排序即“filesort_priority_queue_optimization”部分。</li><li>通过“sort_buffer_size”可以发现，这里并没有分配参数sort_buffer_size指定的大小，节约了内存，这在第7节说明了。<br>其他指标在第9节已经说明过了，不在描述。</li></ul><h1 id="十三、回到问题本身"><a href="#十三、回到问题本身" class="headerlink" title="十三、回到问题本身"></a>十三、回到问题本身</h1><p>好了，大概的流程我描述了一遍，这些流程都是主要流程，实际上的流程复杂很多。那么我们回到最开始的案例上来。他的max_sort_length和max_length_for_sort_data均为默认值1024。<br>案例中的group by实际就是一个排序操作，我们从执行计划可以看出来，那么先分析一下它的sort字段。很显然group by 后的都是sort字段，其中字段CREATE_ORG_NAME其定义为 varchar（1000），它的占用空间为（1000 * 2）及2000字节，但是超过了max_sort_length的大小，因此为1024字节，相同的还有UPDATE_ORG_NAME字段也是varchar（1000），也会做同样处理，其他字段不会超过max_sort_length的限制，并且在第5节说过sort 字段是不会进行压缩的。</p><p>我大概算了一下sort字段的全部大小约为 （3900 * 2) 字节，可以看到一行数据的sort字段基本达到了8K的容量，而addon字段的长度（未打包压缩前）会更大，显然超过max_length_for_sort_data的设置，因此对于这样的排序显然不可能使用modified filesort algorithm（不回表排序了），使用的是original filesort algorithm（回表排序），因此一行的记录就是（sort 字段+主键）了，主键大小可以忽略，最终一行记录的大小就是8K左右，这个值通常会远远大于Innodb压缩后存储varchar字段的大小，这也是为什么本例中虽然表只有30G左右但是临时文件达到了200G以上的原因了。<br>好了，我们来重现一下问题，我们使用第2节的例1，我们将其数据增多，原理上我们的例1会使用到original filesort algorithm（回表排序）的方式，因为这里sort字段（a2，a3）的总长度+addon字段（a1，a2，a3）的长度约为300 * 2 * 2+300 * 3 * 3 这显示大于了max_length_for_sort_data的长度， 因此这个排序一行的长度就是sort字段（a2，a3）+ref字段（ROWID），大约就是300 * 2 * 2+6&#x3D;1206字节了。 下面是这个表的总数据和Innodb文件大小（我这里叫做bgtest5表）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; show create table bgtest5 \G</span><br><span class="line">*************************** 1. row ***************************</span><br><span class="line">       Table: bgtest5</span><br><span class="line">Create Table: CREATE TABLE `bgtest5` (</span><br><span class="line">  `a1` varchar(300) DEFAULT NULL,</span><br><span class="line">  `a2` varchar(300) DEFAULT NULL,</span><br><span class="line">  `a3` varchar(300) DEFAULT NULL</span><br><span class="line">) ENGINE=InnoDB DEFAULT CHARSET=utf8</span><br><span class="line">1 row in set (0.01 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; SELECT COUNT(*) FROM bgtest5;</span><br><span class="line">+----------+</span><br><span class="line">| COUNT(*) |</span><br><span class="line">+----------+</span><br><span class="line">|    65536 |</span><br><span class="line">+----------+</span><br><span class="line">1 row in set (5.91 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; desc select * from bgtest5  order by a2,a3;</span><br><span class="line">+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+----------------+</span><br><span class="line">| id | select_type | table   | partitions | type | possible_keys | key  | key_len | ref  | rows  | filtered | Extra          |</span><br><span class="line">+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+----------------+</span><br><span class="line">|  1 | SIMPLE      | bgtest5 | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 66034 |   100.00 | Using filesort |</span><br><span class="line">+----+-------------+---------+------------+------+---------------+------+---------+------+-------+----------+----------------+</span><br><span class="line">1 row in set, 1 warning (0.00 sec)</span><br></pre></td></tr></table></figure><p>注意这里是全表排序了，没有where过滤条件了，下面是这个表ibd文件的大小：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[root@gp1 test]# du -hs bgtest5.ibd</span><br><span class="line">11M     bgtest5.ibd</span><br><span class="line">[root@gp1 test]# </span><br></pre></td></tr></table></figure><p>下面我们就需要将gdb的断点打在merge_many_buff，我们的目的就是观察临时文件1的大小，这个文件前面说过了是存储内存排序结果的，如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[root@gp1 test]# lsof|grep tmp/MY</span><br><span class="line">mysqld     8769     mysql   69u      REG              252,3   79101952    2249135 /mysqldata/mysql3340/tmp/MYzfek5x (deleted)</span><br></pre></td></tr></table></figure><p>可以看到这个文件的大小为79101952字节，即80M左右，这和我们计算的总量1206（每行大小） * 65535（行数） 约为 80M 结果一致。这远远超过了ibd文件的大小11M，并且要知道，随后还会生成一个大小差不多的文件来存储归并排序的结果如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[root@gp1 test]# lsof|grep tmp/MY</span><br><span class="line">mysqld     8769     mysql   69u      REG              252,3   79167488    2249135 /mysqldata/mysql3340/tmp/MYzfek5x (deleted)</span><br><span class="line">mysqld     8769     mysql   70u      REG              252,3   58327040    2249242 /mysqldata/mysql3340/tmp/MY8UOLKa (deleted)</span><br></pre></td></tr></table></figure><p>因此得到证明，排序的临时文件远远大于ibd文件的现象是可能出现的。</p>]]>
    </content>
    <id>https://ilongda.com/2020/mysql-filesort/</id>
    <link href="https://ilongda.com/2020/mysql-filesort/"/>
    <published>2020-06-21T11:42:57.000Z</published>
    <summary>MySQL filesort 排序机制笔记：对比 original 与 modified 算法、max_sort_length 含义及 sort/addon 字段组织方式</summary>
    <title>filesort 详细解析</title>
    <updated>2026-06-09T08:46:25.939Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"聚簇索引介绍","description":"MySQL 聚簇索引与非聚簇索引笔记：InnoDB 主键聚集存储、覆盖索引优势，及与 MyISAM 物理行指针方式对比，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":2816,"datePublished":"2020-06-14T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.939Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/mysql-cluster-index/"},"url":"https://ilongda.com/2020/mysql-cluster-index/","inLanguage":"zh-CN","keywords":["Database","MySQL"],"articleSection":["Database","MySQL"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"聚簇索引介绍","item":"https://ilongda.com/2020/mysql-cluster-index/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>【转载】<span class="exturl" data-url="aHR0cDovL3d3dy5tYW5vbmdqYy5jb20vZGV0YWlsLzE3LXNzdXRoZXhidXpiam1sYi5odG1s">http://www.manongjc.com/detail/17-ssuthexbuzbjmlb.html<i class="fa fa-external-link-alt"></i></span><br>本文章向大家介绍Mysql聚簇索引和非聚簇索引，主要包括Mysql聚簇索引和非聚簇索引使用实例、应用技巧、基本知识点总结和需要注意事项，具有一定的参考价值，需要的朋友可以参考一下。</p><h1 id="基本介绍"><a href="#基本介绍" class="headerlink" title="基本介绍"></a>基本介绍</h1><p>聚簇索引并不是一种单独的索引类型，而是一种数据存储方式，具体的细节依赖于实现方式，InnoDB的聚簇索引实际上在同一个结构中保存了B+Tree索引和数据行。</p><p>当表中有聚簇索引时，它的数据实际上存储在索引的叶子页中（叶子页中包含了行的全部数据）。而没有聚簇索引时B+Tree叶子页存放的是指向数据的指针。（页是mysql存储引擎最小的存储单元，InnoDB每个页默认大小为16k）可以理解为 有聚簇索引时，数据和对应的叶子页在同一页中，没有聚簇索引时，叶子页和对应的数据不在同一页中。</p><p>InnoDB存储引擎通过主键聚集数据(聚簇索引)，如果没有定义主键，InnoDB会选择一个唯一的非空索引代替。如果没有唯一索引，InnoDB会隐式定义一个主键来作为聚簇索引。InnoDB 只聚集在同一个页面中的记录。包含相邻健值的页面可能相距甚远。</p><span id="more"></span><p>MyISAM中主键索引和其他索引 都指向物理行 (非聚簇索引)</p><p>下图展示了聚簇索引是如何存放的（图片来自《高性能MySQL(第三版)》）：</p><h2 id="聚簇索引和非举措索引的区别："><a href="#聚簇索引和非举措索引的区别：" class="headerlink" title="聚簇索引和非举措索引的区别："></a>聚簇索引和非举措索引的区别：</h2><p>聚簇索引，索引的顺序就是数据存放的顺序（物理顺序），只要索引是相邻的，那么对应的数据一定也是相邻地存放在磁盘上的。一张数据表只能有一个聚簇索引。（一个数据页中数据物理存储是有序的）</p><p>非聚簇索引通过叶子节点指针找到数据页中的数据，所以非聚簇索引是逻辑顺序。</p><h2 id="聚集索引的优点："><a href="#聚集索引的优点：" class="headerlink" title="聚集索引的优点："></a>聚集索引的优点：</h2><p>数据存放的顺序和索引顺序一致,可以把相关数据保存在一起。例如实现电子邮箱时，可以根据用户 ID 来聚集数据，这样只需要从磁盘读取少数的数据页就能获取某个用户的全部邮件。如果没有使用聚簇索引，则每封邮件都可能导致一次磁盘 I&#x2F;O。<br>数据访问更快，聚簇索引将索引和数据保存在同一个B-Tree中，因此从举措索引中获取数据通常比非聚簇索引查找更快。<br>使用覆盖索引扫描的查询可以直接使用页节点中的主键值（二级索引(非聚簇索引) 的叶子节点保存的不是指向行的物理位置的指针，而是行的主键值）。<br>（PS:覆盖索引：Mysql 可以使用索引来直接获取列的数据，这样就不需要查到索引后，然后通过叶子节点的指针回表读取数据行，如果索引的叶子节点中已经包含了或者说覆盖 所有需要查询的字段的值，那么就没有必要再回表查询了，这种称之为“覆盖索引”）</p><h2 id="聚簇索引的缺点："><a href="#聚簇索引的缺点：" class="headerlink" title="聚簇索引的缺点："></a>聚簇索引的缺点：</h2><pre><code>聚簇数据提高了IO性能，如果数据全部放在内存中，则访问的顺序就没那么重要了插入速度严重依赖插入顺序。按主键的顺序插入是速度最快的。但如果不是按照主键顺序加载数据，则需在加载完成后最好使用optimize table重新组织一下表更新聚簇索引列的代价很高。因为会强制innod将每个被更新的行移动到新的位置基于聚簇索引的表在插入新行，或主键被更新导致需要移动行的时候，可能面临页分裂的问题。页分裂会导致表占用更多的磁盘空间。聚簇索引可能导致全表扫描变慢，尤其是行比较稀疏，或由于页分裂导致数据存储不连续的时非聚集索引比想象的更大，因为二级索引的叶子节点包含了引用行的主键列非聚集索引访问需要两次索引查找(非聚集索引中叶子节点保存的行指针指向的是行的主键值)，对于innodb自适应哈希索引可以减少这样的重复工作</code></pre><p>聚簇索引尽量选择有序的列（如AUTO_INCREMENT自增列）,这样可以保证数据行是顺序写入，对于根据主键做关联操作的性能也会更好。</p><p>最好避免随机的（不连续且值的分布范围非常大）聚簇索引，特别是对于I&#x2F;O密集型的应用。</p><p>从性能角度考虑，使用UUID来做聚簇索引会很糟糕，它使得聚簇索引的插入变得完全随机，这是最坏的情况，是的数据没有任何聚集的特性。</p><p>总结下使用类似UUID这种随机的聚簇索引的缺点：<br>UUID字段长，索引占用的空间更大。<br>写入是乱序的，InnoDB不得不频繁的做页分裂操作，以便新的行分配空间，页分裂会导致移动大量数据，一次插入最少需要修改三个页而不是一个页。<br>写入的目标页可能已经刷到磁盘上并从缓存中移除，或者还没有被加载到缓存中，InnoDB在插入之前不得不先找到并从磁盘读取目标页到内存中，这将导致大量的随机IO。<br>频繁的页分裂，页会变的稀疏并被不规则的填充，会产生空间碎片。<br><span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vbGVhcm4tb250aGV3YXkvcC8xMjE1MDUyMS5odG1s">https://www.cnblogs.com/learn-ontheway/p/12150521.html<i class="fa fa-external-link-alt"></i></span></p><p>MySQL的InnoDB索引数据结构是B+树，主键索引叶子节点的值存储的就是MySQL的数据行，普通索引的叶子节点的值存储的是主键值，这是了解聚簇索引和非聚簇索引的前提</p><p>什么是聚簇索引？<br>很简单记住一句话：找到了索引就找到了需要的数据，那么这个索引就是聚簇索引，所以主键就是聚簇索引，修改聚簇索引其实就是修改主键。</p><p>什么是非聚簇索引？<br>索引的存储和数据的存储是分离的，也就是说找到了索引但没找到数据，需要根据索引上的值(主键)再次回表查询,非聚簇索引也叫做辅助索引。</p><p>clustered index（MySQL官方对聚簇索引的解释）<br>The InnoDB term for a primary key index. InnoDB table storage is organized based on the values of the primary key columns, to speed up queries and sorts involving the primary key columns. For best performance, choose the primary key columns carefully based on the most performance-critical queries. Because modifying the columns of the clustered index is an expensive operation, choose primary columns that are rarely or never updated.<br>注意标黑的那段话，聚簇索引就是主键的一种术语</p><p>一个例子<br>下面我们创建了一个学生表，做三种查询，来说明什么情况下是聚簇索引，什么情况下不是。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">create table student (</span><br><span class="line">    id bigint,</span><br><span class="line">    no varchar(20) ,</span><br><span class="line">    name varchar(20) ,</span><br><span class="line">    address varchar(20) ,</span><br><span class="line">    PRIMARY KEY (`branch_id`) USING BTREE,</span><br><span class="line">    UNIQUE KEY `idx_no` (`no`) USING BTREE</span><br><span class="line">)ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;</span><br></pre></td></tr></table></figure><p>　　第一种，直接根据主键查询获取所有字段数据，此时主键是聚簇索引，因为主键对应的索引叶子节点存储了id&#x3D;1的所有字段的值。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select * from student where id = 1</span><br></pre></td></tr></table></figure><p>　　第二种，根据编号查询编号和名称，编号本身是一个唯一索引，但查询的列包含了学生编号和学生名称，当命中编号索引时，该索引的节点的数据存储的是主键ID，需要根据主键ID重新查询一次，所以这种查询下no不是聚簇索引</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select no,name from student where no = &#x27;test&#x27;</span><br></pre></td></tr></table></figure><p>　　第三种，我们根据编号查询编号（有人会问知道编号了还要查询？要，你可能需要验证该编号在数据库中是否存在），这种查询命中编号索引时，直接返回编号，因为所需要的数据就是该索引，不需要回表查询，这种场景下no是聚簇索引</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select no from student where no = &#x27;test&#x27;</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>主键一定是聚簇索引，MySQL的InnoDB中一定有主键，即便研发人员不手动设置，则会使用unique索引，没有unique索引，则会使用数据库内部的一个行的id来当作主键索引,其它普通索引需要区分SQL场景，当SQL查询的列就是索引本身时，我们称这种场景下该普通索引也可以叫做聚簇索引，MyisAM引擎没有聚簇索引。</p><p>原文链接：<span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3hpbmdkdWFuNTE1My9hcnRpY2xlL2RldGFpbHMvMTA2MTg5MzQwLw==">https://blog.csdn.net/xingduan5153/article/details/106189340/<i class="fa fa-external-link-alt"></i></span></p><p>　　推荐：<span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vamlhbmdkcy9wLzgyNzY2MTMuaHRtbA==">https://www.cnblogs.com/jiangds/p/8276613.html<i class="fa fa-external-link-alt"></i></span></p><h1 id="索引优化"><a href="#索引优化" class="headerlink" title="索引优化"></a>索引优化</h1><p>1、缺省情况下建立的索引是非聚簇索引，但有时它并不是最佳的。在非群集索引下，数据在物理上随机存放在数据页上。合理的索引设计要建立在对各种查询的分析和预测上。一般来说：<br>　　a.有大量重复值、且经常有范围查询（ &gt; ,&lt; ，&gt; &#x3D;,&lt; &#x3D;）和order by、group by发生的列，可考<br>　　虑建立聚集索引；<br>　　b.经常同时存取多列，且每列都含有重复值可考虑建立组合索引；<br>　　c.组合索引要尽量使关键查询形成索引覆盖，其前导列一定是使用最频繁的列。索引虽有助于提高性能但不是索引越多越好，恰好相反过多的索引会导致系统低效。用户在表中每加进一个索引，维护索引集合就要做相应的更新工作。<br>2、ORDER BY和GROPU BY使用ORDER BY和GROUP BY短语，任何一种索引都有助于SELECT的性能提高。<br>3、多表操作在被实际执行前，查询优化器会根据连接条件，列出几组可能的连接方案并从中找出系统开销最小的最佳方案。连接条件要充份考虑带有索引的表、行数多的表；内外表的选择可由公式：外层表中的匹配行数*内层表中每一次查找的次数确定，乘积最小为最佳方案。<br>4、任何对列的操作都将导致表扫描，它包括数据库函数、计算表达式等等，查询时要尽可能将操作移至等号右边。<br>5、IN、OR子句常会使用工作表，使索引失效。如果不产生大量重复值，可以考虑把子句拆开。拆开的子句中应该包含索引。</p>]]>
    </content>
    <id>https://ilongda.com/2020/mysql-cluster-index/</id>
    <link href="https://ilongda.com/2020/mysql-cluster-index/"/>
    <published>2020-06-14T11:42:57.000Z</published>
    <summary>MySQL 聚簇索引与非聚簇索引笔记：InnoDB 主键聚集存储、覆盖索引优势，及与 MyISAM 物理行指针方式对比，更多细节与示例见正文。</summary>
    <title>聚簇索引介绍</title>
    <updated>2026-06-09T08:46:25.939Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="生活" scheme="https://ilongda.com/categories/%E7%94%9F%E6%B4%BB/"/>
    <category term="生活" scheme="https://ilongda.com/tags/%E7%94%9F%E6%B4%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"10周年纪念","description":"回顾入职阿里十周年职业生涯：从承诺至少五年到近十载坚守，分享对平台包容、团队信任与职业成长的感恩与随想，更多细节与示例见正文。","image":"https://ilongda.com/img/10years.png","wordCount":942,"datePublished":"2020-06-07T11:07:43.000Z","dateModified":"2026-06-09T08:46:25.930Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/10years/"},"url":"https://ilongda.com/2020/10years/","inLanguage":"zh-CN","keywords":["生活"],"articleSection":["生活"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"生活","item":"https://ilongda.com/categories/生活/"},{"@type":"ListItem","position":3,"name":"10周年纪念","item":"https://ilongda.com/2020/10years/"}]}</script><h1 id="随谈"><a href="#随谈" class="headerlink" title="随谈"></a>随谈</h1><img data-src="/img/10years.png"  alt="10周年纪念"><span id="more"></span><p>今天是入职阿里的10周年， 对于阿里，对于职业生涯， 有无数感慨， 还没有到10周年的时候，提醒自己写一篇总结， 终于到了10周年， 写下一点点感触， 更多的可能是感谢。</p><p>当年入职阿里的时候， 我说我会在阿里至少干5年， 因为过去都是2年跳一次，2年跳一次， 5年实际上是一个不短的旅程。 当时的hr （我记得是杨排风还是谁）特别高兴，回答道： 你有这个心在阿里愿意待5年特别好， 不过世事无常， 但不要给自己束缚太多， 也许2年或3年后，就会有很多变化，而且变化可能超出你的接受范围。每当想起这段交流， 我都忍不住对自己说， 哈哈，你都待了快10年， 早就完成当初的承诺了。 </p><p>待了这么多家公司，以前年少不经事， 总是忍不住不停吐槽公司， 后来见的多了， 当前自己觉得糟的公司，未必如想的那么糟， 自己一心向往的公司，也未必如梦中那么美好， 只有适合自己和不适合自己的公司。 李一男， 这么金光闪闪的神人， 离开华为后， 也是一路跌跌撞撞， 最终才找到自己的落脚点。 </p><p>感谢阿里， 也感谢这个黄金时代， 感谢自己搭载这阿里这周大船，乘风破浪， 如果不是阿里， 自己也就只是一个普普通通的打工仔， 可能依旧奋斗在温饱线上， 至少，阿里让自己从一个屌丝转身成为一个中产阶级家庭， 阿里这个大家庭， 虽然有的时候， 相互pk， 相互投诉，相互较劲， 但也给了一个舞台给我们展示自己的才华， 虽然， 很多时候， 自己的表演比较逊色， 有时候都觉得自己太弱了， 但站在公司的角度， 给一个足够的舞台，这已经是一家公司给予个人最大的支持。天高任鸟飞，其实很多时候， 自己翅膀不够硬，即使够硬了，不一定有足够的空间。古代士为知己者死，女为悦己者容，似乎就是类似思想。另外有的时候，自己管不住自己的一张嘴， 喜欢喷天，喷地， 喷一切， 自己的无知，以前在内网吐槽无数次， 但老板和同事都很包容自己， 感谢他们的包容。一家公司就是由无数的人组成， 这些人的言谈举止就决定了这家公司的风格， 上面最大的老板可能会将公司定一个基调，但真正这些基调的执行或落实，都是身边一个一个鲜活的人来展现。 感谢老板的信任， 很多决策其实有很多风险，但老板都坚定的支持， 也感谢身边的同事和团队的伙伴， 你们总是让一切由不可能变为可能。   </p><p>感谢阿里成就了自己， 而自己也将人生最宝贵的青春奉献给了阿里， 自己从一个青涩青年， 成长为一个油腻大叔， 从一个典型的程序员，到一个很小的管理者。 阿里已经深深在我的行为做事上烙上阿里的痕迹， 就像华为之于老华为人一样，阿里和华为都是极具中国特色的公司。 </p><p>当写到这里， 突然感觉像离职感谢信， 哈哈， 也许忍不住感谢， 一感谢，就像极了离职感谢信。 </p>]]>
    </content>
    <id>https://ilongda.com/2020/10years/</id>
    <link href="https://ilongda.com/2020/10years/"/>
    <published>2020-06-07T11:07:43.000Z</published>
    <summary>回顾入职阿里十周年职业生涯：从承诺至少五年到近十载坚守，分享对平台包容、团队信任与职业成长的感恩与随想，更多细节与示例见正文。</summary>
    <title>10周年纪念</title>
    <updated>2026-06-09T08:46:25.930Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="读书笔记" scheme="https://ilongda.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <category term="读书笔记" scheme="https://ilongda.com/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《华为崛起》","description":"《华为崛起》读书笔记：梳理华为创业、成长与生存阶段的关键决策，探讨客户第一、蓝海选择与组织管理反思，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":1657,"datePublished":"2020-04-30T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.935Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/huawei/"},"url":"https://ilongda.com/2020/huawei/","inLanguage":"zh-CN","keywords":["读书笔记"],"articleSection":["读书笔记"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"读书笔记","item":"https://ilongda.com/categories/读书笔记/"},{"@type":"ListItem","position":3,"name":"《华为崛起》","item":"https://ilongda.com/2020/huawei/"}]}</script><h1 id="题外话"><a href="#题外话" class="headerlink" title="题外话"></a>题外话</h1><p>今年年初， 买书的时候，无意中看到《华为崛起》， 介绍的挺有意思的， 于是买了一本看看， 买了很久但一直没有看， 拖了很久， 终于看完了， 忍不住写点总结吧， 免得左边耳朵进，右边耳朵出。<br>华为从一家这么小的公司，最后成长成为差不多中国最大的民营企业， 而且很多年前, 营收和研发投入就是互联网三巨头bat 的总和， 绝对值得去学习和研究一下(现在互联网叫bat， 其实叫hat 更合适)。 这本书讲了很多华为成长过程中，发生的一些事和决策，可以从中思考一下，为什么华为成功了，而巨大中华中，其他的几乎没有多少声音了。<br>另外，绝对要吐槽一下作者， 全文无处不在的拍马屁， 有的地方，简直要吐了。这本书组织管理介绍比较浅， 很多组织管理的思考充斥着对任老板和管理层的拍马屁。</p><p>创业的朋友，推荐读一下这本书， 带着问题 “为什么华为能成功”？ 读下去，相信会有很多收获。 而我没有什么体感，只能站在第三者的角度去看， 所以聊的深度比较浅， 大家就当博君一笑。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>华为大概分了几个阶段， 每一次都是破茧成蝶，突破天花板</p><ol><li>创业之战</li><li>成长之战</li><li>生存之战</li><li>大象狂奔</li></ol><span id="more"></span><h1 id="创业之战"><a href="#创业之战" class="headerlink" title="创业之战"></a>创业之战</h1><p>创业的时候， 选择了暴利行业的电信行业， 这个是最大的关键点， 如果不是选择这个暴利行业， 否则根本没有钱去支撑后续一系列的研发和快速扩张， 包括允许了一定的战略决策失误。 今天，对于创业的朋友来说，就是一个常说的词， 蓝海和红海， 只有下海到蓝海中， 才有更多的机会，更多的容错性（反脆弱）。 而怎样找到一个蓝海， 就是需要发现问题。</p><h1 id="成长之战"><a href="#成长之战" class="headerlink" title="成长之战"></a>成长之战</h1><p>最早代理交换机， 只要有路子，能拿到货， 转手一下， 就有大把钞票。 有的时候，经常有些热门的交换机，根本拿不到， 任老板于是决定自己去研发，避免自己被人卡脖子。 但凡做事的人， 都会希望自己能把握关键点，降低风险， 这个其实没有什么好吹的。 但老任比较牛b的是，就是坚持，在一系列研发失败后， 最后赌了一把大的， all in 了光纤交换机，而且一下就搞万门交换机，选择了一个非常正确的方向， 这个非常有魄力。 另外，当时第一款产品出来时， 找到第一个关键客户 – 义乌电信局时， 真的是客户第一， 研发所有人员都蹲现场， 现场有问题，现场解决问题， 第一时间解决所有问题， 和义乌电信局成了命运共同体， 一荣皆荣， 一损俱损。 这种手法，是甲方和乙方的最高境界， 也给了华为一个真正的练兵场， 帮助华为打开了局面。</p><p>一家小公司成长起来，非常依赖老板的战略决策， 赌对了就飞黄腾达，赌错了，就树倒猢狲散，做大事的人，往往赌性比较重， 但当公司长大后， 却要防止这种错误的决策, 需要一种机制来防止决策失误， 类似阿里的合伙人，华为的轮值董事等。另外一种防止决策错误的思路是， 客户第一。 华为在客户第一上，做的真是贯穿到骨子里。</p><h1 id="生存之战"><a href="#生存之战" class="headerlink" title="生存之战"></a>生存之战</h1><p>在2003年的时候， 任正非中间打算将华为打包价75亿美金卖掉， 想想这背后的导火索，肯定就是华为遭遇了生存危机。<br>这个生存危机， 第一 港湾之战， 第二 思科之战。<br>港湾之战， 李一男 林立山头， 最初任老板的想法是，让李一男 帮助华为补位， 做一些华为的配合工作，但在巨大利益面前， 谁能抵挡的， 哪个公司不想成为巨头， 所以不要挑战人性， 利益面前，人是会变的。 第二个就是港湾就是华为的山寨版， 做事方式和华为一模一样， 这种方式，如果不斩草除根， 不彻底干掉， 哪怕自损800 也要灭敌1000， 否则就是放虎归山。<br>同样，思科在攻击华为时， 就是害怕自己担负 垄断罪名时， 放了华为一把， 明知华为未来不可阻挡，还是舍不得肉。 这个里面， 华为策略， 小输就是赢， 站的高度还是非常高。<br>2个形成鲜明对比， 一个是宁可自损800也要1000，也要斩草除根， 另外一个舍不得孩子，套不住狼。 最终的结果，大家也看到，永远铭记，从长远思考问题。<br>在2003年时， 挡住饮鸩止渴（挡住小灵通的诱惑），从未来着手布局， 还是高人一等。</p><h1 id="大象狂奔"><a href="#大象狂奔" class="headerlink" title="大象狂奔"></a>大象狂奔</h1><p>采取农村包围城市， 先从一些贫瘠的土地开始播种， 从别人不要的残羹冷炙着手， 深挖梁，缓称王。<br>在一些难啃的国家中， 组建合资公司， 这种做法还是相当smart。<br>在手机业务上， 从默默无闻到巨头， 每一步都是一步一个脚印。<br>布局海思半导体， 最初应该是无奈之举， 但海思半导体却成了华为的一个神来之笔。 </p><h1 id="组织管理"><a href="#组织管理" class="headerlink" title="组织管理"></a>组织管理</h1><p>全书介绍管理上，包装了各种理论， 什么军事管理， 机械主义， 自适应平衡（有个具体的理论，但忘记了名字， 差不多就是每个组织或模块是在一个动态环境中， 需要不停的流入资源&#x2F;人，然后也不断输出资源&#x2F;人，有一点点优胜略汰）。其实背后：</p><ol><li>钱， 2. 权， 3. 精神统一 （客户第一， 奋斗者为本）<br>不同阶段，应用这3点用了不同的手段。</li></ol><h1 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h1><p>今天的华为，俨然一个巨无霸， 不可能一家公司在所有领域都是寡头， 所以，还是需要资源重组，集中优势兵力布局未来主航道， 另外也要给合作伙伴留一条路吧。</p>]]>
    </content>
    <id>https://ilongda.com/2020/huawei/</id>
    <link href="https://ilongda.com/2020/huawei/"/>
    <published>2020-04-30T11:42:57.000Z</published>
    <summary>《华为崛起》读书笔记：梳理华为创业、成长与生存阶段的关键决策，探讨客户第一、蓝海选择与组织管理反思，更多细节与示例见正文。</summary>
    <title>《华为崛起》</title>
    <updated>2026-06-09T08:46:25.935Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Redshift 产品分析 《Amazon Redshift and the Case for Simpler Data Warehouses》","description":"Amazon Redshift SIGMOD 论文产品分析：ParAccel 演化、列存压缩、S3 异步同步及云数仓商业化成功因素解读","image":"https://ilongda.com/img/redshift.png","wordCount":3629,"datePublished":"2020-02-22T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.963Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/docs/paper/redshift/"},"url":"https://ilongda.com/2020/docs/paper/redshift/","inLanguage":"zh-CN","keywords":["论文","数据库"],"articleSection":["论文","数据库"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"Redshift 产品分析 《Amazon Redshift and the Case for Simpler Data Warehouses》","item":"https://ilongda.com/2020/docs/paper/redshift/"}]}</script><h1 id="Redshift-随谈"><a href="#Redshift-随谈" class="headerlink" title="Redshift 随谈"></a>Redshift 随谈</h1><p>无意中看到《Amazon Redshift and the Case for Simpler Data Warehouses》这篇论文的读书笔记(应该是2018年写的)， 于是将论文笔记梳理一下，分享给大家。<br>这是2015年sigmod的一篇论文，这篇论文介绍了redshift 很多产品化的细节， 技术性探讨并不多(有一点aws 软文的感觉)，强烈建议云数据库类的产品经理好好阅读一下， 里面很多理念和产品化的做法aws 2015年就实现了，这在当时是非常超前的，而且有些东西至今国内很多云厂商都还没有实现。 </p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>先来看一下2017年 Redshift 战绩： 2017财年， 整个aws 数据库部门营收是36 billion $,  而不同于其他数据库云厂商的是， Redshift 占比达到惊人的25%， 也就是9 billion $, 这个数字远远超过国内所有olap 数据库的总和， 简直就是一个天文数字。 </p><p>整片论文，换个角度去看，我们带这问题 《How Redshift is success?》去看论文，会带来很多意想不到的思路：</p><ul><li>Redshift 目标客户是谁？</li><li>这个市场前景是多少？</li><li>目标客户的需求是什么？</li><li>怎么赢得客户</li><li>架构和技术分析</li></ul><span id="more"></span><h1 id="Redshift-基本介绍"><a href="#Redshift-基本介绍" class="headerlink" title="Redshift 基本介绍"></a>Redshift 基本介绍</h1><p>Redshift 是2013年推出来， 2014&#x2F;2015年数据库部门增长率最高的olap数据库(Aurora 出来后，号称Aurora 是增长最快的)。<br><img data-src="/img/redshift.png"  alt="Redshift 产品分析 《Amazon Redshift and the Case for Simpler Data Warehouses》"><br>技术是基于ParAccel 系统演化而来， 这里一个小插曲，ParAccel 大概是硅谷一群做数据库的人，基于postgres 7.x 做出来一款mpp 数据库，这款数据库吸收了之前开源界很多数据库的设计，比如列存借鉴了c-store&#x2F;monetdb&#x2F;x100, 压缩技术类似vertica，  2008年左右，这帮数据库的人自己出来创业， aws 2009 年找到他们，购买了他们的源码授权， 结果2年后，ParAccel自己倒闭了，而aws的Redshift 越活越好（估计aws 没有少挖 ParAccel的人， ParAccel 肯定最后抗不住）。</p><p>今天看Redshift, 有一点点类似postgres-xl的架构，技术上，采用列存，并且支持列存压缩， 计算存储一体化， 支持本地join， code gen， 并且非常容易即可进行scale out。 </p><p>数据同时存在s3 和db本地盘中， 每个数据同步写到第一个slice和至少另外一个node的slice中。当磁盘或节点出现故障时， 队列用于限制受影响的slice数， redshift 尝试平衡，重新replication的资源影响和当disk或node增长时，带来相应的失败。 数据异步同步到s3. primary和secondary &#x2F;s3 上数据皆可以读取， s3 上的备份还可以放到其他region。<br>执行器， query plan会被编译成机器码， 多了一些overhead</p><p> 和aws 很多service 紧密合作， 利用ec2 作为instance， s3 做备份， simple workflow（swf） 管控action， cloudwatch 作为用户实例的metrics， simple notification service（sns）作审计日志， key management service&#x2F;cloudhsm 作为key management ，  vpc 做security, 还利用了很多内部能力来做部署， 短期credential， 日志收集， metering。 可以利用s3 的高可用和便利的api， 允许我们自动备份， 持续，自动解决用户的需求， s3 还可解决本地存储的页错误， 实现流式恢复能力， 元数据和catalog 恢复后，即可sql 查询。</p><p> 有一个manager 帮助部署引擎， 收集events和metrics， 生成实例事件， 归档&#x2F;rotating 日志， 监控节点&#x2F;db&#x2F;日志错误， 还有少量功能执行受限操作， 管控平台是额外集群， 负责集群间的监控和报警， 初始的运维task， 终端用户的请求， 比如节点替换， 集群伸缩容， 备份， 恢复，部署，打patch。</p><h1 id="目标客户是谁？"><a href="#目标客户是谁？" class="headerlink" title="目标客户是谁？"></a>目标客户是谁？</h1><p>大部分的db vendor 目标都是大客户， 但Redshift 目标是olap的所有客户， 当DLA 和snowflake 在市场上获得成功后， redshift 立马推出与之对标的产品， 其目的就是解决所有olap需求。<br>在论文里，其实很清楚的写着他们的宗旨</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">宗旨： 如何更便捷的让用户消费redshift, 让用户非常方便，并且极高的性价比获得分析能力, $1000 /TB/Year</span><br></pre></td></tr></table></figure><h1 id="市场前景"><a href="#市场前景" class="headerlink" title="市场前景"></a>市场前景</h1><p>论文对这的论断是： 分析市场占关系数据库(1&#x2F;3, $14B  VS  $45B), GARTNER预估 每年8 ～ 11% 的增长， 过去10年， 分析的数据量是每年30～ 40% 增长， 过去的12个月，增速已经达到50 ～ 60%。<br>这个论断和国内的市场结果完全不一样， 国内olap市场大概只有1&#x2F;10 的水准， 这个结果可能第一和国内olap数据库水平比较差； 第二，大家上云的需求量有区别，国内oltp比较重要，会对云厂商依赖重一些，而olap 系统挂了也没有关系，因此上云需求也就少了</p><h1 id="客户需求是什么？"><a href="#客户需求是什么？" class="headerlink" title="客户需求是什么？"></a>客户需求是什么？</h1><h2 id="使用数据仓库的企业"><a href="#使用数据仓库的企业" class="headerlink" title="使用数据仓库的企业"></a>使用数据仓库的企业</h2><p> 传统的企业数据仓库， 收集各种数据源的数据， 通过bi 访问， 他们需要无缝从现有bi或etl 工具迁移过来， redshift 解放他们运维这些系统的压力并让用很轻松的scale-out&#x2F;scale-in， 当迁移系统时， 他们更偏爱现有的db系统，而不care 系统是哪家厂商。 当硬件升级或license 到期，或到了规模上限或同厂商提供第二套系统但第二套系统和早期sql 不兼容时，他们会来尝试redshift， 针对这些客户， redshift 不断强化线性增长， 无限扩容， postgres 兼容， 并不断完善工具系统。</p><ul><li>释放DBA – 释放运维压力，附加工具不断完善</li><li>让系统轻松scale-out&#x2F;scale-in， 按需扩容</li><li>良好的兼容性 – <ul><li>无缝兼容现有的BI&#x2F;ETL 工具</li><li>提供现有系统血缘相近的db</li><li>保持协议兼容，现有系统可以继续使用</li></ul></li></ul><h2 id="大数据客户"><a href="#大数据客户" class="headerlink" title="大数据客户"></a>大数据客户</h2><p>去年有一篇文章《大数据已死》介绍了大数据的3驾马车 cloudera&#x2F;hontorworks&#x2F;mapr 3家公司奄奄一息， 直到去年才暴露出大数据的问题。 其实早在redshift 一出来， redshift 就准备干死大数据这一套的开源系统。</p><p>论文这样描述：<br>半结构化的大数据分析， 常常对日志或事务数据集成分析， 一些客户从hive 迁移过来， 获得更好的成本和性能， 他们边用sql或bi工具来使用系统，简便是他们最care的特性之一， 他们没有dba团队， 数据存在大量无效数据， 并且他们需要更简便。 redshift 支持透明数仓， 在数据湖上或自动半结构化数据。</p><p>大部分的数据是dark 数据， 收集了但不分析， 没有什么用， 因此常常把数据存储到hadoop 或nosql（带冷热分离能力）只是解决了一部分的需求。成本， 大部分的商业数据库价格昂贵， 因此很难评估大数据清晰的价值</p><p>总结下来就是：</p><ol><li>超强的性价比： 大数据常常是无效数据多， 因此需要极低的价格， </li><li>释放运维压力，使用大数据的企业没有dba，因此需要解决运维压力</li><li>兼容现有的bi&#x2F;sql 即可</li></ol><h2 id="加速在线业务的客户"><a href="#加速在线业务的客户" class="headerlink" title="加速在线业务的客户"></a>加速在线业务的客户</h2><p>这种客户的特点是业务驱动， 消费大量原始数据， 跑大型sql 产生在线业务需要的结果， 比如广告技术，数据转化&#x2F;数据消费， 客户用sql直接或申明式展示内容并在hadoop 生态也是一样， sql 越来越替代mr的使用。</p><h2 id="samll-data-客户"><a href="#samll-data-客户" class="headerlink" title="samll data 客户"></a>samll data 客户</h2><p>有一些用户并没有使用过传统数仓，他们直接运行分析任务在他们的事务系统中， redshift 帮助他们搭建数仓， 并获得性能提升， oltp系统offline并保留历史， 这些用户会有一个短的滞后， 自动数据变更和schema 自动创建和维护很重要。</p><p>价值点：</p><ol><li>冷热分离， 将冷数据offline到redshift， 实际上对oltp系统有一定的加速</li><li>更强的分析能力，并且不影响在线业务</li><li>让用户快速搭建olap系统，并降低用户迁移成本， 自动数据变更， schema变更</li></ol><h1 id="怎样将用户吸引进来"><a href="#怎样将用户吸引进来" class="headerlink" title="怎样将用户吸引进来"></a>怎样将用户吸引进来</h1><p>Redshift 将如何将用户吸引进来，做的十分极致， 用了大量的手段：</p><ol><li>简化购买流程</li></ol><p>减少用户实验成本， 提供60天免费使用， 压缩ssd 到160g， 随后， 每个节点打包价$0.25&#x2F;hour&#x2F;node, 含软件和硬件维护费用</p><ol><li><p>创建集群快， 即使是PB 级的cluster， 创建不超过15分钟。<br>1.1 “time to first report”, 从一开始浏览网页， 评估redshift 服务， 发送第一个query， 获得第一个结果， 用零售的习惯去理解用户的行为， 对产品决策很有帮助。<br>1.1.1 保持postgres odbc&#x2F;jdbc 完全的兼容， 客户现有的系统无需修改即可使用<br>1.1.2 价格体系是和容量相关<br>1.1.3 减少前端步骤 如创建和配置， 能减少流失率<br>1.2 打包交付， 提供给用户的信息只包含节点的数量和类型， 基本网络类型， 管理账号信息， 未来这些信息也尽可能少。<br>1.2.1 早期创建集群耗时15minute， 后来通过预配置只需3分钟搞定<br>1.2.2 减少错误成本， 用户可以自由实验， 让用户db 很方便收回和exchange， 头2个月免费使用160g 压缩ssd， 并且基于小时的计费方式，减少用户的负担<br>1.2.3 用户可以任意伸缩容或部署一个新的集群，做并行迁移， 让老的集群只读</p></li><li><p>数据快速加载</p></li></ol><p>10 minute load 5B rows, 9.75 hours load 150 B rows, backup 30min, restore 用了4.8 hour， join 2 万亿 和60亿 只花了14 minute<br>数据加载是一个特殊的query 过程， 使用修改过的postgressql copy 命令， 可以直接从s3， dynamoDB, emr, SSH 上获取数据， 多个slice 可以同时并行拉取数据， copy还可以直接支持json和压缩， 加密数据。</p><h1 id="怎样将用户留下来"><a href="#怎样将用户留下来" class="headerlink" title="怎样将用户留下来"></a>怎样将用户留下来</h1><ul><li>性价比</li><li>简单易用，释放运维压力</li><li>稳定性第一</li><li>后台推荐系统</li></ul><h2 id="性价比"><a href="#性价比" class="headerlink" title="性价比"></a>性价比</h2><p>redshift 目前基本超过现在能看到的开源olap 数据库或大数据系统， 并且提供极低的价格， $1000 &#x2F;TB&#x2F;Year， 这个价格下，很少有系统能提供如此高的性价比。</p><h2 id="释放运维压力"><a href="#释放运维压力" class="headerlink" title="释放运维压力"></a>释放运维压力</h2><ul><li><p>减少运维管理<br>复杂性， 云上数据库需要解决数据库的复杂性， 比如部署， 运维， 备份， 调优. 减少管理， 减少迁移， 备份， 自动备份， 恢复， 部署，打patch， 错误探测和恢复， 高级功能如加密，伸缩容， 灾难恢复也只要点几下。</p><ol><li>不需要dba， 不需要db 日常管理， 如安装，打patch， 监控， 修复备份和恢复。</li><li>db的运维应该是申明式的</li><li>集群备份会均分到每个节点， 集群备份会自动执行和过期。</li><li>当需要备份到其他region， 用户只用点击click box 和选择region， 它会备份到本地或远程region， 恢复也是流式， 几分钟就能在其他region 进行恢复。</li><li>加密是很直接</li><li>未来， 用户不用初始的table 管理操作， 让他们接近备份操作， db能自我决定， 当load 过载或访问性能下降。</li><li>减少扩容担心， 价格仅和数据量相关， 管控操作也是并行的</li><li>部署和打patch是自动， 用户无感知， 一个星期一个feature， 快速迭代来寻找用户最关心的feature</li></ol></li><li><p>减少性能调优<br>自动tuning， 默认设置是足够的， 高级用户可以做一些调优的操作， 比如自动挑选压缩类型， 通过多维-curve 来避免indexing 和projecting</p><ol><li>很少工具来进行性能调优， 我们来承担这份工作， 用户只需关注db类型和节点数， 个别表的sort 方式和分布模型。</li><li>用户有时想设置一些参数， 比如列压缩类型， redshift 减少这种设置， 或者让这些设置更精准， db 提供足够信息如查询pattern， 数据分布，压缩成本</li><li>减少设置， sort column和key平衡分布， 我们的一个技术是减少后续优化操作</li><li>系统可以grow 或收缩 随load 变化， 减轻数据和查询的连接</li></ol></li></ul><p> 一个功能就是， 在周5释放集群，在周日晚上恢复。</p><h2 id="稳定性第一"><a href="#稳定性第一" class="headerlink" title="稳定性第一"></a>稳定性第一</h2><p>对用户非常尊重， 每个节点增加1分钟的开销都需要很多天的调研， 一个缺陷的bug fix， 即使是1000多天才回让集群重启一下都要调研一下， 持续提高可靠性，可用性，自驱。<br>列了一些教训</p><ol><li>2013年开始，几千套集群， 更偏重迭代而不是重构， 失败是很常见的， 当出现故障时， 降级比丢失可用性更关键， aws 有5千万的code， 经常会出现少量的regression， 当某个service 失去服务时， 让自己弹性很有必要。 我们每个data center 有预配置节点， 可以持续部署或替换节点，当ec2部署中断， 可以增加本地备份，防止s3 或网络故障。</li><li>不间断服务， 用户期望小的patch而不是大的， 打patch 是繁重过程， 因此自动打patch， 限制在用户允许的30分钟窗口内每周， patch 可回滚， 当出现错误或延迟时，可以自动回滚。 任何时刻用户只在一个patch verison， 这样可以很便捷的确认issue， 每两周推动新的engine， 以前是4周， 现在降低失败的概率</li></ol><h2 id="客户推荐系统"><a href="#客户推荐系统" class="headerlink" title="客户推荐系统"></a>客户推荐系统</h2><p>用pareto 分析调度任务， severity level 2 的报警才让engineer 参与， 否则研发会被日常维护给淹没， 系统自动收集日志，分析前10 的错误。 pareto 可以帮助了解用户需求， 每年会直接1v1 对话客户。</p>]]>
    </content>
    <id>https://ilongda.com/2020/docs/paper/redshift/</id>
    <link href="https://ilongda.com/2020/docs/paper/redshift/"/>
    <published>2020-02-22T11:42:57.000Z</published>
    <summary>Amazon Redshift SIGMOD 论文产品分析：ParAccel 演化、列存压缩、S3 异步同步及云数仓商业化成功因素解读</summary>
    <title>Redshift 产品分析 《Amazon Redshift and the Case for Simpler Data Warehouses》</title>
    <updated>2026-06-09T08:46:25.963Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="旅游" scheme="https://ilongda.com/categories/%E6%97%85%E6%B8%B8/"/>
    <category term="旅游" scheme="https://ilongda.com/tags/%E6%97%85%E6%B8%B8/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"哥本哈根两日游","description":"哥本哈根两日游游记：购哥本哈根卡畅玩古城与展览馆，推荐嘉士伯艺术中心、克里斯蒂安皇宫与国家博物馆等景点，更多细节与示例见正文。","image":"https://ilongda.com/img/copenhagen/c0.jpeg","wordCount":694,"datePublished":"2020-02-01T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.931Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/copenhagen/"},"url":"https://ilongda.com/2020/copenhagen/","inLanguage":"zh-CN","keywords":["旅游"],"articleSection":["旅游"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"旅游","item":"https://ilongda.com/categories/旅游/"},{"@type":"ListItem","position":3,"name":"哥本哈根两日游","item":"https://ilongda.com/2020/copenhagen/"}]}</script><h1 id="题外话"><a href="#题外话" class="headerlink" title="题外话"></a>题外话</h1><p>哥本哈根挺好玩的， 即使完全不做攻略，也基本上可以玩的很happy， 很多玩的地方都集中在古城区， 在古城区很轻松就可以呆上一天， 另外坐上公交就可以到四处转转，比如美人鱼的地方，在那里可以看很多漂亮的公园。</p><p>强烈建议，先去tourist 中心购买哥本哈根卡， 持有哥本哈根卡，就可以免费走，免费看， 免费看87个展览馆，性价比极高。<br>整个北欧，艺术氛围非常浓厚，随处可见雕塑，建筑也是五彩缤纷，有非常多的展览馆或艺术馆，展览馆内保存着各式各样的雕塑和油画， 很多喜爱画画的人，拿着画具，找到一个博物馆，找一个雕塑，一呆就是一天。</p><p>我们在隔壁哈根，玩了下面几个地方，觉得推荐指数排序从高到低：</p><ol><li>嘉士伯艺术中心</li><li>christians 皇宫</li><li>thorvaldsens 博物馆</li><li>丹麦国家博物馆</li><li>天文馆&#x2F;tivoli 游乐场 – 适合小孩去玩，</li><li>美人鱼</li></ol><p>还有许多非常不错的地方，我们没有去，  比如amalienborg， 丹麦艺术与设计博物馆</p><span id="more"></span><h2 id="嘉士伯艺术中心"><a href="#嘉士伯艺术中心" class="headerlink" title="嘉士伯艺术中心"></a>嘉士伯艺术中心</h2><p>嘉士伯艺术中心，是我见过雕塑最多的展馆，格式雕塑，很多雕塑都可以追溯到罗马时期，甚至更早。<br>这里随便摘选几个<br><img data-src="/img/copenhagen/c0.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/c1.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/c2.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/c3.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/c4.jpeg"  alt="哥本哈根两日游"><br>雕塑太多了，油画部分还没有来得及参观</p><h2 id="christians-皇宫"><a href="#christians-皇宫" class="headerlink" title="christians 皇宫"></a>christians 皇宫</h2><p>皇宫也是非常值得推荐参观游览的，</p><img data-src="/img/copenhagen/p0.jpeg"  alt="哥本哈根两日游"> 一进门就是与众不同的柱子<img data-src="/img/copenhagen/p1.jpeg"  alt="哥本哈根两日游"><p>王位<br><img data-src="/img/copenhagen/p2.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/p3.jpeg"  alt="哥本哈根两日游"></p><p>大厅，大厅中还有许许多多的画像，每一幅画像背后都寓有含义，可惜听不懂丹麦语， 旁边导游的讲解实在无法理解。只能贴2张图吧。<br><img data-src="/img/copenhagen/p4.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/p5.jpeg"  alt="哥本哈根两日游"></p><p>还展示了皇宫的家具和餐具，其中餐具非常精美<br><img data-src="/img/copenhagen/p6.jpeg"  alt="哥本哈根两日游"></p><h2 id="thorvaldsens-博物馆"><a href="#thorvaldsens-博物馆" class="headerlink" title="thorvaldsens 博物馆"></a>thorvaldsens 博物馆</h2><p>Thorvaldsens 是一个天才艺术家， 从小自学成才， 当时雕塑有很多教条主义，而thorvaldsens凭借自己的天性追求艺术，雕塑了无数的精美作品， 而且更牛掰的是，自己死后就葬在展览馆的中央，四周环绕他的作品， 让世人敬仰和崇拜。</p><p>很多素描的，找一幅雕塑，一画就是一下午<br><img data-src="/img/copenhagen/t1.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/t2.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/t3.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/t4.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/t5.jpeg"  alt="哥本哈根两日游"></p><h1 id="国家博物馆"><a href="#国家博物馆" class="headerlink" title="国家博物馆"></a>国家博物馆</h1><p>国家博物馆，展示丹麦的历史，从古到现在，从石器时代到青铜，到钢铁，再到现在，中间穿插很多宗教信仰的艺术品</p><img data-src="/img/copenhagen/h1.jpeg"  alt="哥本哈根两日游"><img data-src="/img/copenhagen/h2.jpeg"  alt="哥本哈根两日游"><img data-src="/img/copenhagen/h3.jpeg"  alt="哥本哈根两日游"><img data-src="/img/copenhagen/h4.jpeg"  alt="哥本哈根两日游"><h1 id="街拍"><a href="#街拍" class="headerlink" title="街拍"></a>街拍</h1><p>市政厅和世界之钟<br><img data-src="/img/copenhagen/a1.jpeg"  alt="哥本哈根两日游"></p><p>安徒生雕像<br><img data-src="/img/copenhagen/a2.jpeg"  alt="哥本哈根两日游"></p><p>市政厅前雕塑<br><img data-src="/img/copenhagen/a0.jpeg"  alt="哥本哈根两日游"></p><p>安徒生童话之美人鱼<br><img data-src="/img/copenhagen/a8.jpeg"  alt="哥本哈根两日游"></p><p>皇宫外景<br><img data-src="/img/copenhagen/a3.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/a4.jpeg"  alt="哥本哈根两日游"></p><p>吉菲昂喷泉<br><img data-src="/img/copenhagen/a5.jpeg"  alt="哥本哈根两日游"><br><img data-src="/img/copenhagen/a6.jpeg"  alt="哥本哈根两日游"><br>皇家图书馆<br><img data-src="/img/copenhagen/a7.jpeg"  alt="哥本哈根两日游"></p>]]>
    </content>
    <id>https://ilongda.com/2020/copenhagen/</id>
    <link href="https://ilongda.com/2020/copenhagen/"/>
    <published>2020-02-01T11:42:57.000Z</published>
    <summary>哥本哈根两日游游记：购哥本哈根卡畅玩古城与展览馆，推荐嘉士伯艺术中心、克里斯蒂安皇宫与国家博物馆等景点，更多细节与示例见正文。</summary>
    <title>哥本哈根两日游</title>
    <updated>2026-06-09T08:46:25.931Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="旅游" scheme="https://ilongda.com/categories/%E6%97%85%E6%B8%B8/"/>
    <category term="旅游" scheme="https://ilongda.com/tags/%E6%97%85%E6%B8%B8/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"斯德哥尔摩一日游","description":"斯德哥尔摩一日游游记：市政厅 Golden Hall 与 Princess Hall 壁画故事，及北欧多城串联行程偏紧的随想","image":"https://ilongda.com/img/stockholm/hall.jpg","wordCount":632,"datePublished":"2020-01-30T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.945Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/stockholm/"},"url":"https://ilongda.com/2020/stockholm/","inLanguage":"zh-CN","keywords":["旅游"],"articleSection":["旅游"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"旅游","item":"https://ilongda.com/categories/旅游/"},{"@type":"ListItem","position":3,"name":"斯德哥尔摩一日游","item":"https://ilongda.com/2020/stockholm/"}]}</script><h1 id="题外话"><a href="#题外话" class="headerlink" title="题外话"></a>题外话</h1><p>这次北欧之行， 有一点安排不合理就是增加了斯德哥尔摩， 中间从挪威离开，没有直接去丹麦，而是在斯德哥尔摩呆了一天，让整个行程非常紧张， 本身每次换城市，都是在白天的黄金时间换，到酒店checkin基本上差不多2点了，而北欧的白天又特别短， 不到5点就天黑了，玩的时间太匆忙了。</p><p>斯德哥尔摩参观了几个不错的景点， 另外斯德哥尔摩有许多非常有意思的景点， 还是推荐更长的假期来慢慢游玩。</p><h1 id="市政厅"><a href="#市政厅" class="headerlink" title="市政厅"></a>市政厅</h1><p>我们正好赶上4点钟市政厅的导游， 导游给我们讲解了市政厅的许多故事和展览。</p><p>加一段介绍<br>建于1911年，历时12年才完成，是瑞典建筑中最重要的作品。建筑两边临水，一座巍然矗立着的塔楼，与沿水面展开的裙房形成强烈的对比，加之装饰性很强的纵向长条窗，整个建筑犹如一艘航行中的大船，宏伟壮丽。<br>800万块红砖砌成的外墙，在高低错落、虚实相谐中保持着北欧传统古典建筑的诗情画意。市政厅的右侧是一座高106米，带有3个镀金皇冠的尖塔，代表瑞典、丹麦、挪威三国人民的合作无间。据说登上塔顶部，可一览整个城市的风貌。</p><p>先来看一看外观<br><img data-src="/img/stockholm/hall.jpg"  alt="斯德哥尔摩一日游"></p><span id="more"></span><p>整个市政厅最辉煌的是golden hall， golden hall 是用来办舞会而建，大厅金碧辉煌，用真的金子贴上玻璃而装饰，大厅里带有瑞典历史文化色彩和信仰。 </p><p>lake queen, 瑞典神话中的女神，象征和平和幸福<br><img data-src="/img/stockholm/hall1.jpeg"  alt="斯德哥尔摩一日游"></p><p>瑞典男神和女神<br><img data-src="/img/stockholm/hall2.jpeg"  alt="斯德哥尔摩一日游"><br><img data-src="/img/stockholm/hall3.jpeg"  alt="斯德哥尔摩一日游"></p><p>遗留的人头<br><img data-src="/img/stockholm/hall4.jpeg"  alt="斯德哥尔摩一日游"></p><p>princess hall, 墙上的壁画是公主历史五年亲手一笔一笔画出来，每画一副都必须一气呵成，非常麻烦， 中间一个小插曲， 公主花1年多，已经完成了一版，但几幅画中，有2幅不是很满意，公主决定推倒重来，于是又花了好几年才完成所有壁画（大概4幅）</p><img data-src="/img/stockholm/hall5.jpeg"  alt="斯德哥尔摩一日游"><img data-src="/img/stockholm/hall6.jpeg"  alt="斯德哥尔摩一日游"><img data-src="/img/stockholm/hall7.jpeg"  alt="斯德哥尔摩一日游"><p>其他还有一些厅就不介绍，比如blue hall， council room等。</p><h1 id="街拍"><a href="#街拍" class="headerlink" title="街拍"></a>街拍</h1><p>去的时候，瑞典皇宫已经关门了，很遗憾<br><img data-src="/img/stockholm/s.jpeg"  alt="斯德哥尔摩一日游"></p><p>诺贝尔展览馆也关门了<br><img data-src="/img/stockholm/s4.jpeg"  alt="斯德哥尔摩一日游"></p><p>一个不错的教堂<br><img data-src="/img/stockholm/s1.jpeg"  alt="斯德哥尔摩一日游"><br><img data-src="/img/stockholm/s3.jpeg"  alt="斯德哥尔摩一日游"></p><p> 街拍<br><img data-src="/img/stockholm/s2.jpeg"  alt="斯德哥尔摩一日游"></p>]]>
    </content>
    <id>https://ilongda.com/2020/stockholm/</id>
    <link href="https://ilongda.com/2020/stockholm/"/>
    <published>2020-01-30T11:42:57.000Z</published>
    <summary>斯德哥尔摩一日游游记：市政厅 Golden Hall 与 Princess Hall 壁画故事，及北欧多城串联行程偏紧的随想</summary>
    <title>斯德哥尔摩一日游</title>
    <updated>2026-06-09T08:46:25.945Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="旅游" scheme="https://ilongda.com/categories/%E6%97%85%E6%B8%B8/"/>
    <category term="旅游" scheme="https://ilongda.com/tags/%E6%97%85%E6%B8%B8/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"挪威3日游(3)","description":"卑尔根一日游游记：乘缆车俯瞰世界文化遗产城市，逛五色木屋汉萨码头与博物馆油画艺术，中餐馆收官，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://ilongda.com/img/norway/b1.jpeg","wordCount":608,"datePublished":"2020-01-29T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.941Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/norway3/"},"url":"https://ilongda.com/2020/norway3/","inLanguage":"zh-CN","keywords":["旅游"],"articleSection":["旅游"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"旅游","item":"https://ilongda.com/categories/旅游/"},{"@type":"ListItem","position":3,"name":"挪威3日游(3)","item":"https://ilongda.com/2020/norway3/"}]}</script><h1 id="卑尔根"><a href="#卑尔根" class="headerlink" title="卑尔根"></a>卑尔根</h1><p>卑尔根，号称是挪威最漂亮的城市，也是世界文化遗产， 这里有非常多漂亮的建筑和展览馆。</p><p>这里插一段介绍<br>卑尔根（Bergen），是挪威霍达兰郡的首府，也是挪威第二大城市，同时还是挪威西海岸最大最美的港都，曾在2000年被联合国评选为“欧洲文化之都”，这里气候温和多雨，是一座“雨城”。<br>卑尔根是挪威第二大城市及霍达兰郡首府，也是挪威西部最大的城市，坐落在挪威西海岸陡峭的峡湾线上，倚着港湾和七座山头，市区频临碧湾（Byfjord），直通大西洋，是座风光明媚的港湾之城。由于受墨西哥暖流影响而生的暖风，使卑尔根成为多雨的地区。<br>2000年，卑尔根被评选为9个欧洲文化城市之一。它的魅力展示在剧院，舞蹈，音乐，艺术，食品和展览会中。卑尔根的主要建筑物游览区在港口附近，北部则留存着许多中世纪汉撒同盟时代的古老建筑，南部则是现代化的购物街。</p><p>非常漂亮的建筑<br><img data-src="/img/norway/b1.jpeg"  alt="挪威3日游(3)"><br><img data-src="/img/norway/b2.jpeg"  alt="挪威3日游(3)"><br><img data-src="/img/norway/b3.jpeg"  alt="挪威3日游(3)"></p><span id="more"></span><p>我们乘缆车爬到山顶，一览整个卑尔根城市</p><img data-src="/img/norway/b4.jpeg"  alt="挪威3日游(3)"><img data-src="/img/norway/b5.jpeg"  alt="挪威3日游(3)"><img data-src="/img/norway/b6.jpeg"  alt="挪威3日游(3)"><p>这里我们碰到挪威的吉祥物， 山妖， 山妖面相丑陋，不过看起来挺可爱的</p><img data-src="/img/norway/b7.jpeg"  alt="挪威3日游(3)"><p>下山后，来到码头随便乱逛了一把<br>这里是卑尔根标志性的建筑， 5色木屋， 它是德国统治期间汉萨联盟搭建的房屋， 以前是汉萨商人的店铺或仓库<br><img data-src="/img/norway/b8.jpeg"  alt="挪威3日游(3)"></p><p>汉萨联盟遗址， 以前是汉萨商人的仓库，现在是各种商店， 因为是周末，店铺休息，看不到店铺里卖的是什么<br><img data-src="/img/norway/b9.jpeg"  alt="挪威3日游(3)"><br><img data-src="/img/norway/b10.jpeg"  alt="挪威3日游(3)"><br><img data-src="/img/norway/b11.jpeg"  alt="挪威3日游(3)"></p><p>离开码头，我们吃过午饭， 准备去卑尔根博物馆， 这里收藏有大量的艺术品，对于爱好艺术的人来说，是一次饕餮大餐， 逛一天都逛不完。<br><img data-src="/img/norway/b12.jpeg"  alt="挪威3日游(3)"></p><p>各种艺术品和家具等<br><img data-src="/img/norway/b13.jpeg"  alt="挪威3日游(3)"><br><img data-src="/img/norway/b14.jpeg"  alt="挪威3日游(3)"><br>各种各样的油画， 有人物肖像， 有风景，有抽象派等等，仔细逛，一天都逛不完<br><img data-src="/img/norway/b15.jpeg"  alt="挪威3日游(3)"><br><img data-src="/img/norway/b16.jpeg"  alt="挪威3日游(3)"><br><img data-src="/img/norway/b17.jpeg"  alt="挪威3日游(3)"></p><p>离开博物馆， 走在街上， 随便拍一拍，都有非常不错的收获<br><img data-src="/img/norway/b18.jpeg"  alt="挪威3日游(3)"><br><img data-src="/img/norway/b19.jpeg"  alt="挪威3日游(3)"><br><img data-src="/img/norway/b20.jpeg"  alt="挪威3日游(3)"></p><p>最后在一家中餐馆作为今天的收官之战，“china palace”， 还不错，值得推荐</p><img data-src="/img/norway/b21.jpeg"  alt="挪威3日游(3)">]]>
    </content>
    <id>https://ilongda.com/2020/norway3/</id>
    <link href="https://ilongda.com/2020/norway3/"/>
    <published>2020-01-29T11:42:57.000Z</published>
    <summary>卑尔根一日游游记：乘缆车俯瞰世界文化遗产城市，逛五色木屋汉萨码头与博物馆油画艺术，中餐馆收官，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>挪威3日游(3)</title>
    <updated>2026-06-09T08:46:25.941Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="旅游" scheme="https://ilongda.com/categories/%E6%97%85%E6%B8%B8/"/>
    <category term="旅游" scheme="https://ilongda.com/tags/%E6%97%85%E6%B8%B8/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"挪威3日游(2)","description":"挪威缩影第二程游记：火车穿越米达尔至弗洛姆、峡湾游船及沿途冬夏四季风光，附沿途视频记录分享，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://ilongda.com/img/norway/nutshell.jpeg","wordCount":451,"datePublished":"2020-01-28T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.941Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/norway2/"},"url":"https://ilongda.com/2020/norway2/","inLanguage":"zh-CN","keywords":["旅游"],"articleSection":["旅游"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"旅游","item":"https://ilongda.com/categories/旅游/"},{"@type":"ListItem","position":3,"name":"挪威3日游(2)","item":"https://ilongda.com/2020/norway2/"}]}</script><h2 id="挪威缩影（Norway-nutshell）"><a href="#挪威缩影（Norway-nutshell）" class="headerlink" title="挪威缩影（Norway nutshell）"></a>挪威缩影（Norway nutshell）</h2><p>挪威缩影，是挪威旅游局将挪威沿途的风景安排成一个行程，让游客做做火车，坐坐轮船，体验挪威的漂亮风景。这一路， 夏天山清水秀，冬天白雪皑皑， 景色确实很赞。</p><span id="more"></span><h3 id="第一程-奥斯陆到米达尔"><a href="#第一程-奥斯陆到米达尔" class="headerlink" title="第一程 奥斯陆到米达尔"></a>第一程 奥斯陆到米达尔</h3><p>这一路开始还是山清水秀，走着走着就变成白雪皑皑， 沿途看到无数河流环山而绕，开始河流还山清水秀，后面就冰封一层。到最后就是茫茫一片， 天地洁白， 偶尔看到几个滑雪的人， 都有时候担心滑雪会不会迷失方向， 中间还看到有人滑伞， 在雪山上滑伞，从来没有体验过， 看起来很刺激。</p><img data-src="/img/norway/nutshell.jpeg"  alt="挪威3日游(2)"><iframe height=498 width=510 src="http://player.youku.com/embed/XNDUzNDMwMzMzMg" frameborder=0 allowfullscreen></iframe><img data-src="/img/norway/nutshell1.jpeg"  alt="挪威3日游(2)"><h3 id="第二程-米达尔到flam"><a href="#第二程-米达尔到flam" class="headerlink" title="第二程 米达尔到flam"></a>第二程 米达尔到flam</h3><p>这一段是高山火车， 这一段景色更加迷人， 这一段如果在夏天， 中间还会有一些小的表演看看，不过因为临近过年，冰天雪地，表演人员就没有为大家show了。<br>高山火车行驶一段后，来到一个观景台， 大家下车拍照观景， 观景台正对着一个小瀑布<br><img data-src="/img/norway/nutshell2.jpeg"  alt="挪威3日游(2)"><br><img data-src="/img/norway/nutshell3.jpeg"  alt="挪威3日游(2)"><br>离开观景台， 时而左边风景优美，时而右边如画<br><img data-src="/img/norway/nutshell4.jpeg"  alt="挪威3日游(2)"><br><img data-src="/img/norway/nutshell6.jpeg"  alt="挪威3日游(2)"><br>快到了flam， 就差不多开始看到村庄了<br><img data-src="/img/norway/nutshell5.jpeg"  alt="挪威3日游(2)"><br><img data-src="/img/norway/nutshell7.jpeg"  alt="挪威3日游(2)"></p><h3 id="第三程，-flam-游峡湾"><a href="#第三程，-flam-游峡湾" class="headerlink" title="第三程， flam 游峡湾"></a>第三程， flam 游峡湾</h3><p>这一段差不多就是缩影的高潮部分， 这里大家坐船在峡湾中穿行2个小时。 </p><img data-src="/img/norway/ship.jpeg"  alt="挪威3日游(2)"><img data-src="/img/norway/ship1.jpeg"  alt="挪威3日游(2)"><img data-src="/img/norway/ship2.jpeg"  alt="挪威3日游(2)"><iframe height=498 width=510 src="http://player.youku.com/embed/XNDUzNDMwNjI2MA" frameborder=0 allowfullscreen></iframe>### 第四程/第五程， 大巴和火车因为第四程和第五程，天色以黑， 也看不到什么东西， 在车上已经有点昏昏欲睡。 不过这一段路程，如果是在夏天，就可以看到火车沿着海岸线行走，看沿途的大海。]]>
    </content>
    <id>https://ilongda.com/2020/norway2/</id>
    <link href="https://ilongda.com/2020/norway2/"/>
    <published>2020-01-28T11:42:57.000Z</published>
    <summary>挪威缩影第二程游记：火车穿越米达尔至弗洛姆、峡湾游船及沿途冬夏四季风光，附沿途视频记录分享，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>挪威3日游(2)</title>
    <updated>2026-06-09T08:46:25.941Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="旅游" scheme="https://ilongda.com/categories/%E6%97%85%E6%B8%B8/"/>
    <category term="旅游" scheme="https://ilongda.com/tags/%E6%97%85%E6%B8%B8/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"挪威3日游(1)","description":"挪威奥斯陆一日游游记：维格兰雕塑公园、大学与皇宫见闻，并附挪威缩影行程与奥斯陆卡购买攻略，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://ilongda.com/img/norway/park1.jpeg","wordCount":1072,"datePublished":"2020-01-27T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.940Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/norway/"},"url":"https://ilongda.com/2020/norway/","inLanguage":"zh-CN","keywords":["旅游"],"articleSection":["旅游"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"旅游","item":"https://ilongda.com/categories/旅游/"},{"@type":"ListItem","position":3,"name":"挪威3日游(1)","item":"https://ilongda.com/2020/norway/"}]}</script><h1 id="题外话"><a href="#题外话" class="headerlink" title="题外话"></a>题外话</h1><p>因为团队中有位同事是挪威人， 我们经常听他讲一些挪威的事情， 还是对挪威蛮好奇的。 这次挪威转一圈， 发现挪威还是真的挺漂亮的， 冬天可以滑雪， 夏天可以看各种风景， 尤其是峡湾风景， 挪威号称是峡湾之国， 到处可以看到河流， 河流常常环山而下， 又常常在峡谷深处。 另外， 挪威还是看到各种艺术作品， 尤其是油画。 </p><h1 id="行程"><a href="#行程" class="headerlink" title="行程"></a>行程</h1><p>第一天，在奥斯陆转1天， 第二天参加挪威缩影旅行， 第三天卑尔根随便逛， 第4天飞瑞典。其中奥斯陆有很多不错展馆，没有逛完，略有遗憾。 </p><h2 id="攻略"><a href="#攻略" class="headerlink" title="攻略"></a>攻略</h2><ol><li>到了奥斯陆后， 可以做火车到central station， 然后到tourist information 处， 买张奥斯陆卡或者奥斯陆交通卡都可以， 顺便也可以要一份 《奥斯陆旅游指南》</li><li>挪威缩影， 直接可以在淘宝或蚂蜂窝购买， 价格都差不多， 购买了后， 是电子票， 可以打印出来或者保存在手机中， 一路会有人来检查。 到了奥斯陆central station 后， 在大屏幕上找到千万卑尔根的火车的站台（通常是早上8点25）</li><li>卑尔根， 卑尔根号称是卑尔根最漂亮的城市， 却是值得随便逛逛。</li></ol><p>奥斯陆，我们还有非常多的景点，我们没有玩到，这个城市，建议可以再多呆上2天，体会体会。</p><span id="more"></span><h1 id="奥斯陆"><a href="#奥斯陆" class="headerlink" title="奥斯陆"></a>奥斯陆</h1><p>《奥斯陆旅游指南》上有很多旅游推荐项目， 如果在奥斯陆呆超过1天或2天的话， 推荐可以看看这个介绍，上面找一些不错的推荐项目。</p><p>因为我们check in 酒店再出来， 已经3点了，其实完的东西已经不多了。</p><p>不过，好在还是转了几个地方，<br>强烈推荐维格兰雕塑公园， 维格兰雕塑公园非常漂亮， 即使没有维格兰的雕塑，整个公园景色也是十分漂亮，很多挪威人在这里跑步，闲逛，而维格兰的雕塑更是让这个公园升华到不是一座普通的公园，拥有着自己独特含义和独特思考的公园。<br>这里引述一段介绍<br>维格兰雕塑公园，又名弗罗格纳公园，是一座以雕像为主题的公园，园内展出了挪威雕像家古斯塔夫·维格兰的212座雕像作品。公园内的雕像集中突出人类“生与死”的主题，从婴儿出世开始，经过童年、少年、青年、壮年、老年，直到死亡，反映人生的全过程，发人深思。在众多雕塑中最著名的当属“愤怒的男孩”(Sinnataggen)和大石柱(The Monolith)。巨型石柱十分显眼，足有14米高，石上共雕刻了121个人物。至于“愤怒的男孩”，位于前往巨型石柱的小桥上的左侧，不留意很容易错过。</p><p>先来一个全景<br><img data-src="/img/norway/park1.jpeg"  alt="挪威3日游(1)"></p><p>挑选几张有意思的</p><iframe height=498 width=510 src="http://player.youku.com/embed/XNDUzNDMwNjcyNA" frameborder=0 allowfullscreen></iframe><img data-src="/img/norway/park7.jpeg"  alt="挪威3日游(1)"><img data-src="/img/norway/park8.jpeg"  alt="挪威3日游(1)"><img data-src="/img/norway/park10.jpeg"  alt="挪威3日游(1)"><img data-src="/img/norway/park11.jpeg"  alt="挪威3日游(1)"><img data-src="/img/norway/park13.jpeg"  alt="挪威3日游(1)"><img data-src="/img/norway/park14.jpeg"  alt="挪威3日游(1)"><p> 夕阳下的维格兰公园，红色的夕阳照在雕塑上，像敷了一层红色的薄纱。<br><img data-src="/img/norway/park3.jpeg"  alt="挪威3日游(1)"><br><img data-src="/img/norway/park9.jpeg"  alt="挪威3日游(1)"></p><p>逛完公园， 做电车随便乱逛， 先去了viking 博物馆，但时间太晚，人家已经闭馆了，于是再坐电车回到市区， 先到了奥斯陆大学<br><img data-src="/img/norway/park5.jpeg"  alt="挪威3日游(1)"></p><p>因为挪威皇宫就在附近，于是我们也顺便去了一下挪威皇宫， 挪威皇宫是欧洲最简陋的，也是最平易近人的皇宫<br><img data-src="/img/norway/park6.jpeg"  alt="挪威3日游(1)"></p><p>在奥斯陆码头一家米其林餐厅，享用了一顿西餐， 在这里第一次吃生的生蚝，以前在国内不敢吃， 加上一些酱汁，味道非常鲜。</p><img data-src="/img/norway/park2.jpeg"  alt="挪威3日游(1)"><p>吃完饭， 吃的太饱了， 于是在码头上闲逛， 远处就能清晰看到阿克斯胡斯城堡， 于是决定围绕它走了一圈， 因为天色太晚， 不能买票进去， 走一圈也算完成一件心愿吧。<br><img data-src="/img/norway/park4.jpeg"  alt="挪威3日游(1)"><br><img data-src="/img/norway/park12.jpeg"  alt="挪威3日游(1)"></p><p>转完城堡，好累啊， 已经迈不开腿，赶紧回酒店睡觉，还好酒店也不是很远， 又坚持了10分钟回到酒店。 </p>]]>
    </content>
    <id>https://ilongda.com/2020/norway/</id>
    <link href="https://ilongda.com/2020/norway/"/>
    <published>2020-01-27T11:42:57.000Z</published>
    <summary>挪威奥斯陆一日游游记：维格兰雕塑公园、大学与皇宫见闻，并附挪威缩影行程与奥斯陆卡购买攻略，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>挪威3日游(1)</title>
    <updated>2026-06-09T08:46:25.940Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="旅游" scheme="https://ilongda.com/categories/%E6%97%85%E6%B8%B8/"/>
    <category term="旅游" scheme="https://ilongda.com/tags/%E6%97%85%E6%B8%B8/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"赫尔辛基之旅","description":"芬兰赫尔辛基一日游游记：西贝柳斯公园管风琴雕塑、岩石教堂与世界最便宜 LV 等北欧人文与建筑见闻分享，更多细节与示例见正文。","image":"https://ilongda.com/img/helsinki/xibei.jpg","wordCount":1678,"datePublished":"2020-01-25T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.935Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/helsinki/"},"url":"https://ilongda.com/2020/helsinki/","inLanguage":"zh-CN","keywords":["旅游"],"articleSection":["旅游"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"旅游","item":"https://ilongda.com/categories/旅游/"},{"@type":"ListItem","position":3,"name":"赫尔辛基之旅","item":"https://ilongda.com/2020/helsinki/"}]}</script><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>赫尔辛基是芬兰的首都， 也是芬兰最大的港口城市， 也是芬兰经济、政治、文化、旅游和交通中心，世界著名的国际大都市。该市已连续多年被评为全球最宜居的城市之一。 该市同时也是全球幸福感最高的城市之一。</p><p>相对北欧的其他几个国家而言，人文和艺术的氛围略微逊色一下，但整体而言，还是极具北欧风格，很多建筑颜色分明，街道五彩缤纷。 导游推荐了几个地方， </p><ol><li>西贝柳斯公园</li><li>岩石教堂</li><li>红白教堂</li><li>购物， 因为芬兰有最高的退税率， 因此这里的lv 号称是全球最便宜的lv 专卖店。</li></ol><p>个人感觉，赫尔辛基最好的方式是白天玩一天，晚上做dfds 游轮去瑞典斯德哥尔摩， 即欣赏了傍晚的港湾，有省一晚的酒店。<br>因为飞猪给我们定的酒店是当地为数不多的5星酒店之一，而且位于市中心，所以，我们最终还是入住酒店，然后第二天再启程到挪威的奥斯陆。</p><span id="more"></span><h2 id="西贝柳斯公园"><a href="#西贝柳斯公园" class="headerlink" title="西贝柳斯公园"></a>西贝柳斯公园</h2><p>西贝柳斯公园，景色其实不错，旁边是波罗的海，而西贝柳斯公园内的两座雕像是园内的最大亮点，最显眼的一座由600根空心钢管组成，呈波浪状排列，约有6.5米高，酷似一架巨型管风琴，风吹过之时，这架“管风琴”会发出悦耳玄妙的声音，与周边的花草树木交相应和。这座抽象派的雕塑作品由芬兰著名的雕塑家埃拉·希尔图宁设计，以此来表现西贝柳斯交响乐的精髓所在。这座管风琴雕塑亦已成为赫尔辛基的地标性建筑。</p><p>位于管风琴附近的第二座雕像是西贝柳斯本人的头像，于1967年西贝柳斯逝世10周年之际完成。这尊音乐大师的金属头像被镶嵌在一旁的红色岩石上，供人瞻仰缅怀。<br><img data-src="/img/helsinki/xibei.jpg"  alt="赫尔辛基之旅"></p><p>餐馆西贝柳斯背后有一些故事，推荐读者网上去搜一些西贝柳斯的故事。<br>管风琴在风的吹动下，会发出呜呜的声音，象征着芬兰人反抗压迫的呐喊。</p><h2 id="岩石教堂"><a href="#岩石教堂" class="headerlink" title="岩石教堂"></a>岩石教堂</h2><p>岩石教堂，非常漂亮， 和欧洲绝大部分教堂都是完全不一样，他更像一个音乐厅， 我摘一段话<br>“岩石教堂又名坦佩利奥基奥教堂，位于赫尔辛基市中心坦佩利岩石广场，由建筑师苏马连宁兄弟精心设计，是世界上唯一建在岩石中的教堂，也是赫尔辛基最著名和不错过的景点之一。</p><p>历史背景<br>岩石教堂建成于1969年2月，事实上在此处修建教堂的方案早在1930年已经存在，但是由于二战爆发，被迫搁浅。战后通过公开募集选择了现在的方案。人们来到这里都会为这座别具创意的杰作惊叹不已，难以想象一整块坚硬的岩石内部是如何被打造成一座教堂的。</p><p>建筑之美<br>当你站在教堂外，映入眼帘的是一块巨大的岩石，看不到一般教堂所具有的尖顶和钟楼，甚至都注意不到教堂的所在，只有一个直径20多米的淡蓝色铜制圆形拱顶暴露在岩石的最上面。这是因为教堂修建于一块巨大的岩石中，将岩石挖开后于上方修建了玻璃顶棚，实现自然采光，而教堂的外墙就是岩石本身。<br>教堂入口设计成隧道，内部墙面仍为原有的岩石，整座教堂如同着陆的飞碟一般，非常奇特。屋顶采用圆顶设计，有一百条放射状的梁柱支撑，同时镶上透明玻璃，有了自然采光后丝毫感觉不到身处岩石内部。”</p><p>岩石教堂又名坦佩利奥基奥教堂，位于赫尔辛基市中心坦佩利岩石广场，由建筑师苏马连宁兄弟精心设计，是世界上唯一建在岩石中的教堂，也是赫尔辛基最著名和不错过的景点之一。</p><img data-src="/img/helsinki/rock.jpg"  alt="赫尔辛基之旅"><img data-src="/img/helsinki/rock1.jpg"  alt="赫尔辛基之旅"><h2 id="乌斯别斯基东正教堂"><a href="#乌斯别斯基东正教堂" class="headerlink" title="乌斯别斯基东正教堂"></a>乌斯别斯基东正教堂</h2><p>乌斯别斯基东正教堂, 是我这几天参观教堂中， 内部装饰最华丽的一个教堂， 教堂离我们住的比较近， 走过去就到了</p><img data-src="/img/helsinki/red2.jpeg"  alt="赫尔辛基之旅"><img data-src="/img/helsinki/red1.jpeg"  alt="赫尔辛基之旅"><img data-src="/img/helsinki/red3.jpeg"  alt="赫尔辛基之旅"><p>这里引述蚂蜂窝上一段介绍<br>芬兰首都赫尔辛基的乌斯别斯基大教堂建于1862至1868年间，外观的金绿圆顶和红砖墙很为醒目，具有俄罗斯的建筑风格。教堂的颜色和式样都充满着神秘的色彩。乌斯别斯基东正教教堂位于赫尔辛基市中心，它的十三个金顶，与古雅红砖外墙，在赫尔辛基城市轮廓间，突显一抹俄罗斯在芬兰宗教上所留下的遗痕。显眼的金圆顶和红砖砌成的教堂显得格外凝重，旁边两棵大树与教堂交相辉映，正好是俄罗斯风情渗入芬兰历史的见证。精雕细琢的拱顶和花岗岩石柱是乌斯本斯基大教堂的两大特色，教堂内部的绘画都是由俄国画家完成的，完全保留了传统东正教堂的艺术风格。</p><h2 id="议会广场-赫尔辛基大教堂"><a href="#议会广场-赫尔辛基大教堂" class="headerlink" title="议会广场&#x2F;赫尔辛基大教堂"></a>议会广场&#x2F;赫尔辛基大教堂</h2><p>其实议会广场&#x2F;赫尔辛基大教堂 参观点并不多，教堂内部和乌斯别斯基东教堂相对，逊色非常多， 就教堂前的广场略有可看。这个教堂又称白教堂</p><img data-src="/img/helsinki/white2.jpeg"  alt="赫尔辛基之旅"><img data-src="/img/helsinki/white1.jpeg"  alt="赫尔辛基之旅"><h2 id="芬兰国家博物馆"><a href="#芬兰国家博物馆" class="headerlink" title="芬兰国家博物馆"></a>芬兰国家博物馆</h2><p>做电车，随便逛， 突然发现来到了芬兰国家博物馆， 于是就进去逛了一把， 芬兰国家博物馆，主要是介绍芬兰的历史和发展过程，相对而言，没有什么艺术展品，可观赏性要略微逊色一下， 不过也是一个kill time的好时光。</p><img data-src="/img/helsinki/museum.jpg"  alt="赫尔辛基之旅"><h2 id="电车环城游"><a href="#电车环城游" class="headerlink" title="电车环城游"></a>电车环城游</h2><p>在芬兰可以直接买票， 票在2个小时内都是有效，随便做，于是最好的方式就是环城电车游。</p><img data-src="/img/helsinki/daughter.jpeg"  alt="赫尔辛基之旅">阿曼达雕像, 波罗的海女儿<img data-src="/img/helsinki/music.jpeg"  alt="赫尔辛基之旅">国家音乐厅门前<img data-src="/img/helsinki/light.jpeg"  alt="赫尔辛基之旅">最火的网红餐厅前， 玻璃屋前的街景]]>
    </content>
    <id>https://ilongda.com/2020/helsinki/</id>
    <link href="https://ilongda.com/2020/helsinki/"/>
    <published>2020-01-25T11:42:57.000Z</published>
    <summary>芬兰赫尔辛基一日游游记：西贝柳斯公园管风琴雕塑、岩石教堂与世界最便宜 LV 等北欧人文与建筑见闻分享，更多细节与示例见正文。</summary>
    <title>赫尔辛基之旅</title>
    <updated>2026-06-09T08:46:25.935Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="旅游" scheme="https://ilongda.com/categories/%E6%97%85%E6%B8%B8/"/>
    <category term="旅游" scheme="https://ilongda.com/tags/%E6%97%85%E6%B8%B8/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"罗瓦涅米之旅","description":"芬兰罗瓦涅米极光冬季游记：破冰船、极光摄影团与森林滑雪体验，附 Nordic Travels 官网预订与行程延改攻略，更多细节与示例见正文。","image":"https://ilongda.com/img/rovaniemi/aurora.jpeg","wordCount":2733,"datePublished":"2020-01-23T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.943Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/rovaniemi/"},"url":"https://ilongda.com/2020/rovaniemi/","inLanguage":"zh-CN","keywords":["旅游"],"articleSection":["旅游"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"旅游","item":"https://ilongda.com/categories/旅游/"},{"@type":"ListItem","position":3,"name":"罗瓦涅米之旅","item":"https://ilongda.com/2020/rovaniemi/"}]}</script><h1 id="题外话"><a href="#题外话" class="headerlink" title="题外话"></a>题外话</h1><p>原本计划去新西兰玩一圈， 攻略和行程都差不多计划好， 正准备双11 开始腐败时， 忽然老婆说飞猪上有亏本卖的芬兰极光之旅， 从来没有去看过极光， 也从来没有去那么冷的地方玩，一直很想玩高山滑雪， 虽然对芬兰那边的极冷环境有一点点害怕，但看了飞猪的极光之旅，感觉整个行程，还是充满很多惊喜和欢乐。另外，还有一点特别打动我的是， 飞猪可以将行程延几天， 但实际上， 在双11 那天， 让飞猪去问， 能不能延5天，答复过年期间机票很紧张， 无法延续， 再次咨询能否延3天， 依旧答复不可以。 最后一怒之下，决定彻底放飞自己， 在整个芬兰之后的行程里， 自己安排了6天， 飞挪威，丹麦， 瑞典。 </p><p> 芬兰这个6天 机票 + 酒店的性价比非常高，完全不需要导游， 所有的游玩项目都是标准件， 想玩什么就直接上飞猪或<span class="exturl" data-url="aHR0cDovL3d3dy5ub3JkaWN0cmF2ZWxzLmV1Lw==">www.nordictravels.eu<i class="fa fa-external-link-alt"></i></span>(当地一个很大旅游公司， 更推荐直接在这个上面进行预定)直接预定。完全不需要导游， 如果想要延期玩几天的划， 直接申请延长5天， 多余的5天，可以去挪威，瑞典玩一下。 注意，有可能在双11 这天， 因为订单比较多， 反而不能延长， 建议牺牲掉双11的红包， 提前一个月进行预定。 </p><p>整个北欧冬天白天时间非常短，早上9点天才灰蒙蒙的亮，在罗瓦涅米下午3点天就彻底黑了， 在赫尔辛基下午4点天就黑了， 其他如挪威丹麦稍微好一点，能坚持到5点，天才黑。不过，傍晚和朝霞是非常漂亮，尤其是阳光照在厚厚积雪的屋顶，漂亮极了。 </p><p>先上几张图片镇楼<br><img data-src="/img/rovaniemi/aurora.jpeg"  alt="罗瓦涅米之旅"><br><img data-src="/img/rovaniemi/aurora1.JPG"  alt="罗瓦涅米之旅"></p><p>另外有那种雪地摩托，也值得推荐， 另外关于哈士奇雪橇和驯鹿雪橇， 有短途的500m和1千米， 也有远一点的10km，费用不一样， 需要注意一点。</p><span id="more"></span><h1 id="攻略"><a href="#攻略" class="headerlink" title="攻略"></a>攻略</h1><p>罗瓦涅米，我们是过年前一周去的， 是1月20 ～ 23， 还不是一年最冷的时候，零下1度到10度（导游一直说我们去的这一年是最暖和的一年），非常适合游玩， 等我们离开的时候，温度不断下降， 离开后4天，温度达到零下25度。罗瓦涅米其实也是一个只适合在冬天游玩的地方。 这个地方很多有非常多的游玩项目，自己想玩什么就玩什么。 </p><p>建议1. 游玩项目，建议直接上<span class="exturl" data-url="aHR0cDovL3d3dy5ub3JkaWN0cmF2ZWxzLmV1Lw==">www.nordictravels.eu<i class="fa fa-external-link-alt"></i></span> 这家官网上直接预定， 这家公司，非常大， 所有游玩项目都是官方价格， 童叟无欺， 而且很多游玩比如极光之类的游玩，他们都很专业。 </p><p>我们在罗瓦涅米， 玩了， 破冰船， 极光摄影团， 极光巴士， 罗瓦涅米一日游， 森林滑雪。 整个体验下来。好玩程度， 从高到底</p><ol><li>极光摄影团</li><li>森林滑雪</li><li>破冰船</li><li>罗瓦涅米一日游</li></ol><p>晚上看了， 有人推荐冰钓也不错， 不过冰钓主要是看风景，可能带小孩不是特别方便。 另外有那种雪地摩托，也值得推荐， 另外关于哈士奇雪橇和驯鹿雪橇， 有短途的500m和1千米， 也有远一点的10km，费用不一样， 需要注意一点。</p><h2 id="极光之旅"><a href="#极光之旅" class="headerlink" title="极光之旅"></a>极光之旅</h2><p>来罗瓦涅米，最好玩也是印象最深刻的是极光之旅， 来罗瓦涅米如果没有看到极光，那相当于白来一趟。 如果碰上极光很好的夜晚， 而你又特别喜欢摄影的话， 可以在那里好好享受几个小时的摄影时光。通常旅行社会开车把大家带到一个很偏远的地方， 这个地方光污染比较少， 经常是在一个冰湖上。在那里视野非常开阔， 能见度也非常的高。 我在这里碰到了我有史以来最明亮的夜晚。 </p><img data-src="/img/rovaniemi/beauty.JPG"  alt="罗瓦涅米之旅">放一张美女镇楼<img data-src="/img/rovaniemi/love.JPG"  alt="罗瓦涅米之旅">在极光下， 向爱人示爱或求婚， 是不是非常浪漫呢？<img data-src="/img/rovaniemi/me.jpeg"  alt="罗瓦涅米之旅"><img data-src="/img/rovaniemi/me2.JPG"  alt="罗瓦涅米之旅">应广大群众要求，放2张楼主照片看一次极光，一位大人差不多要900一次，如果提前预定了，但当晚没有极光或者云比较多，也看不到，白白浪费钱， 因此，建议不要提前进行预定， 等到了罗瓦涅米， 在下午5点时，根据情况再决定是否要预定还是不预定。 1. 下2个app， "Aurora Map" 和 “Aurora Now” 2个app，2. 下了app 后，在app上看当地的kp （是叫电磁强度还是太阳风暴强度不记得了），里面会对当晚或后面几天kp进行预测，  如果当晚kp 不高（小于3），就建议不要预定了。 大于等于3 就可以考虑了3. 如果5点左右还在下雨或云层比较厉害， 在app再看看cloud 预测，如果cloud 很厉害，也不用看4. 如果kp和cloud 都满足条件， 赶紧上www.nordictravels.eu 这家网站， 下订单， 可以直接打电话给他们，他们提供中文电话服务。 5. 下单推荐下极光摄影团项目， 旅行社他们一个大巴40/50人，会配上2个摄影师，专门帮助大家拍照， 而且他们拍照经验相对而已还是非常丰富， 比大部分摄影菜鸟还是要强很多。 另外第二次报名时， 直接半价。 <p>这里吐槽一下我们接机的导游zero， 我们第二天晚上， 下着小雨， 但却不停忽悠我们说， 今晚的kp是这几天最高，建议我们购买她的极光巴士， 结果差不多2千块全部打水飘， 就为了赚一些中介费，纯粹是坑人。 </p><h2 id="滑雪"><a href="#滑雪" class="headerlink" title="滑雪"></a>滑雪</h2><p>滑雪其实也是罗瓦涅米最好玩的项目， 但不知为啥，国人似乎玩的比较少， 罗瓦涅米有很多滑雪场， 这些滑雪场都是免费的， 只要你有装备， 你想怎么划就怎么划。 菜鸟就去菜鸟的滑雪场， 高手就去高手的滑雪场， 茄子萝卜各有所爱。 推荐的玩法， 如果在飞猪上下单， 记得提前几天下单， 另外一点， 滑雪是全英文的， 没有中文讲解， 因此要稍微懂一些英文， 即使在飞猪上选择中文， 教练还是英文教练，选择中文还要贵200. 也可以上<span class="exturl" data-url="aHR0cDovL3d3dy5ub3JkaWN0cmF2ZWxzLmV15LiK5LiL5Y2VLi8=">www.nordictravels.eu上下单。<i class="fa fa-external-link-alt"></i></span> </p><p>滑雪的教练，会先问，滑雪水平如何， 如果是菜鸟， 会带到菜鸟的场地， 然后先教大家简单的滑雪动作， 比如刹车， 防止摔伤， 怎么爬坡，怎么滑行等， 动作非常简单，没有玩的，基本上练1个小时后差不多就会基本动作了。另外芬兰的滑雪装备比国内的要非常优秀， 很轻， 和每个人的身高体重很适配， 比国内的滑雪板使用起来更舒适和顺手。 教练带我们练了一个小时后， 带我们到一个菜鸟滑道上，带我们玩了一圈， 然后我们自己就在这个菜鸟滑道上玩了2个小时， 坦白讲，大人和小孩都玩的很开心。 </p><img data-src="/img/rovaniemi/ski.jpeg"  alt="罗瓦涅米之旅">推荐玩法：1. 第一天在飞猪或nodict官网下单， 让他们的教练先教我们一下基本的动作， 另外， 也会带我们去滑雪场和租赁中心租设备。2. 如果后面想自己去滑雪， 就可以自己几个人组个团，租一辆车，带上中午吃的一些东西， 自己去昨天的滑雪场和租赁中心进行租赁。 <h2 id="破冰船"><a href="#破冰船" class="headerlink" title="破冰船"></a>破冰船</h2><p>破冰船， 就是搭乘一艘破冰船，航线在冰封的海面上。 破冰船每天都开一次， 行走的路线非常固定，行走路线的冰层每天都被破， 因此没有路线上的冰没有那么后， 路线边上的冰大概看了一下30公分左右。</p><img data-src="/img/rovaniemi/icebreak.jpeg"  alt="罗瓦涅米之旅">然后船航行差不多一个小时后， 到了一个稍微开阔的水域， 船停在这里，让大家穿着虾服， 可以下水游泳， 游完泳， 大家上船换羽绒服，然后到冰封的海面上玩， 堆堆雪人拍拍照, 冰封的海面，一望无际， 小朋友可以在这里打打雪仗，堆堆雪人，大人可以多拍拍照片。 <img data-src="/img/rovaniemi/swim.jpeg"  alt="罗瓦涅米之旅">另外， 玩破冰船的，中午会在瑞典一个小镇上 吃午餐，这个小镇的风景非常漂亮，无论是冬天还是夏天，景色都是非常迷人， 可以驻足拍照一段时间。<img data-src="/img/rovaniemi/icetown.jpeg"  alt="罗瓦涅米之旅"> 整体而已， 破冰船，玩一次就够了，下次估计就不会再想玩了。 一点小建议就是， 破冰船如果想玩，可以提前在双11 预定， 会有一点小红包。 <h2 id="圣诞老人村一日游"><a href="#圣诞老人村一日游" class="headerlink" title="圣诞老人村一日游"></a>圣诞老人村一日游</h2><p>一日游， 基本行程就是带大家去看一下日出，然后到圣诞老人村， 见见圣诞老人， 坐一下哈士奇雪橇（只有1千米）， 坐一些驯鹿雪橇（大概1千米还是2千米）， 最后去一下北极博物馆， 其中北极博物馆，导游的讲解比较详细，还是给行程增色不少。 </p><img data-src="/img/rovaniemi/husky.jpeg"  alt="罗瓦涅米之旅"><img data-src="/img/rovaniemi/reindeer.jpeg"  alt="罗瓦涅米之旅"><p>圣诞老人村，其实挺漂亮的，无论是白天阳光下的木屋，还是晚上的冰雕餐馆，很适合在这里玩上一天，  玩的项目也多，小朋友可以拿着滑雪板在这里坐滑雪板滑雪。很适合在这里玩上一天， </p><img data-src="/img/rovaniemi/house.jpeg"  alt="罗瓦涅米之旅">如果住圣诞老人村的话， 其实这个活动，不需要报这个项目， 因为， 基本上见圣诞老人， 哈士奇雪橇，驯鹿雪橇都是在圣诞老人村， 自己都可以自费玩， 而且费用上自己玩可能更便宜一点， 另外一点时间上会比较自由， 不过，还是推荐雪地摩托， 我看很多老外都有选择雪地摩托，还是对这个有点期待。 <p>北极圈的日出还是很漂亮的，不过不一定要跟团， 自己平时也能看得到， </p><p>如果想要自由行的话， 圣诞老人村差不多非常适合度假和休息， 不过房间非常紧俏， 如果预定，差不多提前2个月就需要开始预定。 而且价格也不便宜</p>]]>
    </content>
    <id>https://ilongda.com/2020/rovaniemi/</id>
    <link href="https://ilongda.com/2020/rovaniemi/"/>
    <published>2020-01-23T11:42:57.000Z</published>
    <summary>芬兰罗瓦涅米极光冬季游记：破冰船、极光摄影团与森林滑雪体验，附 Nordic Travels 官网预订与行程延改攻略，更多细节与示例见正文。</summary>
    <title>罗瓦涅米之旅</title>
    <updated>2026-06-09T08:46:25.943Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《FusionInsight LibrA Huawei’s Enterprise Cloud Data Analytics Platform》解读","description":"华为 FusionInsight LibrA（高斯200）VLDB 论文解读：MPP 架构演进、SQL on Hadoop 与自动 tuning 等工程特性","image":"https://ilongda.com/img/libr-arch.png","wordCount":2682,"datePublished":"2020-01-11T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.962Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/docs/paper/libr/"},"url":"https://ilongda.com/2020/docs/paper/libr/","inLanguage":"zh-CN","keywords":["论文","数据库"],"articleSection":["论文","数据库"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"《FusionInsight LibrA Huawei’s Enterprise Cloud Data Analytics Platform》解读","item":"https://ilongda.com/2020/docs/paper/libr/"}]}</script><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>2018年华为在vldb 上发表了论文《FusionInsight LibrA: Huawei’s Enterprise Cloud Data Analytics Platform》。 这篇论文罗列了LibrA 的架构和很多powerful的功能， 整体看下来， 特别亮瞎眼睛的功能到没有，但在工程的角度，介绍很多很接地气的做法， 比起很多列一大堆数据公式， 一大堆机器学习的算法论文，可读性更强， 也更好理解数据库的实现和优化。 </p><img data-src="/img/libr-arch.png"  alt="《FusionInsight LibrA Huawei’s Enterprise Cloud Data Analytics Platform》解读"><p>FusionInsight MPPDB, 又称Libr, 后来又改名为高斯200， 是针对olap 分析的一款mppdb 数据库。 高斯200 是高斯部门很成功的一款产品。从2012年开始研发， 2014年第一代原型研发出来， 现在已经在全球大量使用， 包括最大的金融企业工行。并且以用户为导向，实现了很多powerful的功能，如在线扩容， 自动tuning， sql on hdfs， 智能jit 编译执行（llvm code gen）， 提供行列混存， 数据压缩特性， 智能workload管理， 为提高高可用增加重试失败request， 用sctp 协议替换tcp协议以提高scalability等等。 </p><span id="more"></span><h1 id="发展史"><a href="#发展史" class="headerlink" title="发展史"></a>发展史</h1><p>最早的原型是基于postgres-xc来实现, 采用share nothing 架构， 支持ansi 2008 sql 语法标准。 2014年第一代原型，完成向量化执行， 并行线程执行， 并且主要用于分布式存储的元数据分析。 </p><p>第二代推广给金融和电信领域用户， 第二代以后以用户需求为导向， 增加很多实用的功能， 如系统可用性， 自动tuning， query 异构数据（尤其云上）， 利用新硬件等。 </p><p>2016年开始支持sql-on-hadoop, 可以让mpp engine跑在hadoop上， 而不用把数据从hdfs迁移到libr上， 用户对这个需求十分强烈，并让libr 在2016年成为FusionInsight 的产品。 </p><p>2017年Libr 上云， 四大特性， 1. 系统可用性； 2. 自动tuning； 3. 可以query 大量异构数据模型；4. 充分利用新硬件。 </p><h1 id="功能介绍"><a href="#功能介绍" class="headerlink" title="功能介绍"></a>功能介绍</h1><ol><li>高可用， 增加节点或升级，通过在线扩容或在线升级， 很少影响客户业务。 系统可以线性扩容， 以支持几百台机器去处理高并发ad-hoc。</li><li>自动tuning， oracle的自驱动数据库强调了数据库的自我管理和自我tuning， 利用机器学习对runtime的反馈进行自我tuning。 </li><li>异构存储， 客户存有各种现成的数据， DataLake 变得流行， 2016年提供SQL-ON-HADOOP 后， 这个功能大受欢迎。 </li><li>支持新硬件， 现代机器配置大内存， 数据库可以reside in 内存， fast io device如ssd， optane。 </li><li>通过在执行引擎上使用jit 动态编译 code generate 的方式来对query 进行加速。 query 产生的特定的runtime机器码可以省掉传统的解析开销（理论上还有大量的出入栈和虚函数调用等开销）， 最后对结果进行分析， jit 编译效果由编译带来的开销和优化的可执行代码带来性能提升 来决定。<br>支持行存和列存 混存。 </li><li>向量化执行引擎使用了最新的simd 指令集。</li></ol><h1 id="事务支持"><a href="#事务支持" class="headerlink" title="事务支持"></a>事务支持</h1><ol><li>datanode 是分区来管理， 支持本地acid 语义。跨分区一致性由二阶段提交和全局事务管理器来进行管理。 </li><li>创新的使用了gtm-lite, 分布式事务管理， 单分区事务可以被加速， 因为避免了获取中心事务id 和全局snapshot。 </li><li>支持read committed 事务隔离级别。</li></ol><h1 id="高可用强化"><a href="#高可用强化" class="headerlink" title="高可用强化"></a>高可用强化</h1><h2 id="连接优化"><a href="#连接优化" class="headerlink" title="连接优化"></a>连接优化</h2><p>如果使用tcp&#x2F;ip 协议， 当几百台机器互联是， connection 急剧增加， 如1000个node集群， node 之间的连接会超过1百万（1000个节点 * 100 个并发 * 10个exchange operator）， 研发了一种新的协议， 每个数据交换的提供方和消费方组成一个虚拟连接。 多个虚拟连接可以共享一个物理连接， libr 选择了sctp协议， 它支持可靠传输， 一个物理连接上可以支持64k的逻辑连接， 并且支持带外流控。 </p><h2 id="分组模式"><a href="#分组模式" class="headerlink" title="分组模式"></a>分组模式</h2><p>高可用主动模式和同步机制， 节省存储空间至关重要， 当备机挂了后， 会启动一个节点只做log-copy操作来提升可用性， 当备机挂了， 主节点依旧可执行bulk load和dml。 </p><h2 id="资源管理器"><a href="#资源管理器" class="headerlink" title="资源管理器"></a>资源管理器</h2><p>workload 管理器， 管理query的并发书， 做了限流功能， 分为3个部分， 资源池， workload组和一个controler。 资源池管理内存和磁盘i&#x2F;o， 设置各种阀值来决定是否执行。workload group 用于分配请求的query到资源池。 用应用名来标示query（估计资源分组）。</p><p>控制器，评估query的cost和系统当前的可用资源来决定是否运行query， 当不满足时，query 进入等待队列。 资源的预申请和反馈用于追踪系统的可用资源。 </p><h2 id="在线扩容"><a href="#在线扩容" class="headerlink" title="在线扩容"></a>在线扩容</h2><img data-src="/img/libr-redistribute.png"  alt="《FusionInsight LibrA Huawei’s Enterprise Cloud Data Analytics Platform》解读"><p>在线扩容， 最大的问题是如何将数据分布到新的节点， 通常是对distribute key 进行计算来决定（hash算法， round-robin， modulo算法）， 通常这些算法会依赖节点数， 数据重新分布需要恢复一致性和在分配算法和实际数据位置。 hash算法可能导致数据倾向， 导致有些节点out of space， 这种情况下，需要采用新的hash 算法来进行平衡。 简单的一种做法是使用影子表， 原始表可以继续被查询， 直到 数据被分布到新的节点， 分布属性是不含新节点。 一种让数据可被访问，在重分布过程中， 使用random算法替换hash 算法。 这种方式会让性能下降， 相关查询（collocation join）不支持，另外写或修改也是不允许的。 </p><p>librA 使用shadow table， 但没有让表只读， 让表append-only， 并阻止存储空间的recycle。 这种方式可以很快识别哪些record是新增的，哪些是历史的， 创建一个表存储删除的数据， 然后lock 表， apply append的delta， 再apply delete的delta， 于此同时， shadow table 会增加一列隐藏列rowid。 这样好处可以一个minibatchi 接着一个minibatch 执行， 并且支持重来和resume。 </p><p>算法。</p><ol><li>创建T的影子表。</li><li>Mark t as append-only</li><li>disable garbage collect on T</li><li>create delete-delat D for delete on T</li><li>redistribute a segment from T 到 S</li><li>apply D on S, 并且重试D  当apply D完时</li><li>提交修改</li><li>重复执行执行，直到T的数据小于一个阀值</li><li>Lock T, 重复5 和6</li><li>在catalog中， Switch T as S, </li><li>提交修改</li><li>rebuild index</li></ol><h1 id="自动tuning"><a href="#自动tuning" class="headerlink" title="自动tuning"></a>自动tuning</h1><ol><li>基于 data exchange 的cbo来生成mpp 的plan</li><li>cbo 基于vector 执行和多种文件系统如orc</li><li>query rewrite engine, 在olap系统中添加一些额外的 rewrite 很关键</li><li>基于机器学习的cutting edge 技术</li><li>早期的优化器的机器学习是基于统计学， 需要大量的资源投入， 不通的数据来源不同的数据格式又要求合适的精度带来很大的挑战。</li><li>可选捕获执行参数，为后续类似提供精确参考， 这种方式比传统数据收集方式代价要小。</li></ol><h1 id="SPM"><a href="#SPM" class="headerlink" title="SPM"></a>SPM</h1><img data-src="/img/libr-spm.png"  alt="《FusionInsight LibrA Huawei’s Enterprise Cloud Data Analytics Platform》解读"><p>执行器可选捕获执行计划到plan store。 每一个步骤 scan， join， aggregation， 并预估和实际获取的row counts， 一般情况下， 预估值和真实值是有很大出入的。 plan store 另外一个功能是用于sql 审计和离线分析。 </p><ol><li>优化器从plan store中获取statics 用于cbo而不是自己评估的， 如果没有找到，则使用自己评估的<br>plan store 类似一个cache，可以通过api来高效获取数据。 </li><li>plan store的cache 封装不同步骤的信息，保护step type， step prediction和input descritption。 </li><li>早期， 对于scan和join做了statics learning。这个阶段称为selectivity matching.</li><li>除了抽取之前存取的谓词， 自动tuning可以用于类似谓词。 可以收集predicate selectivity的反馈到谓词cache(不同之前的plan store cache)中， 并用它来评估类似的情况。 许多机器学习或statics learning 算法技术可用在这个阶段， 我们称这第二个learning为similarity selectivity。similarity selectivity模型最初用于复杂的predicate 如x》y+ c， x和y都是column，而c是常数。 在date field经常碰到这种情况 如tpch中。 这种predicates 给query 优化带来一种挑战并且他们是一种好的candidates 来做similarity selectivity。 libra 使用knn（k nearest neighbors）来获取similarity selectivity。</li></ol><h1 id="SQL-ON-Hadoop"><a href="#SQL-ON-Hadoop" class="headerlink" title="SQL-ON-Hadoop"></a>SQL-ON-Hadoop</h1><img data-src="/img/libr-sql-on-hadoop.png"  alt="《FusionInsight LibrA Huawei’s Enterprise Cloud Data Analytics Platform》解读">通过pq foreign data wrapper来访问hdfs， bypass hadoop mr 框架， 引入一个调度器， 动态吧文件的分片分配到mpp的节点上进行计算， 一个hdfs目录被影射到db的外表，这个外表支持分区表。 因为hdfs的分区是基于目录的， 优化器可用做一些优化操作从而跳过一些分区而减少io操作。 支持orc或parque 格式， 这些格式内部保护一些index，充分利用这些信息。 hdfs 客户常常有2个额外的要求， 1. dml和acid 支持； 2. 要求更好的性能（需要知道data collocation）<h2 id="性能优化"><a href="#性能优化" class="headerlink" title="性能优化"></a>性能优化</h2><ol><li><p>优化器可用做谓词下推优化操作，减少io。<br>query engine支持向量化执行。<br>使用动态 多维 runtime 过滤 从start join 到分区裁剪。</p></li><li><p>better data collocation<br>商业数据库通过一致性hash算法来达到data collocation， 标准db 一般要求数据shuffle在join或group by之后。</p></li></ol><p>2种data collocation<br>mppdb 和hdfs node之间的data collocation， mapp datanode 读取hdfs的data 通过快速的本地读接口。 </p><p>table collocation在hdfs node， table 被分区到hdfs 不同的data node上，执行co-located join和group 来减少网络shuffle</p><p>当把数据通过mppdb data node 导到hdfs上， 用一个本地描述的table 来记录每个节点每个文件的ownership。 datanode 序列号数据到特定的pax 格式文件（orc&#x2F;parquet）到hdfs。 这个本地table 由一列组成， 如blockid， min&#x2F;max 一个block每个列， 删除列的bitmap。 </p><p>通过hdfs hint 来尽可能本地访问。 </p><h2 id="DML"><a href="#DML" class="headerlink" title="DML"></a>DML</h2><p>block map 决定了block中row的可见性， 先把数据插到row base的delta table， 当delta table到了一定量，刷hdfs （orc&#x2F;parquet）， 如果在delta table删除数据，则直接删除数据， 如果数据在pax file时删除，在block map的bitmap中，标记这个行被删除， 当删除的行超过一定阀值， 执行compaction操作。 </p><p> # 未完待续<br>后续讲了一下优化器的优化<br>以后有机会补充一下 </p>]]>
    </content>
    <id>https://ilongda.com/2020/docs/paper/libr/</id>
    <link href="https://ilongda.com/2020/docs/paper/libr/"/>
    <published>2020-01-11T11:42:57.000Z</published>
    <summary>华为 FusionInsight LibrA（高斯200）VLDB 论文解读：MPP 架构演进、SQL on Hadoop 与自动 tuning 等工程特性</summary>
    <title>《FusionInsight LibrA Huawei’s Enterprise Cloud Data Analytics Platform》解读</title>
    <updated>2026-06-09T08:46:25.962Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Snowflake 架构讨论 《The Snowflake Elastic Data Warehouse》","description":"论文解读 -- 《The Snowflake Elastic Data Warehouse》","image":"https://ilongda.com/img/snowflake.png","wordCount":2416,"datePublished":"2020-01-05T11:42:57.000Z","dateModified":"2024-02-02T13:31:20.275Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/docs/paper/snowflake/"},"url":"https://ilongda.com/2020/docs/paper/snowflake/","inLanguage":"zh-CN","keywords":["论文","数据库"],"articleSection":["论文","数据库"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"Snowflake 架构讨论 《The Snowflake Elastic Data Warehouse》","item":"https://ilongda.com/2020/docs/paper/snowflake/"}]}</script><h1 id="snowflake-随谈"><a href="#snowflake-随谈" class="headerlink" title="snowflake 随谈"></a>snowflake 随谈</h1><h1 id="随想"><a href="#随想" class="headerlink" title="随想"></a>随想</h1><p>今天，如果从事大数据或者OLAP 领域的朋友，都 应该仔细阅读一下snowflake 的论文。 snowflake 是前年看的， 但一直想写一篇介绍的读后感，一直没有下笔， 放在心中， 成为一个疙瘩， 终于在新年新气象的号召下， 终于把当初的阅读笔记找出， 梳理一遍， 列一下snowflake 中，很多有意思的东西。<br />论文原地址 ：<span class="exturl" data-url="aHR0cHM6Ly93d3ctY29uZi5zbGFjLnN0YW5mb3JkLmVkdS94bGRiMjAxNi90YWxrcy9wdWJsaXNoZWQvVHVlc183X01hcmNpbl9aX1hMREItMjAxNi0wNS0yNC1yZWxlYXNlLnBkZg==">The Snowflake Elastic Data Warehouse<i class="fa fa-external-link-alt"></i></span></p><h1 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h1><p>snowflake 的架构呈现出来时， 还是很让人眼前一亮， 这一套架构， 吸收了传统数据库mpp 的架构的优点， 有吸收了云上DLA 架构（serverless）的优势， 整体是一套对云计算非常友好的olap 系统。 这一套系统比传统大数据（hadoop系列&#x2F;emar） 要轻量高效， 比传统的mpp数据库（on-premise的云数据库）又要性价比高， 更便宜更灵活。 </p><p>整套系统是完全部署在云上， 所有的计算节点和service 都是购买虚拟机， 而存储是使用aws 的s3或微软的类s3的对象存储。 整套架构是share disk 的架构， 并且设计了一套都有cache机制，让整个计算层是无状态的， 所有有状态的东西存储到kvstore中， 而前端节点采用微服务， 保障系统的可靠性又让系统的成本最低化。  </p><img data-src="/img/snowflake.png"  alt="Snowflake 架构讨论 《The Snowflake Elastic Data Warehouse》"><span id="more"></span><h1 id="计算"><a href="#计算" class="headerlink" title="计算"></a>计算</h1><p>计算层有个query process pool， virtual warehouse 就是一个用户独享的cluster， 这个virtual warehouse 由几个ec2 来组成。 这个cluster 的规格就由用户购买规格来决定的， 用户购买规格类似购买t-shirt 一样， 就只有xxs –&gt; xxl 等几个规格， 用户不需要涉及多少个cpu&#x2F;内存&#x2F;本地存储等等一系列概念， 非常简单直接， 而且每种规格是以分钟计费。</p><p>当扩容后， 下一分钟后的新sql或者队列中的sql 就可以运行在扩容后virtual warehouse。</p><p>当没有query时， 可以将virtual house 进行自动挂起（不过计算单位还是分钟）， 当query 来时， 自动resume。 （这样的功能，更加证明）<br /><br><br />启动一个session时， 并没有绑定一个virtual warehouse， 直到一个virtual warehouse 绑定到一个session后， 这个session的所有sql才能到virtual warehouse上执行。 一个session只能绑定一个virtual warehouse， 但一个virtual warehouse可以绑定多个session， 甚至绑定一个user， 这样这个user的所有session自动绑定到这个virtual warehouse。  <br /><br><br />不同版本的virtual warehouse 可以使用相同的ec2， 因此可以共享cache。 <br /><br><br />未来snowflake， 可以进行share ec2. <br /><br><br />根据上面提供几点功能， 我对背后架构的一个解读。 </p><ol><li>能提供这么强的弹性， 第一种方式是类似hadoop 这种大集群架构（先表示对论文提到的virtual warehouse 是有ec2 来组成保持一点怀疑，因为大集群方式通常是成本最低的）， 部署一个超大的hadoop， 来了一个sql， 就分配一批docker 来为这个sql 进行服务， docker 隔离了 cpu和内存。 </li><li>第二种方式， 是以虚拟机为调度单元， 有请求时，将用户设定大小的虚拟机集群划给用户， 当没有请求时， 这些虚拟机分配给其他的用户。</li></ol><p> </p><p>从论文的描述来讲， 更倾向于第二种架构， 第一，在云上要保证足够的安全性和隔离性 使用虚拟机会更安全， 但一般来讲，虚拟机是无法达到如此高的灵活性。 但snowflake 做一些投机取巧的事情， 第一， 他是以分钟为单位进行计费， 1分钟足够将一个虚拟机划给另外一个客户。 第二，在数据库领域很少出现一分钟内没有任何请求的case， 很多bi 工具或客户段， 他们都会定时发送请求过来， 激活链接。  </p><p>在基于virtual warehouse由ec2来组成后， snowflake 有一个非常powerful的调度系统， 并且随时在系统中保留了几个hot的ec2， 随时让这几个ec2 扩容到需要扩容的virtual warehouse中。 并向前再推导一步， ec2 的规格应该非常少， 不会超过3种， 这样就能很方便的将一个ec2 从一个virtual warehouse 迁移到另外一个virtual warehouse。 <br /><br><br />另外， snowflake的优化器和执行器 提供一个很强的扩展性， 比如在1000个节点可以运行， 在10个节点也能运行， 只是时间被拉长。 这样印证了系统。 </p><p>snowflake 就支持xxs –&gt; xxl 这些规格， 这样就将主流用户把握住， 避免浪费精力在那种超级大客户上， 这种超级大客户（规模超过xxl）很多时候， 他们需要的是服务， 而不是产品，甚至比拼的是销售。 而且技术上要为这些超级大客户备机器也是一件拉高成本的糟糕事情。 </p><h1 id="存储"><a href="#存储" class="headerlink" title="存储"></a>存储</h1><ol><li>目前的设计， 每个virtual warehouse的cache 是不能共享。</li><li>每个ec2 都有一个本地存储， 当读取数据时，都是从s3 中将数据捞到本地存储中， 当写入数据时， 如果本地磁盘满，会将结果临时存储到s3上， 保障写通畅。 </li><li>每个ec2 上的cache 采用lru 算法。 当扩容时， 采用一致性hash， 如果数据已被cache住，就直接读取， 如果因为扩容， 数据在一致性hash后，飘到其他机器上， 没有关系，重新捞取数据，然后利用lru的自动淘汰算法，将老的数据全部淘汰换成新的。 </li><li>存储格式上， 使用了列村， 并且对列村进行压缩和优化过， 对用户不可见。 </li><li>每张表被分成大小不变的file （这样降低s3 的成本）， 将每个列的元数据信息存储公共的元数据服务里面。 </li><li>支持半结构化数据， json， avro 格式数据</li><li>支持acid – 事物， 号称支持snapshot isolation。 </li><li>支持回收站， 跨region backup， 支持clone。 （以来share disk 和cache 的无状态设计）</li><li>对热点文件做了一个优化， 使用了file stealing技术， 当一个peer 发现他的peer 节点还有文件没有读取， 改变文件的ownership 在当前query下， 这样这个peer帮助忙节点完成一些计算工作。 （这也是为什么s3 中文件都是固定大小）</li><li>没有事物引擎， 也没有buffer pool</li></ol><p>分析：<br />这种share disk和cache 的设计，让整个virtual warehouse 处在一个无状态的状态下，因此，调度器可以随时将一些ec2 切走。 另外， 因为定位的是olap系统，不是oltp系统， 对 请求的失败或时延没有那么高的要求。 </p><p>当数据写入时， 可以通道直接打通到virtual warehouse， 但作者没有介绍，如何解决故障问题， 比如当数据写入到s3 前，写入节点的ec2 发生故障， 怎么保障数据一致性。 因为数据是share disk架构， 并且系统没有做类似paxos 的3节点日志， 系统应该是通过提供一个事物来支持这种failover， 当写入s3 成功后，才返回事物成功。 <br /> </p><h1 id="优化器-执行引擎"><a href="#优化器-执行引擎" class="headerlink" title="优化器&amp; 执行引擎"></a>优化器&amp; 执行引擎</h1><ol><li>号称没有使用index （一种怀疑是全列存架构）</li><li>推迟执行， 减少optimizer的错误</li><li>持续收集query state， performance counter， detect node fail</li><li>执行引擎支持vectorize – simd – 也说明他们底层实现是c&#x2F;c++ 语言</li><li>做了大量的下推操作， </li><li>没有采用pruning 技术， 在oltp中，随机访问很场景， 因此使用b+ 树非常多， 但在s3中，并且大量使用压缩的情况下， pruning 很多时候没有什么效果，需要采用其他的技术， </li><li>min-max based pruning， 做区间判断是否可以跳过文件。 （比如join时， 在build table时，收集信息， 然后在probe时，可以跳过一个不匹配的文件）</li><li>small materialized aggregate</li><li>zone map</li><li>data skipping</li></ol><br /><h1 id="common-service"><a href="#common-service" class="headerlink" title="common service"></a>common service</h1><ol><li>所有的service 是无状态的， 随时可以升级，扩容</li><li>hard state 存储到kv store中， kv store 也是通过mapping layer来访问， 使用metadata version， schema evolution， 保障向前兼容。</li><li>采用微服务的这种架构， 让系统扩展性非常好，并且节省了成本。</li></ol><p> </p><h1 id="半结构化数据"><a href="#半结构化数据" class="headerlink" title="半结构化数据"></a>半结构化数据</h1><p>可以将数据从json， avro， xml， load 到variant<br />variant 自描述， 压缩binary 序列化， 可以快速kv 查询， 高些test， hash 笔记， schema 可以自我进化。 </p><p>udf 支持javascript， udf 支持variant， 以后支持存储过程。 </p><p>impala 和dremel 都使用完整的table schema ，从而支持半结构化的data， snowflake 用了一种新的自动类型探测和列式存储。 </p><p>当存半结构化数据是， 对table file 进行statics 分析， 自动类型探测， 决定哪种type ， 然后对某些列从原始文档中删除，然后用相同格式和压缩方式进行单独存储， 这些列通过物化视图来访问。 </p><p>分析：<br />这一段吹的神乎其神， 应该是解决了某几个场景，但个人觉得对半结构化支持， 应该牺牲了性能来做。 <br />像论文里面提到一种方法flatten， 旋转nested document到多行， 用sql lateral view来展示flatten的操作。 </p><h1 id="mvcc"><a href="#mvcc" class="headerlink" title="mvcc"></a>mvcc</h1><ol><li>一个历史文件默认保留90天</li><li>有一个time travel， 读取一个历史版本 （timestamp xxx before）</li><li>回收站</li><li>clone， 不做物理clone， 只是metadata clone，做snapshot非常方便。  两个table 可以独立修改。</li></ol><p>  </p>]]>
    </content>
    <id>https://ilongda.com/2020/docs/paper/snowflake/</id>
    <link href="https://ilongda.com/2020/docs/paper/snowflake/"/>
    <published>2020-01-05T11:42:57.000Z</published>
    <summary>论文解读 -- 《The Snowflake Elastic Data Warehouse》</summary>
    <title>Snowflake 架构讨论 《The Snowflake Elastic Data Warehouse》</title>
    <updated>2024-02-02T13:31:20.275Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="生活" scheme="https://ilongda.com/categories/%E7%94%9F%E6%B4%BB/"/>
    <category term="生活" scheme="https://ilongda.com/tags/%E7%94%9F%E6%B4%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"新年愿望","description":"2020新年随想与心愿清单：回顾上一年未完成的flag，立下写博客、看书、健身、陪家人运动等一年生活与成长目标，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":233,"datePublished":"2020-01-01T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.931Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2020/New-Year-Wish/"},"url":"https://ilongda.com/2020/New-Year-Wish/","inLanguage":"zh-CN","keywords":["生活"],"articleSection":["生活"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"生活","item":"https://ilongda.com/categories/生活/"},{"@type":"ListItem","position":3,"name":"新年愿望","item":"https://ilongda.com/2020/New-Year-Wish/"}]}</script><p>忽然之间， 发现已经到了2020年， 回首2019年立下的flag， 似乎一大半都还没有完成， 就来到了2020年。<br>2020 拥有2个20， 在中国传统习惯中， 好事成双，大家都喜欢偶数的东西， 同时20又是10的倍数，就更吉利。 看到2020年这么吉利的数字， 也祝愿自己新的一年， 顺顺利利， 开开心心。 </p><p>今天，打开朋友圈， 发现一堆朋友，都跑步或健身， 似乎在这新的一年中， 大家都更积极面对生活，期待拥有一个更棒的身体。 </p><p>最后立一个心愿清单吧：</p><ol><li>儿子身体棒棒， 个子长得高一点。</li><li>每周带儿子和老婆， 做一次家庭运动。</li><li>今年写完50篇博文</li><li>看完12本书 （4本技术， 4本修心， 4本育儿）</li><li>争取练出6块腹肌。 </li><li>每天12点前睡觉</li></ol>]]>
    </content>
    <id>https://ilongda.com/2020/New-Year-Wish/</id>
    <link href="https://ilongda.com/2020/New-Year-Wish/"/>
    <published>2020-01-01T11:42:57.000Z</published>
    <summary>2020新年随想与心愿清单：回顾上一年未完成的flag，立下写博客、看书、健身、陪家人运动等一年生活与成长目标，更多细节与示例见正文。</summary>
    <title>新年愿望</title>
    <updated>2026-06-09T08:46:25.931Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="Distributed Database" scheme="https://ilongda.com/tags/Distributed-Database/"/>
    <category term="Spanner" scheme="https://ilongda.com/tags/Spanner/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Spanner: Google’s Globally Distributed Database","description":"Google Spanner 全球分布式数据库论文阅读笔记：TrueTime、Paxos 与外部一致性事务","image":"https://ilongda.com/img/spanner/arch.png","wordCount":4154,"datePublished":"2019-11-05T11:42:57.000Z","dateModified":"2026-06-08T13:57:33.028Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/spanner/"},"url":"https://ilongda.com/2019/spanner/","inLanguage":"zh-CN","keywords":["Database","Distributed Database","Spanner"],"articleSection":["Database"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"Spanner: Google’s Globally Distributed Database","item":"https://ilongda.com/2019/spanner/"}]}</script><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><blockquote><p>Spanner is Google’s scalable, multi-version, globally-distributed, and synchronously-replicated database. It is the first system to distribute data at global scale and support externally-consistent distributed transactions. This paper describes how Spanner is structured, its feature set, the rationale underlying various design decisions, and a novel time API that exposes clock uncertainty. This API and its implementation are critical to supporting external consistency and a variety of powerful features: non-blocking reads in the past, lock-free snapshot transactions, and atomic schema changes across all of Spanner.</p></blockquote><p>Spanner是Google的可扩展，多版本，全球分布式和同步复制数据库。它是第一个在全球范围内分布数据并支持外部一致的分布式事务的系统。本文描述了Spanner的结构，其功能集，各种设计决策背后的原理，以及一个揭示时钟不确定性的新型时间API。这个API及其实现对于支持外部一致性和各种强大功能至关重要：在过去进行非阻塞读取，无锁快照交易，以及跨越所有Spanner的原子性模式更改。</p><img data-src="/img/spanner/arch.png"  alt="Spanner: Google’s Globally Distributed Database"><span id="more"></span><ol><li>是一个全球数据库, 数据分布到全球datacenter(能跨continents), 设计目标是支撑百万机器跨域数百个datacenter, 数据量支撑trillions of database row</li><li>通过paxos 来保证数据高可用</li><li>数据能自动做resharding, 当扩缩容或数据变化, failover时</li></ol><h1 id="技术概述"><a href="#技术概述" class="headerlink" title="技术概述"></a>技术概述</h1><ol><li>最早用户是F1, F1 使用5个副本, 一般情况下是一个地理region下3到5个datacenter, 这样能抗1~2个datacenter 灾难, 并且引用优先选择本地region</li><li>最开始的痛点来自bigtable:<ol start="3"><li>复杂, 并持续演进的schema</li><li>强一致性在跨区域环境(低时延环境)</li><li>有一些尝试用megastore(300多个应用, 如gmail, picasa, calandar, android market, AppEngine), 尽管写吞吐比较查, 当支持半关系模型数据和支撑强同步</li></ol></li><li>开始从bigtable 这样的kv store 进化而来, 支撑多版本的数据库<ol start="7"><li>数据采用半结构化table</li><li>数据具备多版本, 每个版本以commit timestamp 标签</li><li>应用可以使用老的timestamp 进行获取老的版本</li><li>老的版本数据会由gc 策略来决定是否保留</li><li>支持参加事物模型</li><li>提供sql 查询语言</li><li>percolator 的性能慢, 导致事务都在上层来处理, 从而提高性能</li></ol></li><li>技术特点<ol start="14"><li>数据的副本配置, 可以由应用动态调整, 并且以一定的细粒度</li><li>应用可以控制, <ol start="16"><li>数据放到哪个datacenter, </li><li>数据距离user 多远, </li><li>每个副本之间的距离</li><li>有多少个副本</li></ol></li><li>数据可以根据使用负载平衡透明并动态的从一个datacenter 挪到另外一个</li><li>事务2个能力(支持分布式)<ol start="22"><li>external consistent reads and writes <ol start="23"><li>序列化顺序, 如果t1 比t2 commit 更早, 则t1 的timestamp 比t2 的timestamp 更小. </li><li>核心是TrueTime API</li></ol></li><li>以一个timestamp 全局一致性读</li></ol></li><li>事务的能力保障了一致性backup, 一致性mapreduce 执行, 原子更新, 全球范围内, 甚至正在进行的事务中. </li><li></li></ol></li></ol><h2 id="True-Time"><a href="#True-Time" class="headerlink" title="True Time"></a>True Time</h2><ol><li>true time api 直接暴露clock 的不确定性</li><li>如果clock 的不确定性太大, spanner 会slow down and wait uncertain. </li><li>true time api 由cluster-managerment software 来提供并实现</li><li>cluster-managerment software 的实现保障不确定性足够小, 一般不小于10ms. 通过使用多个现代时钟reference(gps和原子钟)</li><li>不确定性的保守report 对正确性很有必要, 并保持不确定区间小从而保障了性能.</li></ol><h2 id="架构细节"><a href="#架构细节" class="headerlink" title="架构细节"></a>架构细节</h2><ol><li><p>数据搬迁, 副本, 数据locality的最小单元是direcotry</p></li><li><p>一个spanner的部署称为universe. (可以理解为一个universe 为一个spanner 集群)</p></li><li><p>spanner 集群由一组zone 来管理, 一个zone 是一批spanner的模拟组合. (非常类似ob)</p></li><li><p>可管理部署单元是zone, 可以添加一个zone(添加一个新的datacenter), 也可以减少一个zone(turn off 一些机器), zone 是物理isolation, 一个datacenter 可能含有一个或多个zone, 一个datacenter的不同组的机器可以组成不同的zone, 然后数据被分区到不同的zone里. </p><img data-src="/img/spanner/arch.png"  alt="Spanner: Google’s Globally Distributed Database"> </li><li><p>一个zone 有一个zonemaster和数以牵记的spannerserver. zonemaster 负责assign data 到spannerserver. </p></li><li><p>spannerserver 响应客户请求. </p></li><li><p>每个zone 有一批location proxy, 他们负责路由. </p></li><li><p>universe master 和placement driver 当前是单点. </p></li><li><p>universe master 展示所有zone的状态信息</p></li><li><p>placement driver 定期和spannerserver 通信, 确认哪些数据需要搬迁(满足负载均衡或升级副本限制), 搬迁的粒度是分钟级.</p></li></ol><h2 id="软件栈"><a href="#软件栈" class="headerlink" title="软件栈"></a>软件栈</h2><img data-src="/img/spanner/software-stack.png"  alt="Spanner: Google’s Globally Distributed Database"><ol><li>在底层, 每个spannerserver 负责100 到1000 个tablet 实例. </li><li>tablet 类似bigtable tablet 的抽象. 可以理解为一批类似如下<ol start="3"><li>(key:string, timestamp:int64) –&gt; string</li></ol></li><li>tablet 组织在类似b-tree的file 和wal 的日志中, </li><li>所有数据存在colossus这个分布式文件系统(gfs的继任者)</li><li>每个tablet 一个paxos group(最早一个tablet 多个paxos group), 每个</li><li>每个paxos state machine 把它的元数据和日志存到它的tablet中</li><li>paxos 支持long-lived leader, leader 是基于时间的leader lease, 默认10s</li><li>每个log 实际上要写2次, 一次在tablet 的log, 另外一次在paxos log 中. (这是当前的权宜之计, 后续会彻底修正)</li><li>paxos 是基于pipeline的, 因此会提升吞吐量. pipeline 基于Lamport “multi-decree parliament”, pipeline 摊平了leader 选举的代价以及在不同的裁定中允许并行投票. 尽管裁定(decree)允许乱序, 当实现中还是让裁定有序. </li><li>paxos 实现了一致性的复制性的mapping(前述数据的mapping 方式), 每个副本的key-value mapping 存到它对应的tablet中. </li><li>leader 发起paxos 协议写, 如果副本已经更新了, 可以直接读副本的底层tablet的state. </li><li>每个paxos group 的leader 来实现lock table 来实现并发控制(leader 的long live 是保障lock table 高效的关键手段).  lock table 含2阶段locking, <ol start="14"><li>map key的范围到lock 状态</li><li>当冲突时, 在优化并发控制下, 长事务慢慢执行</li><li>要求同步的操作如事务读, 需要申请lock table 的lock, 其他操作bypass lock table. </li><li>lock table的状态是volatile</li></ol></li><li>每个paxos group 的leader实现事务manager来支持分布式事务. <ol start="11"><li>事务manager 用于实现 参与leader(participant leader), 其他为参与slave</li><li>如果一个事务只涉及一个paxos group, 它会bypass 事务manager, lock table和paxos 可以提供事务能力</li><li>如果一个事务涉及多个paxos group, paxos group的leader 会执行2阶段提交. 其中之一的leader 会被选举为协调者, 那个paxos group的其他成员为协调slave. </li><li>每一个事务manager的状态也是用paxos group 存储下来的.</li></ol></li><li>数据搬迁, 副本, 数据locality的最小单元是direcotry (更好的称呼应该为bucket)<ol start="12"><li>directory 是一段连续的key, 贡献一个公共的prefix</li><li>应用可以控制这批数据的locality, 可以通过小心选择key</li><li>dirctory下的数据拥有相同副本配置. </li><li>directory 可以在不同的paxos group 之间迁移</li><li>常常因为让数据更靠近访问者而进行搬迁</li><li>一个50MB 的direcoty 几秒就可以搬迁万</li><li>一个paxos group 包含多个directory</li><li>paxos group 并不是row space 一段字段序连续的partition</li><li>实际上一个tablet 是包含多个row space partition的container, 方便多个directory 被关联访问. </li><li>如果directory 如果增长非常大, 会把directory 分成fragments, 搬迁粒度会以fragment拉完成</li></ol></li><li>movedir 是一个后台任务, 不仅可以在paxos group 中进行搬迁direcotry, 也可以将一个paxos group 中的directory 添加或删除到一个新的paxos group.<ol start="21"><li>movedir 并不是一个事务, 为了不阻塞后续的读写. </li><li>它是后台进行数据搬迁, 如果大部分数据已经搬迁完, 剩下的数据通过事务来完成搬迁和元数据变更.</li></ol></li><li>placement 可以应用来确定 <ol start="24"><li>placement language 单独负责副本配置管理. </li><li>管理控制2个维度<ol start="26"><li>副本类型和数量</li><li>副本的地理placement</li></ol></li><li>应用可以自由控制, 比如a的数据在欧洲有3个副本, b的数据在北美有5个副本.</li></ol></li></ol><h2 id="数据模式"><a href="#数据模式" class="headerlink" title="数据模式"></a>数据模式</h2><ol><li>应用数据模型基于key-value的directory-bucket的分层模型. 一个应用在一个universe中创建一个或多个database. 每个database 可以包含无数个schemaed table. table 类似关系数据库的table, 有row, column和带版本的值. </li><li>数据模型并不是纯粹的关系模型, row 必须有名字. 每个表必须有一个或多个排好序的主键.</li><li>只要为某些key 定义了value(即使是null), 这些行都会存在. 选择key的区间, 可以决定数据放在哪些directory, 从而也就决定locality.<img data-src="/img/spanner/datamode.png"  alt="Spanner: Google’s Globally Distributed Database"></li><li>example 显示:<ol start="5"><li>通过interleave in 声明在schema 来定义table的hierachies. </li><li>一个hierachies的最顶部为directory table. </li><li>在directory table的每一行都有key, 它的后续关联table 和这个key 相关的所有行页放在这里, 按照字典序进行排列, 形成一个directory. </li><li>on delete cascade 意味着 删除directory table的这一行, 会删除所有相关的child row. </li><li>这种方式, 保留了多个table 的相关性, 并具备相同的locality. 也会有更好的性能.</li></ol></li></ol><h2 id="TrueTime"><a href="#TrueTime" class="headerlink" title="TrueTime"></a>TrueTime</h2>   <img data-src="/img/spanner/truetime_api.png"  alt="Spanner: Google’s Globally Distributed Database">1. 上图展示了truetime的api, truetime 用TTinterval来表示时间, 是一个不确定的时间间隔. 2. TTinterval 的端点是TTstamp. 3. TT.now 表示调用的绝对时间. time epoch 类似 unix的time(支持闰秒).4. 定义瞬间error bound为ε, 是间隔的一半宽度5. 平均error bound 为ε(带上划线)6. 一个event的绝对时间用Tabs(e), tt = TT.now(), tt.earlist <= Tabs(Enow) <= tt.latest. Enow 是调用event7. 使用gps 和原子钟作为truetime的参考, 因为他们有不同的失败模型. GPS参考源的脆弱性包括天线和接收器故障，本地无线电干扰，相关故障（例如，设计错误，如不正确的闰秒处理和欺骗），以及GPS系统停机。原子钟可能以与GPS和彼此无关的方式失败，并且由于频率错误，长时间可能会产生显著的漂移。8. 每个datacenter 有个time master, 每台机器上有个timeslave 后台进程. 大多数主服务器都有专用天线的GPS接收器;这些主服务器都被物理分隔开以减少天线故障、无线电干扰和欺骗的影响。剩余的主服务器（我们称之为Armageddon master）都配备了原子钟。一个原子钟并不那么昂贵：Armageddon master的成本与GPS主服务器的成本大致相当。所有主服务器的时间参考都定期互相对比。每个主服务器也会交叉检查其参考时间推进的速率与自己的本地时钟，并在存在大的偏差时剔除自己。在同步之间，Armageddon master会宣布一个慢慢增加的时间不确定性，这是从保守应用的最坏情况时钟漂移中得出的。GPS主服务器宣布的不确定性通常接近于零。9. 每个守护进程都会查询各种主服务器，以减少对任何一个主服务器错误的敏感性。其中一些是从附近的数据中心选择的GPS主服务器；其余的是来自较远数据中心的GPS主服务器，以及一些 Armageddon masters。守护进程应用Marzullo的算法的一个变体来检测和拒绝说谎者，并将本地机器的时钟与非说谎者同步。为了防止本地时钟出错，那些表现出频率偏差大于由组件规格和操作环境派生出的最坏情况界限的机器将被逐出。正确性取决于确保最坏情况界限得到执行。10. 在同步之间，守护进程会产生一个缓慢增加的时间不确定性。ε 是由保守地应用最坏情况的本地时钟漂移派生出来的。ε 还取决于timemaster的不确定性和与timemaster的通信延迟。在我们的生产环境中，ε 通常是时间的锯齿函数，每个轮询间隔从大约 1 到 7 毫秒变化。因此，ε 大部分时间都是 4 毫秒。守护进程的轮询间隔目前是 30 秒，当前应用的漂移率设置为每秒 200 微秒，这两者共同构成了从 0 到 6 毫秒的锯齿边界。剩下的 1 毫秒来自与时间主的通信延迟。在故障存在的情况下可能会偏离这个锯齿。例如，偶然的时间主不可用可能会导致数据中心范围内的 ε 增加。同样，过载的机器和网络连接可能会导致偶然的本地化 ε 峰值。由于 Spanner 可以等待不确定性，所以 ε 的变化不会影响正确性，但是如果 ε 增加太多，性能可能会降低。<h2 id="并行控制"><a href="#并行控制" class="headerlink" title="并行控制"></a>并行控制</h2><ol><li>如何实现外部一致性事务, lock free 快照事务和过去的非阻塞读<img data-src="/img/spanner/tx.png"  alt="Spanner: Google’s Globally Distributed Database"></li><li>单独的写转为读写事务, 无快照单独读转为快照读, 都是内部重试, 无需客户端做重试. </li><li>快照事务可以享受快照隔离性的优点. <ol start="4"><li>快照事务必须声明无写操作. 并不是一个简单的不带写操作的读写事务. </li><li>快照读用一个获取的系统时间来执行, 并没有locking, 所以后续的写都是无锁的. </li><li>快照事务读中, 当任何一个副本都是update-to-date时, 都可以提供读服务</li></ol></li><li>快照读事务中, 客户端可以选择一个timestamp 或者提供期望timestamp 区间的上限,从而spanner来选择一个时间. </li><li>在快照事务或快照读事务中, 一旦选择了timestamp, 就commit, 除非那个timestamp的数据被gc. 客户端可以避免不停retry.<ol start="9"><li>当一个server失败时, 客户端会用timestamp和当前读位置, 从别的server 上进行读.</li></ol></li><li>paxos leader lease<ol start="10"><li>leader lease 大概10s. 当候选leader 获取多数派后, 它获得leader lease. </li><li>当leader lease 快超时, 当前leader 发起投票, 副本会在一个成功的写响应中携带lease 投票. </li><li>每个paxos group 的leader 都是不相交的. </li><li>leader 可以通过lease 投票, 退位给slave.</li></ol></li><li>读写事务都是严格的2阶段锁. <ol start="15"><li>当获得所有的锁后并且还没有释放任何锁前, 他们会被分配timestamp, 这个timestamp 就是代表事务提交的paxos write的timestamp. </li><li>单调性<ol start="17"><li>paxos write 保持单调递增, 即使交叉leader. 通过利用leader的不交叉性, 强化了跨leader时的单调递增性. </li><li>一个leader 只能assign 它任期内的timestamp</li><li>任何时候, assign 一个timestamp, Smax 进阶到s 来保障不相交.</li></ol></li></ol></li><li>强化外部一致性<ol start="21"><li>T2 事务的起始时间在T1 事务的commit 时间之后, T2的commit 时间必须大于 T1的commit时间. </li><li>一个事务Ti的star 事件和commit 事件用E-i-star, E-i-commit(数学表达式, 不好用markdown来写, 转义一下), Ti 的commit timestamp 用Si 表示. </li><li>写事务Ti在协调者leader的cmmit 到达时间为E-i-server.</li><li>Ti 事务的commit timestamp Si 不小于 TT.now().latest. 参与的leader 无关紧要. </li><li>commit wait. 协调者保证客户端无法看到Ti commit的数据直到TT.after(si) 为true. commit wait 保障si 小于Ti的绝对commit 时间.<img data-src="/img/spanner/commit-ts.png"  alt="Spanner: Google’s Globally Distributed Database"></li><li>每个副本的同步时间称为safe time, Tsafe &#x3D; min(T-safe-paxos, T-safe-TM), <ol start="27"><li>每个paxos state 有一个safe time T-safe-paxos, 每个事务manager 有个T-safe-TM</li><li>T-safe-paxos 简单, 是最高aplly的paxos write 时间. paxos的write 是单调递增并排序的. </li><li>T-safe-TM 比较复杂<ol start="30"><li>当没有prepared 事务时, 是无穷大. 也就是事务处在2阶段提交期间. </li><li>2阶段提交中, 每个参与者知道prepared 事务的timestamp的下限.</li><li>用S-i&#x2F;g-prepare表示prepare record的prepare timestamp, 并保证事务的提交时间si &gt;&#x3D; S-i&#x2F;g-prepare. </li><li>T-safe-TM &#x3D; min(all事务)(S-i&#x2F;g-prepare) -1 时间.</li></ol></li></ol></li></ol></li><li>快照事务执行2阶段提交, <ol start="35"><li>分配一个timestamp S-read, 然后用S-read来读</li><li>最简单的方式: 当一个事务开启时, S-read &#x3D; TT.now().latest. 但有的时候会遇到一些问题. </li><li>当副本的T-safe 还不足够时, 会block 在S-read上.</li><li>未来减少blocking, spanner 选择了保持外部一致性的最大时间.</li></ol></li><li>读写事务<ol start="40"><li>读写事务中的读使用wound-wait 方式来避免读写锁. </li><li>请求发到leader, 申请读锁和读取最新的数据</li><li>如果客户端事务重新打开, 使用keepalive 方式, 避免参与leader超时. </li><li>客户端选择协调者paxos group, 然后发送commit 信息带上协调者id和buffered 的写操作 到所有的参与leader. </li><li>当非协调者的leader 第一次申请写锁, 它会选择一个prepare timestamp, 这个timestamp 会比之前事务的timestamp 都要大, 并通过paxos 来写preprare record 的日志. 并通知协调者leader它的prepare timestamp. </li><li>当协调者第一次申请写锁时, 它会跳过prepare 阶段. 在收到所有参与者leader后, 它会选择一个timestamp 作为整个事务的timestamp(应该时commit timestamp). <ol start="46"><li>commit timestamp s 会比所有prepare timestamp 都要大或者和最大相等. 实际上会比协调者收到commit message 时的TT.now().latest 还要大.也会比之前所有的事务的所有timestamp 都要大. </li><li>然后协调者通过paxo log这个commit record.</li></ol></li><li>只能paxo leader 才能申请锁. 仅仅当事务prepare 阶段才会log 锁的状态<ol start="49"><li>如果在prepare之前丢失锁(死锁, paxos leader 变更, timeout等因素), 参与者会放弃. </li><li>spanner 保证自由当所有的锁都hold 时, 才会去log 一个prepare或commit record. </li><li>如果leader 发送变更, 新leader 会在接受新事务之前, 恢复所有已经prepare但还没有uncommit事务的锁状态,</li></ol></li><li>在所有的协调者的副本应用commit record之前, 协调者的leader会wait 直到TT.after(s), 从而可以遵守commit-wait 规则. 因为要保证TT.now().latest确实已经成为过去. 通常这个等待值是2倍的平均bound ε. 这个等待可以和paxos 通信并行. </li><li>在commit-wait 之后, 每个协调者会把commit timestamp 发给客户端和所有的参与者leader. </li><li>每个参与者log 这个事务的输出通过paxos 协议. 然后应用这个timestamp, 最后释放锁.</li></ol></li><li>快照事务. <ol start="56"><li>在分配timestamp之前, 需要这个read 所有涉及的paxos group leader 进行协商.</li><li>然后spanner require 一个scope expression, 这个expression 会总结这个读事务的key, 然后触发独立查询.</li><li>如果这个scope 的values 只涉及一个paxos group, 客户端就直接发送这个快照事务到这个paxos leader. <ol start="59"><li>这个leader 会分配一个S-read, 并执行这个read. </li><li>对于单节点读, spanner 会比TT.now().latest做的更好.</li><li>定义LastTS() 为最后一个已经提交的写事务的timestamp.</li><li>如果没有prepared 事务, 直接S-read &#x3D; LastTS() 来满足外部一致性.</li></ol></li><li>如果是多paxos group 来<ol start="53"><li>复杂的做法: 所有的参与者的leader 需要一起协商S-read 基于每个LastTS()</li><li>简单的做法(当前做法):客户端不用一轮协商, 直接选择一个安全的S-read&#x3D;TT.now().latest.</li></ol></li><li>Schema-Change 事务:<ol start="56"><li>truetime 支持原子schema 变更. </li><li>使用标准的事务是不可行的, 因为参与者数以百万计</li><li>bigtable 支持在一个datacent 进行原子schema change, 但这个操作会block 所有的操作</li><li>当前schema change事务是一个标准事务的无锁变种. </li><li>第一步, 分配一个未来时间的timestamp, 在prepare 阶段. 减少对数千个server 的当前业务产生冲击. </li><li>明显依赖这个schema的读写请求,把注册 schema-change的timestamp t 同步起来</li></ol></li></ol></li></ol>]]>
    </content>
    <id>https://ilongda.com/2019/spanner/</id>
    <link href="https://ilongda.com/2019/spanner/"/>
    <published>2019-11-05T11:42:57.000Z</published>
    <summary>Google Spanner 全球分布式数据库论文阅读笔记：TrueTime、Paxos 与外部一致性事务</summary>
    <title>Spanner: Google’s Globally Distributed Database</title>
    <updated>2026-06-08T13:57:33.028Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"performance schema 源码走读","description":"MySQL 技术解读 -- performance schema 源码走读","image":"https://ilongda.com/img/my.jpg","wordCount":3206,"datePublished":"2019-08-09T11:42:57.000Z","dateModified":"2024-02-02T13:24:47.466Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/performance_schema/general/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/performance_schema/general/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"performance schema 源码走读","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/performance_schema/general/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>转载：<a href="/knowledge/mysql/source_code_reading/storage/performance_schema">performance_schema</a></p><p>performance schema 是一个存储引擎, 可以提供对mysql 所有指标的监控， 是一套非常详细而复杂的监控系统， 不同的指标，使用了不同的接口， 另外有几个特点：</p><ol><li>它是运行时态， 因此是全内存存储， 重启后会丢失之前的数据</li><li>为了减少对运行时态的影响， 绝大部分资源都是提前申请好， 在performance_schema 初始化的时候，已经申请好了。涉及到2块内存， 一个是class 配置信息， 一个pfs state </li><li>不能增加sql 种类和语法</li></ol><p>本文主要分 3块：</p><ol><li>初始化</li><li>基本数据结构</li><li>使用过程</li></ol><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><p>分为几个步骤</p><ol><li>准备pfs 内部系统的内存监控的类</li><li>准备好pfs 配置的内存, pfs 配置主要用于设置pfs_xx_class</li><li>初始化pfs – 初始化的核心操作， 最主要的核心操作是准备好PFS需要的资源，尤其是内存申请， class， pfs 监控项的container， 以mutex 为例： 申请param-&gt;m_mutex_class_sizing 个PFS_mutex_class， 存储到PFS_mutex_class的mutex_class_array中， 另外会申请监控項的container 如global_mutex_container</li><li>设置好所有的service, </li><li>把所有的pfs 的key 注册到pfs 中， 方便后续使用</li></ol><span id="more"></span><h3 id="pre-initialize-performance-schema"><a href="#pre-initialize-performance-schema" class="headerlink" title="pre_initialize_performance_schema"></a>pre_initialize_performance_schema</h3><p>第一步， 初始化PFS_builtin_memory_class的一些类， 和一些全局状态跟踪</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">void pre_initialize_performance_schema() &#123;</span><br><span class="line">  pfs_initialized = false;</span><br><span class="line"></span><br><span class="line">  init_all_builtin_memory_class();</span><br><span class="line">  // 初始化类似 builtin_memory_mutex/builtin_memory_rwlock/builtin_memory_mdl 等等</span><br><span class="line">  // 这些变量可以跟踪每种指标对应的内存消耗</span><br><span class="line">  // builtin_memory_mutex 类型为 PFS_builtin_memory_class</span><br><span class="line"></span><br><span class="line">  PFS_table_stat::g_reset_template.reset();</span><br><span class="line">  // 对PFS_table_stat 类的静态变量g_reset_template 进行重设</span><br><span class="line">  // PFS_table_stat 主要成员是</span><br><span class="line">  //    PFS_table_io_stat m_index_stat[MAX_INDEXES + 1], 跟进这个index 的fetch/insert/update/delete</span><br><span class="line">  //    PFS_table_lock_stat m_lock_stat; table 有9种锁, 每种锁的状态</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  global_idle_stat.reset();  // idle 的状态跟踪 </span><br><span class="line">  global_table_io_stat.reset();  // table io 的状态跟踪, </span><br><span class="line">  global_table_lock_stat.reset();  // table 锁的状态跟踪</span><br><span class="line">  g_histogram_pico_timers.init();   // PFS_histogram_timers 的状态跟踪</span><br><span class="line">  global_statements_histogram.reset(); //PFS_histogram </span><br><span class="line"></span><br><span class="line">  /*</span><br><span class="line">    There is no automatic cleanup. Please either use:</span><br><span class="line">    - my_thread_end()</span><br><span class="line">    - or PSI_server-&gt;delete_current_thread()</span><br><span class="line">    in the instrumented code, to explicitly cleanup the instrumentation.</span><br><span class="line">  */</span><br><span class="line">  THR_PFS = nullptr;           // PFS_thread</span><br><span class="line">  for (int i = 0; i &lt; THR_PFS_NUM_KEYS; ++i) &#123;</span><br><span class="line">    THR_PFS_contexts[i] = nullptr;  //PFS_table_context</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="init-pfs-instrument-array"><a href="#init-pfs-instrument-array" class="headerlink" title="init_pfs_instrument_array"></a>init_pfs_instrument_array</h3><p>   申请内存存放， PFS_instr_config, 每個pfs 的监控项的开关放在这里， 后面每个pfs 监控项在register class时， 会从这个配置项中获取是否打开， 是否要进行timer</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line">  Initialize the dynamic array used to hold PFS_INSTRUMENT configuration</span><br><span class="line">  options.</span><br><span class="line">*/****</span><br><span class="line">void init_pfs_instrument_array() &#123;</span><br><span class="line">  pfs_instr_config_array = new Pfs_instr_config_array(PSI_NOT_INSTRUMENTED);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">typedef Prealloced_array&lt;PFS_instr_config *, 10&gt; Pfs_instr_config_array;</span><br></pre></td></tr></table></figure><h3 id="initialize-performance-schema"><a href="#initialize-performance-schema" class="headerlink" title="initialize_performance_schema"></a>initialize_performance_schema</h3><p>初始化 performance_schema storage</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">pfs_rc = initialize_performance_schema(</span><br><span class="line">          &amp;pfs_param, &amp;psi_thread_hook, &amp;psi_mutex_hook, &amp;psi_rwlock_hook,</span><br><span class="line">          &amp;psi_cond_hook, &amp;psi_file_hook, &amp;psi_socket_hook, &amp;psi_table_hook,</span><br><span class="line">          &amp;psi_mdl_hook, &amp;psi_idle_hook, &amp;psi_stage_hook, &amp;psi_statement_hook,</span><br><span class="line">          &amp;psi_transaction_hook, &amp;psi_memory_hook, &amp;psi_error_hook,</span><br><span class="line">          &amp;psi_parallel_query_hook, &amp;psi_parallel_operator_hook,</span><br><span class="line">          &amp;psi_data_lock_hook, &amp;psi_system_hook);</span><br><span class="line">      if ((pfs_rc != 0) &amp;&amp; pfs_param.m_enabled) &#123;</span><br><span class="line">        pfs_param.m_enabled = false;</span><br><span class="line">        LogErr(WARNING_LEVEL, ER_PERFSCHEMA_INIT_FAILED);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">initialize_performance_schema （） &#123;</span><br><span class="line">  pfs_automated_sizing(param);  //把PFS_sizing_data large_data 设置到param 中， 主要是类似p-&gt;m_events_waits_history_long_sizing</span><br><span class="line">  pfs_minimal_setting(param);  如果设置了performance_schema_minimal 为true， 则很多设置全部关掉</span><br><span class="line">  init_timers();  //初始化timer 一些偏硬件/操作系统底层函数， 方便获取一些时间</span><br><span class="line">  init_event_name_sizing(param); //设置 mutex_class_start/rwlock_class_start， </span><br><span class="line">  //在register psi（register_mutex_class）时， 得到PSI_mutex_info-&gt;m_event_name_index=mutex_class_start + index</span><br><span class="line"></span><br><span class="line">  register_global_classes(); // 注冊global 的class in pre_initialize_performance_schema</span><br><span class="line"></span><br><span class="line">  minimal_global_classes(param); // 当注册performance_schema_minimal 为true， 修改global class的一些enable和m_timed</span><br><span class="line"></span><br><span class="line">  //</span><br><span class="line">  init_sync_class  </span><br><span class="line">  // 以mutex 为例： 申请param-&gt;m_mutex_class_sizing 个PFS_mutex_class， </span><br><span class="line">  //存储到mutex_class_array 后， 后面register 会进行设置， 在init 会查找， 申请的内存变化会更新到builtin_memory_mutex_class</span><br><span class="line">  init_thread_class</span><br><span class="line">  init_table_share  // 初始化global_table_share_container， 但没有真正申请内存  </span><br><span class="line">  //typedef PFS_buffer_scalable_container&lt;PFS_table_share, 4 * 1024, 4 * 1024&gt; PFS_table_share_container;</span><br><span class="line">  init_table_share_lock_stat</span><br><span class="line">  // 很多数据结构使用无锁hash 来存储</span><br><span class="line"></span><br><span class="line">  // 设置一大堆consumer的flag</span><br><span class="line">  flag_events_stages_current =</span><br><span class="line">        param-&gt;m_consumer_events_stages_current_enabled;</span><br><span class="line"></span><br><span class="line">  init_pfs_plugin_table</span><br><span class="line">  // PFS_dynamic_table_shares::init_mutex</span><br><span class="line">  //</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="设置PFS-service"><a href="#设置PFS-service" class="headerlink" title="设置PFS service"></a>设置PFS service</h3><p>设置各种service, 类似这样</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">。。。</span><br><span class="line">if (psi_memory_hook != NULL) &#123;</span><br><span class="line">  service = psi_memory_hook-&gt;get_interface(PSI_CURRENT_MEMORY_VERSION);</span><br><span class="line">  if (service != NULL) &#123;</span><br><span class="line">    set_psi_memory_service(service);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">。。。</span><br></pre></td></tr></table></figure><p>这个地方就是把这个地方设置一下<br>typedef struct PSI_mutex_service_v1 PSI_mutex_service_t;<br>PSI_mutex_service_t     psi_mutex_service  &#x3D;   pfs_mutex_service_v1   &#x2F;   psi_mutex_noop<br>psi_mutex_service 定义在psi_noop.cc 文件中， pfs_mutex_service_v1 定义在storage&#x2F;perfschema&#x2F;pfs.cc中</p><p>如果是pfs 插件编译， 就会使用这个， 但目前是直接编译进内核， 因此不需要插件化加载<br>mysql_service_psi_mutex_v1_t               mysql_service_psi_mutex_v1  &#x3D; imp_performance_schema_psi_mutex_v1<br>mysql_service_psi_mutex_v1_t imp_performance_schema_psi_mutex_v1 定义在storage&#x2F;perfschema&#x2F;pfs.cc中</p><h3 id="初始化所有的key"><a href="#初始化所有的key" class="headerlink" title="初始化所有的key"></a>初始化所有的key</h3><p>初始化所有的key, 提前把一部分 register mutext&#x2F;memory psi 等结构 注册进去</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">    Now that we have parsed the command line arguments, and have initialized</span><br><span class="line">    the performance schema itself, the next step is to register all the</span><br><span class="line">    server instruments.</span><br><span class="line">  */</span><br><span class="line">static void init_server_psi_keys(void) &#123;.</span><br><span class="line"></span><br><span class="line">  ...</span><br><span class="line">  count = static_cast&lt;int&gt;(array_elements(all_server_mutexes));</span><br><span class="line">  mysql_mutex_register(category, all_server_mutexes, count);</span><br><span class="line"></span><br><span class="line">  count = static_cast&lt;int&gt;(array_elements(all_server_rwlocks));</span><br><span class="line">  mysql_rwlock_register(category, all_server_rwlocks, count);</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="基本结构"><a href="#基本结构" class="headerlink" title="基本结构"></a>基本结构</h2><p>公共数据结构, 后续会使用到, 先列在这里<br><span class="exturl" data-url="aHR0cHM6Ly9kZXYubXlzcWwuY29tL2RvYy9kZXYvbXlzcWwtc2VydmVyLzguMC4yMC9zdHJ1Y3RQRlNfX2luc3RyLmh0bWw=">https://dev.mysql.com/doc/dev/mysql-server/8.0.20/structPFS__instr.html<i class="fa fa-external-link-alt"></i></span></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">struct PFS_instr &#123;</span><br><span class="line">  /** Internal lock. */</span><br><span class="line">  pfs_lock m_lock;</span><br><span class="line">  /** Enabled flag. */</span><br><span class="line">  bool m_enabled;</span><br><span class="line">  /** Timed flag. */</span><br><span class="line">  bool m_timed;</span><br><span class="line">  /** Container page. */</span><br><span class="line">  PFS_opaque_container_page *m_page; // 参考PFS_partitioned_buffer_scalable_container</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">//存儲在結構pfs_instr_config_array 中</span><br><span class="line">struct PFS_instr_config &#123;  </span><br><span class="line">  /* Instrument name. */</span><br><span class="line">  char *m_name;</span><br><span class="line">  /* Name length. */</span><br><span class="line">  uint m_name_length;</span><br><span class="line">  /** Enabled flag. */</span><br><span class="line">  bool m_enabled;</span><br><span class="line">  /** Timed flag. */</span><br><span class="line">  bool m_timed;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>有這麼多種類</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line">enum PFS_class_type &#123;</span><br><span class="line">  PFS_CLASS_NONE = 0,</span><br><span class="line">  PFS_CLASS_MUTEX = 1,</span><br><span class="line">  PFS_CLASS_RWLOCK = 2,</span><br><span class="line">  PFS_CLASS_COND = 3,</span><br><span class="line">  PFS_CLASS_FILE = 4,</span><br><span class="line">  PFS_CLASS_TABLE = 5,</span><br><span class="line">  PFS_CLASS_STAGE = 6,</span><br><span class="line">  PFS_CLASS_STATEMENT = 7,</span><br><span class="line">  PFS_CLASS_TRANSACTION = 8,</span><br><span class="line">  PFS_CLASS_SOCKET = 9,</span><br><span class="line">  PFS_CLASS_TABLE_IO = 10,</span><br><span class="line">  PFS_CLASS_TABLE_LOCK = 11,</span><br><span class="line">  PFS_CLASS_IDLE = 12,</span><br><span class="line">  PFS_CLASS_MEMORY = 13,</span><br><span class="line">  PFS_CLASS_METADATA = 14,</span><br><span class="line">  PFS_CLASS_ERROR = 15,</span><br><span class="line">  PFS_CLASS_THREAD = 16,</span><br><span class="line">  /* Reserve 17-29 for official mysql */</span><br><span class="line">  PFS_CLASS_PARALLEL_QUERY = 30,</span><br><span class="line"></span><br><span class="line">  PFS_CLASS_LAST = PFS_CLASS_PARALLEL_QUERY,</span><br><span class="line">  PFS_CLASS_MAX = PFS_CLASS_LAST + 1</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">// 做一些状态统计的</span><br><span class="line">struct PFS_single_stat &#123;</span><br><span class="line">  /** Count of values. */</span><br><span class="line">  ulonglong m_count;</span><br><span class="line">  /** Sum of values. */</span><br><span class="line">  ulonglong m_sum;</span><br><span class="line">  /** Minimum value. */</span><br><span class="line">  ulonglong m_min;</span><br><span class="line">  /** Maximum value. */</span><br><span class="line">  ulonglong m_max;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/** Instrumentation metadata for a mutex. */</span><br><span class="line">struct PFS_ALIGNED PFS_mutex_class : public PFS_instr_class &#123;</span><br><span class="line">  /** Mutex usage statistics. */</span><br><span class="line">  PFS_mutex_stat m_mutex_stat;</span><br><span class="line">  /** Singleton instance. */</span><br><span class="line">  PFS_mutex *m_singleton;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/** Information for all instrumentation. */</span><br><span class="line">struct PFS_instr_class &#123;</span><br><span class="line">  /** Class type */</span><br><span class="line">  PFS_class_type m_type;</span><br><span class="line">  /** True if this instrument is enabled. */</span><br><span class="line">  bool m_enabled;</span><br><span class="line">  /** True if this instrument is timed. */</span><br><span class="line">  bool m_timed;</span><br><span class="line">  /** Instrument flags. */</span><br><span class="line">  uint m_flags;</span><br><span class="line">  /** Volatility index. */</span><br><span class="line">  int m_volatility;</span><br><span class="line">  /**</span><br><span class="line">    Instrument name index.</span><br><span class="line">    Self index in:</span><br><span class="line">    - EVENTS_WAITS_SUMMARY_*_BY_EVENT_NAME for waits</span><br><span class="line">    - EVENTS_STAGES_SUMMARY_*_BY_EVENT_NAME for stages</span><br><span class="line">    - EVENTS_STATEMENTS_SUMMARY_*_BY_EVENT_NAME for statements</span><br><span class="line">    - EVENTS_TRANSACTIONS_SUMMARY_*_BY_EVENT_NAME for transactions</span><br><span class="line">  */</span><br><span class="line">  uint m_event_name_index;</span><br><span class="line">  /** Instrument name. */</span><br><span class="line">  char m_name[PFS_MAX_INFO_NAME_LENGTH];</span><br><span class="line">  /** Length in bytes of @c m_name. */</span><br><span class="line">  uint m_name_length;</span><br><span class="line">  /** Documentation. */</span><br><span class="line">  char *m_documentation;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>以mutex 为例，<br>在配置文件中，打开performance_schema， 对部分的配置进行单独设置</p><p>对于performance schema 关闭的情况下, psi_mutex_service 对应的就是psi_mutex_noop<br>对于打开performance schema情况下, 对应的是 pfs_mutex_service_v1<br>每个配置项是 performance_schema_instrument &#x3D; ‘ wait&#x2F;synch&#x2F;mutex  &#x3D;  ON  ‘ 是一行， 可以多项，表示enable 多个pfs 监控项, 如果不是精确匹配的话， 就建议增加正则匹配% 来代表所有</p><p>m_consumer_global_instrumentation_enabled  可以控制如锁之类（mutex&#x2F;lock&#x2F;rwlock&#x2F;cond）， 文件（file）， table 之类， 控制范围比较广， 默认为true<br>performance_schema_consumer_thread_instrumentation， thread 相关的都由他控制， 默认为true， </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">performance_schema = ON</span><br><span class="line">m_consumer_global_instrumentation_enabled = 1  </span><br><span class="line">// 对于一些配置，可以单独进行设置</span><br><span class="line">performance_schema_max_mutex_classes=1024</span><br><span class="line">performance_schema_instrument = &#x27; wait/synch/mutex/%  =  ON  &#x27;</span><br><span class="line">performance_schema_instrument = &#x27; wait/io/file/%  =  ON  &#x27;</span><br></pre></td></tr></table></figure><h3 id="使用接口"><a href="#使用接口" class="headerlink" title="使用接口"></a>使用接口</h3><p>对mutex 的使用完全和使用pthread mutex 行为基本一致， 可以参考components&#x2F;pfs_example&#x2F;pfs_example.cc</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">mysql_mutex_register  --&gt; psi_mutex_service-&gt;register_mutex  --&gt; pfs_register_mutex_v1</span><br><span class="line">mysql_mutex_init   --&gt; psi_mutex_service-&gt;init_mutex   /my_mutex_init</span><br><span class="line">mysql_mutex_destroy</span><br><span class="line">mysql_mutex_lock</span><br><span class="line">mysql_mutex_trylock</span><br><span class="line">mysql_mutex_unlock</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">static PSI_mutex_key key_mutex_x = 0;</span><br><span class="line">static PSI_mutex_key key_mutex_y = 0;</span><br><span class="line"></span><br><span class="line">static PSI_mutex_info all_example_mutex[] = &#123;</span><br><span class="line">    &#123;&amp;key_mutex_x, &quot;X&quot;, PSI_FLAG_SINGLETON, PSI_VOLATILITY_PERMANENT,</span><br><span class="line">     &quot;Example doc, permanent mutex, singleton.&quot;&#125;,</span><br><span class="line">    &#123;&amp;key_mutex_y, &quot;Y&quot;, 0, PSI_VOLATILITY_QUERY,</span><br><span class="line">     &quot;Example doc, very volatile mutexes.&quot;&#125;&#125;;</span><br><span class="line"></span><br><span class="line">static mysql_mutex_t my_mutex_x;</span><br><span class="line">static mysql_mutex_t my_mutex_y;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">mysql_mutex_register(&quot;pfs_example&quot;, all_example_mutex, 2);</span><br><span class="line"></span><br><span class="line">mysql_mutex_init(key_mutex_x, &amp;my_mutex_x, NULL);</span><br><span class="line">mysql_mutex_init(key_mutex_y, &amp;my_mutex_y, NULL);</span><br><span class="line"></span><br><span class="line">mysql_mutex_lock(&amp;my_mutex_x);</span><br><span class="line">mysql_mutex_trylock(&amp;my_mutex_y);</span><br><span class="line"></span><br><span class="line">mysql_mutex_unlock(&amp;my_mutex_y);</span><br><span class="line">mysql_mutex_unlock(&amp;my_mutex_x);</span><br><span class="line"></span><br><span class="line">mysql_mutex_destroy(&amp;my_mutex_x);</span><br><span class="line">mysql_mutex_destroy(&amp;my_mutex_y);</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>lock 的时候， 就是mysql_mutex_lock(&amp;m_thd-&gt;LOCK_thd_data);</p><h3 id="关键的数据结构"><a href="#关键的数据结构" class="headerlink" title="关键的数据结构"></a>关键的数据结构</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line">struct mysql_mutex_t &#123;</span><br><span class="line">  /** The real mutex. */</span><br><span class="line">  my_mutex_t m_mutex;</span><br><span class="line">  /**</span><br><span class="line">    The instrumentation hook.</span><br><span class="line">    Note that this hook is not conditionally defined,</span><br><span class="line">    for binary compatibility of the @c mysql_mutex_t interface.</span><br><span class="line">  */</span><br><span class="line">  struct PSI_mutex *m_psi&#123;nullptr&#125;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">struct my_mutex_t &#123;</span><br><span class="line">  union u &#123;</span><br><span class="line">    native_mutex_t m_native;          ////////pthread_mutex_t</span><br><span class="line">    safe_mutex_t *m_safe_ptr;</span><br><span class="line">  &#125; m_u;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">/** Instrumented mutex implementation. @see PSI_mutex. */</span><br><span class="line">struct PFS_ALIGNED PFS_mutex : public PFS_instr &#123;</span><br><span class="line">  /** Mutex identity, typically a @c pthread_mutex_t. */</span><br><span class="line">  const void *m_identity;</span><br><span class="line">  /** Mutex class. */</span><br><span class="line">  PFS_mutex_class *m_class;</span><br><span class="line">  /** Instrument statistics. */</span><br><span class="line">  PFS_mutex_stat m_mutex_stat;</span><br><span class="line">  /** Current owner. */</span><br><span class="line">  PFS_thread *m_owner;</span><br><span class="line">  /**</span><br><span class="line">    Time stamp of the last lock.</span><br><span class="line">    This statistic is not exposed in user visible tables yet.</span><br><span class="line">  */</span><br><span class="line">  ulonglong m_last_locked;</span><br><span class="line">&#125;;</span><br><span class="line">struct PSI_mutex_info_v1 &#123;</span><br><span class="line">  /**</span><br><span class="line">    Pointer to the key assigned to the registered mutex.</span><br><span class="line">  */</span><br><span class="line">  PSI_mutex_key *m_key;</span><br><span class="line">  /**</span><br><span class="line">    The name of the mutex to register.</span><br><span class="line">  */</span><br><span class="line">  const char *m_name;</span><br><span class="line">  /**</span><br><span class="line">    The flags of the mutex to register.</span><br><span class="line">    @sa PSI_FLAG_SINGLETON</span><br><span class="line">  */</span><br><span class="line">  unsigned int m_flags;</span><br><span class="line">  /** Volatility index. */</span><br><span class="line">  int m_volatility;</span><br><span class="line">  /** Documentation. */</span><br><span class="line">  const char *m_documentation;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">struct PFS_ALIGNED PFS_mutex_class : public PFS_instr_class &#123;</span><br><span class="line">  /** Mutex usage statistics. */</span><br><span class="line">  PFS_mutex_stat m_mutex_stat;</span><br><span class="line">  /** Singleton instance. */</span><br><span class="line">  PFS_mutex *m_singleton;</span><br><span class="line">&#125;;</span><br><span class="line">struct PFS_mutex_stat &#123;</span><br><span class="line">  /** Wait statistics. */</span><br><span class="line">  PFS_single_stat m_wait_stat;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="使用流程"><a href="#使用流程" class="headerlink" title="使用流程"></a>使用流程</h3><ol><li>注册<br>使用上,需要先register psi, 类似这样</li></ol><p>先创建PSI_mutex_key&#x2F;PSI_mutex_info， 然后进行注册</p><p>参考之前的使用方式</p><p>注册函数解析</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">pfs_register_mutex_v1 &#123;</span><br><span class="line">  // 生成formatted_name  &lt;--   mutex_instrument_prefix.str + / + category + / + PSI_mutex_info_v1.m_name</span><br><span class="line">  key = register_mutex_class(formatted_name, (uint)full_length, info);   </span><br><span class="line">  *(PSI_mutex_info_v1-&gt;m_key) = key</span><br><span class="line"></span><br><span class="line">  //register_mutex_class 功能</span><br><span class="line">  // 从mutex_class_array 中找到一个空的 PFS_mutex_class, 这个index 后面存储到*(PSI_mutex_info_v1-&gt;m_key) </span><br><span class="line">  // init_instr_class， 初始化这个PFS_mutex_class， </span><br><span class="line">  // configure_instr_class  ， 从pfs_instr_config_array 中找有没有和PFS_mutex_class-&gt;m_name 正则匹配的， 则设置PFS_mutex_class</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>init 操作</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">static inline int inline_mysql_mutex_init(</span><br><span class="line">    PSI_mutex_key key MY_ATTRIBUTE((unused)), mysql_mutex_t *that,</span><br><span class="line">    const native_mutexattr_t *attr, const char *src_file MY_ATTRIBUTE((unused)),</span><br><span class="line">    uint src_line MY_ATTRIBUTE((unused))) &#123;</span><br><span class="line">  that-&gt;m_psi = PSI_MUTEX_CALL(init_mutex)(key, &amp;that-&gt;m_mutex);   </span><br><span class="line">  // 这个地方调用 psi_mutex_service-&gt;init_mutex(key, &amp;that-&gt;m_mutex); --&gt; pfs_init_mutex_v1</span><br><span class="line">     </span><br><span class="line"></span><br><span class="line">  return my_mutex_init(&amp;that-&gt;m_mutex, attr);  // 这个就是原始pthread 的init</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">//從mutex_class_array 拿到對應key的PFS_mutex_class</span><br><span class="line">// pfs = create_mutex(klass, identity);   从global_mutex_container 中拿到PFS_mutex, 然后初始化PFS_mutex</span><br><span class="line">PSI_mutex *pfs_init_mutex_v1(PSI_mutex_key key, const void *identity) &#123;</span><br><span class="line">  PFS_mutex_class *klass;</span><br><span class="line">  PFS_mutex *pfs;</span><br><span class="line">  klass = find_mutex_class(key);</span><br><span class="line">  if (unlikely(klass == NULL)) &#123;</span><br><span class="line">    return NULL;</span><br><span class="line">  &#125;</span><br><span class="line">  pfs = create_mutex(klass, identity);</span><br><span class="line">  return reinterpret_cast&lt;PSI_mutex *&gt;(pfs);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>lock&#x2F;unlock 的过程<br>当开始锁的时候， 如果enable 了time 跟踪， 会记录下申请锁 到获得锁 的时间戳， 在获得锁的时候， 会把等待时间累加进去， 并记录获得锁的时间， 如果enable 了thread， 会把时间累加到event_name_array[index]上，如果enable FLAG_EVENT, 会有一个PFS_events_waits event , 然后插入 insert_events_waits_history.<br>unlock 的时候，就直接把指针给指向空, 这个地方没有跟踪 lock的时间。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br></pre></td><td class="code"><pre><span class="line">static inline int inline_mysql_mutex_lock(</span><br><span class="line">    mysql_mutex_t *that, const char *src_file MY_ATTRIBUTE((unused)),</span><br><span class="line">    uint src_line MY_ATTRIBUTE((unused))) &#123;</span><br><span class="line">  int result;</span><br><span class="line"></span><br><span class="line">  if (that-&gt;m_psi != NULL) &#123;</span><br><span class="line">    /* Instrumentation start */</span><br><span class="line">    PSI_mutex_locker *locker;</span><br><span class="line">    PSI_mutex_locker_state state;</span><br><span class="line">    locker = PSI_MUTEX_CALL(start_mutex_wait)(</span><br><span class="line">        &amp;state, that-&gt;m_psi, PSI_MUTEX_LOCK, src_file, src_line);  --&gt; pfs_start_mutex_wait_v1</span><br><span class="line"></span><br><span class="line">    /* Instrumented code */</span><br><span class="line">    result = my_mutex_lock(&amp;that-&gt;m_mutex);</span><br><span class="line"></span><br><span class="line">    /* Instrumentation end */</span><br><span class="line">    if (locker != NULL) &#123;</span><br><span class="line">      PSI_MUTEX_CALL(end_mutex_wait)(locker, result);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    return result;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  /* Non instrumented code */</span><br><span class="line">  result = my_mutex_lock(&amp;that-&gt;m_mutex);</span><br><span class="line"></span><br><span class="line">  return result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">PSI_mutex_locker *pfs_start_mutex_wait_v1(PSI_mutex_locker_state *state,</span><br><span class="line">                                          PSI_mutex *mutex,</span><br><span class="line">                                          PSI_mutex_operation op,</span><br><span class="line">                                          const char *src_file, uint src_line) &#123;</span><br><span class="line">  PFS_mutex *pfs_mutex = reinterpret_cast&lt;PFS_mutex *&gt;(mutex);</span><br><span class="line">  DBUG_ASSERT((int)op &gt;= 0);</span><br><span class="line">  DBUG_ASSERT((uint)op &lt; array_elements(mutex_operation_map));</span><br><span class="line">  DBUG_ASSERT(state != NULL);</span><br><span class="line"></span><br><span class="line">  DBUG_ASSERT(pfs_mutex != NULL);</span><br><span class="line">  DBUG_ASSERT(pfs_mutex-&gt;m_class != NULL);</span><br><span class="line"></span><br><span class="line">  if (!pfs_mutex-&gt;m_enabled) &#123;</span><br><span class="line">    return NULL;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  uint flags;</span><br><span class="line">  ulonglong timer_start = 0;</span><br><span class="line"></span><br><span class="line">  if (flag_thread_instrumentation) &#123;</span><br><span class="line">    PFS_thread *pfs_thread = my_thread_get_THR_PFS();</span><br><span class="line">    if (unlikely(pfs_thread == NULL)) &#123;</span><br><span class="line">      return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    if (!pfs_thread-&gt;m_enabled) &#123;</span><br><span class="line">      return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    state-&gt;m_thread = reinterpret_cast&lt;PSI_thread *&gt;(pfs_thread);</span><br><span class="line">    flags = STATE_FLAG_THREAD;</span><br><span class="line"></span><br><span class="line">    if (pfs_mutex-&gt;m_timed) &#123;</span><br><span class="line">      timer_start = get_wait_timer();</span><br><span class="line">      state-&gt;m_timer_start = timer_start;</span><br><span class="line">      flags |= STATE_FLAG_TIMED;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (flag_events_waits_current) &#123;</span><br><span class="line">      if (unlikely(pfs_thread-&gt;m_events_waits_current &gt;=</span><br><span class="line">                   &amp;pfs_thread-&gt;m_events_waits_stack[WAIT_STACK_SIZE])) &#123;</span><br><span class="line">        locker_lost++;</span><br><span class="line">        return NULL;</span><br><span class="line">      &#125;</span><br><span class="line">      PFS_events_waits *wait = pfs_thread-&gt;m_events_waits_current;</span><br><span class="line">      state-&gt;m_wait = wait;</span><br><span class="line">      flags |= STATE_FLAG_EVENT;</span><br><span class="line"></span><br><span class="line">      PFS_events_waits *parent_event = wait - 1;</span><br><span class="line">      wait-&gt;m_event_type = EVENT_TYPE_WAIT;</span><br><span class="line">      wait-&gt;m_nesting_event_id = parent_event-&gt;m_event_id;</span><br><span class="line">      wait-&gt;m_nesting_event_type = parent_event-&gt;m_event_type;</span><br><span class="line"></span><br><span class="line">      wait-&gt;m_thread_internal_id = pfs_thread-&gt;m_thread_internal_id;</span><br><span class="line">      wait-&gt;m_class = pfs_mutex-&gt;m_class;</span><br><span class="line">      wait-&gt;m_timer_start = timer_start;</span><br><span class="line">      wait-&gt;m_timer_end = 0;</span><br><span class="line">      wait-&gt;m_object_instance_addr = pfs_mutex-&gt;m_identity;</span><br><span class="line">      wait-&gt;m_event_id = pfs_thread-&gt;m_event_id++;</span><br><span class="line">      wait-&gt;m_end_event_id = 0;</span><br><span class="line">      wait-&gt;m_operation = mutex_operation_map[(int)op];</span><br><span class="line">      wait-&gt;m_source_file = src_file;</span><br><span class="line">      wait-&gt;m_source_line = src_line;</span><br><span class="line">      wait-&gt;m_wait_class = WAIT_CLASS_MUTEX;</span><br><span class="line"></span><br><span class="line">      pfs_thread-&gt;m_events_waits_current++;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    if (pfs_mutex-&gt;m_timed) &#123;</span><br><span class="line">      timer_start = get_wait_timer();</span><br><span class="line">      state-&gt;m_timer_start = timer_start;</span><br><span class="line">      flags = STATE_FLAG_TIMED;</span><br><span class="line">      state-&gt;m_thread = NULL;</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      /*</span><br><span class="line">        Complete shortcut.</span><br><span class="line">      */</span><br><span class="line">      /* Aggregate to EVENTS_WAITS_SUMMARY_BY_INSTANCE (counted) */</span><br><span class="line">      pfs_mutex-&gt;m_mutex_stat.m_wait_stat.aggregate_counted();</span><br><span class="line">      return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  state-&gt;m_flags = flags;</span><br><span class="line">  state-&gt;m_mutex = mutex;</span><br><span class="line">  return reinterpret_cast&lt;PSI_mutex_locker *&gt;(state);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">void pfs_end_mutex_wait_v1(PSI_mutex_locker *locker, int rc) &#123;</span><br><span class="line">  PSI_mutex_locker_state *state =</span><br><span class="line">      reinterpret_cast&lt;PSI_mutex_locker_state *&gt;(locker);</span><br><span class="line">  DBUG_ASSERT(state != NULL);</span><br><span class="line"></span><br><span class="line">  ulonglong timer_end = 0;</span><br><span class="line">  ulonglong wait_time = 0;</span><br><span class="line"></span><br><span class="line">  PFS_mutex *mutex = reinterpret_cast&lt;PFS_mutex *&gt;(state-&gt;m_mutex);</span><br><span class="line">  DBUG_ASSERT(mutex != NULL);</span><br><span class="line">  PFS_thread *thread = reinterpret_cast&lt;PFS_thread *&gt;(state-&gt;m_thread);</span><br><span class="line"></span><br><span class="line">  uint flags = state-&gt;m_flags;</span><br><span class="line"></span><br><span class="line">  if (flags &amp; STATE_FLAG_TIMED) &#123;</span><br><span class="line">    timer_end = get_wait_timer();</span><br><span class="line">    wait_time = timer_end - state-&gt;m_timer_start;</span><br><span class="line">    /* Aggregate to EVENTS_WAITS_SUMMARY_BY_INSTANCE (timed) */</span><br><span class="line">    mutex-&gt;m_mutex_stat.m_wait_stat.aggregate_value(wait_time);</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    /* Aggregate to EVENTS_WAITS_SUMMARY_BY_INSTANCE (counted) */</span><br><span class="line">    mutex-&gt;m_mutex_stat.m_wait_stat.aggregate_counted();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  if (likely(rc == 0)) &#123;</span><br><span class="line">    mutex-&gt;m_owner = thread;</span><br><span class="line">    mutex-&gt;m_last_locked = timer_end;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  if (flags &amp; STATE_FLAG_THREAD) &#123;</span><br><span class="line">    PFS_single_stat *event_name_array;</span><br><span class="line">    event_name_array = thread-&gt;write_instr_class_waits_stats();</span><br><span class="line">    uint index = mutex-&gt;m_class-&gt;m_event_name_index;</span><br><span class="line"></span><br><span class="line">    DBUG_ASSERT(index &lt;= wait_class_max);</span><br><span class="line">    DBUG_ASSERT(sanitize_thread(thread) != NULL);</span><br><span class="line"></span><br><span class="line">    if (flags &amp; STATE_FLAG_TIMED) &#123;</span><br><span class="line">      /* Aggregate to EVENTS_WAITS_SUMMARY_BY_THREAD_BY_EVENT_NAME (timed) */</span><br><span class="line">      event_name_array[index].aggregate_value(wait_time);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      /* Aggregate to EVENTS_WAITS_SUMMARY_BY_THREAD_BY_EVENT_NAME (counted) */</span><br><span class="line">      event_name_array[index].aggregate_counted();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (flags &amp; STATE_FLAG_EVENT) &#123;</span><br><span class="line">      PFS_events_waits *wait =</span><br><span class="line">          reinterpret_cast&lt;PFS_events_waits *&gt;(state-&gt;m_wait);</span><br><span class="line">      DBUG_ASSERT(wait != NULL);</span><br><span class="line"></span><br><span class="line">      wait-&gt;m_timer_end = timer_end;</span><br><span class="line">      wait-&gt;m_end_event_id = thread-&gt;m_event_id;</span><br><span class="line">      if (thread-&gt;m_flag_events_waits_history) &#123;</span><br><span class="line">        insert_events_waits_history(thread, wait);</span><br><span class="line">      &#125;</span><br><span class="line">      if (thread-&gt;m_flag_events_waits_history_long) &#123;</span><br><span class="line">        insert_events_waits_history_long(wait);</span><br><span class="line">      &#125;</span><br><span class="line">      thread-&gt;m_events_waits_current--;</span><br><span class="line"></span><br><span class="line">      DBUG_ASSERT(wait == thread-&gt;m_events_waits_current);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// unlock 逻辑比较简单， 把对于的psi 对象指针指向空</span><br><span class="line">void pfs_unlock_mutex_v1(PSI_mutex *mutex) &#123;</span><br><span class="line">  PFS_mutex *pfs_mutex = reinterpret_cast&lt;PFS_mutex *&gt;(mutex);</span><br><span class="line"></span><br><span class="line">  DBUG_ASSERT(pfs_mutex != NULL);</span><br><span class="line"></span><br><span class="line">  /*</span><br><span class="line">    Note that this code is still protected by the instrumented mutex,</span><br><span class="line">    and therefore is thread safe. See inline_mysql_mutex_unlock().</span><br><span class="line">  */</span><br><span class="line"></span><br><span class="line">  /* Always update the instrumented state */</span><br><span class="line">  pfs_mutex-&gt;m_owner = NULL;</span><br><span class="line">  pfs_mutex-&gt;m_last_locked = 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="查询performance"><a href="#查询performance" class="headerlink" title="查询performance"></a>查询performance</h3><pre><code>&gt; show tables from performance_schema;&gt; show tables like &#39;events_statement%&#39;;&gt; show tables like &#39;events_wait%&#39;;&gt; select * from events_statements_history;</code></pre>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/storage/performance_schema/general/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/storage/performance_schema/general/"/>
    <published>2019-08-09T11:42:57.000Z</published>
    <summary>MySQL 技术解读 -- performance schema 源码走读</summary>
    <title>performance schema 源码走读</title>
    <updated>2024-02-02T13:24:47.466Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"performance schema 源码走读","description":"MySQL 技术解读 -- performance schema 源码走读","image":"https://ilongda.com/img/my.jpg","wordCount":3206,"datePublished":"2019-08-09T11:42:57.000Z","dateModified":"2024-02-02T13:24:47.466Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/performance_schema/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/performance_schema/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"performance schema 源码走读","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/performance_schema/index/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>转载：<a href="/knowledge/mysql/source_code_reading/storage/performance_schema">performance_schema</a></p><p>performance schema 是一个存储引擎, 可以提供对mysql 所有指标的监控， 是一套非常详细而复杂的监控系统， 不同的指标，使用了不同的接口， 另外有几个特点：</p><ol><li>它是运行时态， 因此是全内存存储， 重启后会丢失之前的数据</li><li>为了减少对运行时态的影响， 绝大部分资源都是提前申请好， 在performance_schema 初始化的时候，已经申请好了。涉及到2块内存， 一个是class 配置信息， 一个pfs state </li><li>不能增加sql 种类和语法</li></ol><p>本文主要分 3块：</p><ol><li>初始化</li><li>基本数据结构</li><li>使用过程</li></ol><h2 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h2><p>分为几个步骤</p><ol><li>准备pfs 内部系统的内存监控的类</li><li>准备好pfs 配置的内存, pfs 配置主要用于设置pfs_xx_class</li><li>初始化pfs – 初始化的核心操作， 最主要的核心操作是准备好PFS需要的资源，尤其是内存申请， class， pfs 监控项的container， 以mutex 为例： 申请param-&gt;m_mutex_class_sizing 个PFS_mutex_class， 存储到PFS_mutex_class的mutex_class_array中， 另外会申请监控項的container 如global_mutex_container</li><li>设置好所有的service, </li><li>把所有的pfs 的key 注册到pfs 中， 方便后续使用</li></ol><span id="more"></span><h3 id="pre-initialize-performance-schema"><a href="#pre-initialize-performance-schema" class="headerlink" title="pre_initialize_performance_schema"></a>pre_initialize_performance_schema</h3><p>第一步， 初始化PFS_builtin_memory_class的一些类， 和一些全局状态跟踪</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">void pre_initialize_performance_schema() &#123;</span><br><span class="line">  pfs_initialized = false;</span><br><span class="line"></span><br><span class="line">  init_all_builtin_memory_class();</span><br><span class="line">  // 初始化类似 builtin_memory_mutex/builtin_memory_rwlock/builtin_memory_mdl 等等</span><br><span class="line">  // 这些变量可以跟踪每种指标对应的内存消耗</span><br><span class="line">  // builtin_memory_mutex 类型为 PFS_builtin_memory_class</span><br><span class="line"></span><br><span class="line">  PFS_table_stat::g_reset_template.reset();</span><br><span class="line">  // 对PFS_table_stat 类的静态变量g_reset_template 进行重设</span><br><span class="line">  // PFS_table_stat 主要成员是</span><br><span class="line">  //    PFS_table_io_stat m_index_stat[MAX_INDEXES + 1], 跟进这个index 的fetch/insert/update/delete</span><br><span class="line">  //    PFS_table_lock_stat m_lock_stat; table 有9种锁, 每种锁的状态</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  global_idle_stat.reset();  // idle 的状态跟踪 </span><br><span class="line">  global_table_io_stat.reset();  // table io 的状态跟踪, </span><br><span class="line">  global_table_lock_stat.reset();  // table 锁的状态跟踪</span><br><span class="line">  g_histogram_pico_timers.init();   // PFS_histogram_timers 的状态跟踪</span><br><span class="line">  global_statements_histogram.reset(); //PFS_histogram </span><br><span class="line"></span><br><span class="line">  /*</span><br><span class="line">    There is no automatic cleanup. Please either use:</span><br><span class="line">    - my_thread_end()</span><br><span class="line">    - or PSI_server-&gt;delete_current_thread()</span><br><span class="line">    in the instrumented code, to explicitly cleanup the instrumentation.</span><br><span class="line">  */</span><br><span class="line">  THR_PFS = nullptr;           // PFS_thread</span><br><span class="line">  for (int i = 0; i &lt; THR_PFS_NUM_KEYS; ++i) &#123;</span><br><span class="line">    THR_PFS_contexts[i] = nullptr;  //PFS_table_context</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h3 id="init-pfs-instrument-array"><a href="#init-pfs-instrument-array" class="headerlink" title="init_pfs_instrument_array"></a>init_pfs_instrument_array</h3><p>   申请内存存放， PFS_instr_config, 每個pfs 的监控项的开关放在这里， 后面每个pfs 监控项在register class时， 会从这个配置项中获取是否打开， 是否要进行timer</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line">  Initialize the dynamic array used to hold PFS_INSTRUMENT configuration</span><br><span class="line">  options.</span><br><span class="line">*/****</span><br><span class="line">void init_pfs_instrument_array() &#123;</span><br><span class="line">  pfs_instr_config_array = new Pfs_instr_config_array(PSI_NOT_INSTRUMENTED);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">typedef Prealloced_array&lt;PFS_instr_config *, 10&gt; Pfs_instr_config_array;</span><br></pre></td></tr></table></figure><h3 id="initialize-performance-schema"><a href="#initialize-performance-schema" class="headerlink" title="initialize_performance_schema"></a>initialize_performance_schema</h3><p>初始化 performance_schema storage</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">pfs_rc = initialize_performance_schema(</span><br><span class="line">          &amp;pfs_param, &amp;psi_thread_hook, &amp;psi_mutex_hook, &amp;psi_rwlock_hook,</span><br><span class="line">          &amp;psi_cond_hook, &amp;psi_file_hook, &amp;psi_socket_hook, &amp;psi_table_hook,</span><br><span class="line">          &amp;psi_mdl_hook, &amp;psi_idle_hook, &amp;psi_stage_hook, &amp;psi_statement_hook,</span><br><span class="line">          &amp;psi_transaction_hook, &amp;psi_memory_hook, &amp;psi_error_hook,</span><br><span class="line">          &amp;psi_parallel_query_hook, &amp;psi_parallel_operator_hook,</span><br><span class="line">          &amp;psi_data_lock_hook, &amp;psi_system_hook);</span><br><span class="line">      if ((pfs_rc != 0) &amp;&amp; pfs_param.m_enabled) &#123;</span><br><span class="line">        pfs_param.m_enabled = false;</span><br><span class="line">        LogErr(WARNING_LEVEL, ER_PERFSCHEMA_INIT_FAILED);</span><br><span class="line">      &#125;</span><br><span class="line"></span><br><span class="line">initialize_performance_schema （） &#123;</span><br><span class="line">  pfs_automated_sizing(param);  //把PFS_sizing_data large_data 设置到param 中， 主要是类似p-&gt;m_events_waits_history_long_sizing</span><br><span class="line">  pfs_minimal_setting(param);  如果设置了performance_schema_minimal 为true， 则很多设置全部关掉</span><br><span class="line">  init_timers();  //初始化timer 一些偏硬件/操作系统底层函数， 方便获取一些时间</span><br><span class="line">  init_event_name_sizing(param); //设置 mutex_class_start/rwlock_class_start， </span><br><span class="line">  //在register psi（register_mutex_class）时， 得到PSI_mutex_info-&gt;m_event_name_index=mutex_class_start + index</span><br><span class="line"></span><br><span class="line">  register_global_classes(); // 注冊global 的class in pre_initialize_performance_schema</span><br><span class="line"></span><br><span class="line">  minimal_global_classes(param); // 当注册performance_schema_minimal 为true， 修改global class的一些enable和m_timed</span><br><span class="line"></span><br><span class="line">  //</span><br><span class="line">  init_sync_class  </span><br><span class="line">  // 以mutex 为例： 申请param-&gt;m_mutex_class_sizing 个PFS_mutex_class， </span><br><span class="line">  //存储到mutex_class_array 后， 后面register 会进行设置， 在init 会查找， 申请的内存变化会更新到builtin_memory_mutex_class</span><br><span class="line">  init_thread_class</span><br><span class="line">  init_table_share  // 初始化global_table_share_container， 但没有真正申请内存  </span><br><span class="line">  //typedef PFS_buffer_scalable_container&lt;PFS_table_share, 4 * 1024, 4 * 1024&gt; PFS_table_share_container;</span><br><span class="line">  init_table_share_lock_stat</span><br><span class="line">  // 很多数据结构使用无锁hash 来存储</span><br><span class="line"></span><br><span class="line">  // 设置一大堆consumer的flag</span><br><span class="line">  flag_events_stages_current =</span><br><span class="line">        param-&gt;m_consumer_events_stages_current_enabled;</span><br><span class="line"></span><br><span class="line">  init_pfs_plugin_table</span><br><span class="line">  // PFS_dynamic_table_shares::init_mutex</span><br><span class="line">  //</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="设置PFS-service"><a href="#设置PFS-service" class="headerlink" title="设置PFS service"></a>设置PFS service</h3><p>设置各种service, 类似这样</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">。。。</span><br><span class="line">if (psi_memory_hook != NULL) &#123;</span><br><span class="line">  service = psi_memory_hook-&gt;get_interface(PSI_CURRENT_MEMORY_VERSION);</span><br><span class="line">  if (service != NULL) &#123;</span><br><span class="line">    set_psi_memory_service(service);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">。。。</span><br></pre></td></tr></table></figure><p>这个地方就是把这个地方设置一下<br>typedef struct PSI_mutex_service_v1 PSI_mutex_service_t;<br>PSI_mutex_service_t     psi_mutex_service  &#x3D;   pfs_mutex_service_v1   &#x2F;   psi_mutex_noop<br>psi_mutex_service 定义在psi_noop.cc 文件中， pfs_mutex_service_v1 定义在storage&#x2F;perfschema&#x2F;pfs.cc中</p><p>如果是pfs 插件编译， 就会使用这个， 但目前是直接编译进内核， 因此不需要插件化加载<br>mysql_service_psi_mutex_v1_t               mysql_service_psi_mutex_v1  &#x3D; imp_performance_schema_psi_mutex_v1<br>mysql_service_psi_mutex_v1_t imp_performance_schema_psi_mutex_v1 定义在storage&#x2F;perfschema&#x2F;pfs.cc中</p><h3 id="初始化所有的key"><a href="#初始化所有的key" class="headerlink" title="初始化所有的key"></a>初始化所有的key</h3><p>初始化所有的key, 提前把一部分 register mutext&#x2F;memory psi 等结构 注册进去</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">/*</span><br><span class="line">    Now that we have parsed the command line arguments, and have initialized</span><br><span class="line">    the performance schema itself, the next step is to register all the</span><br><span class="line">    server instruments.</span><br><span class="line">  */</span><br><span class="line">static void init_server_psi_keys(void) &#123;.</span><br><span class="line"></span><br><span class="line">  ...</span><br><span class="line">  count = static_cast&lt;int&gt;(array_elements(all_server_mutexes));</span><br><span class="line">  mysql_mutex_register(category, all_server_mutexes, count);</span><br><span class="line"></span><br><span class="line">  count = static_cast&lt;int&gt;(array_elements(all_server_rwlocks));</span><br><span class="line">  mysql_rwlock_register(category, all_server_rwlocks, count);</span><br><span class="line">  ...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="基本结构"><a href="#基本结构" class="headerlink" title="基本结构"></a>基本结构</h2><p>公共数据结构, 后续会使用到, 先列在这里<br><span class="exturl" data-url="aHR0cHM6Ly9kZXYubXlzcWwuY29tL2RvYy9kZXYvbXlzcWwtc2VydmVyLzguMC4yMC9zdHJ1Y3RQRlNfX2luc3RyLmh0bWw=">https://dev.mysql.com/doc/dev/mysql-server/8.0.20/structPFS__instr.html<i class="fa fa-external-link-alt"></i></span></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">struct PFS_instr &#123;</span><br><span class="line">  /** Internal lock. */</span><br><span class="line">  pfs_lock m_lock;</span><br><span class="line">  /** Enabled flag. */</span><br><span class="line">  bool m_enabled;</span><br><span class="line">  /** Timed flag. */</span><br><span class="line">  bool m_timed;</span><br><span class="line">  /** Container page. */</span><br><span class="line">  PFS_opaque_container_page *m_page; // 参考PFS_partitioned_buffer_scalable_container</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">//存儲在結構pfs_instr_config_array 中</span><br><span class="line">struct PFS_instr_config &#123;  </span><br><span class="line">  /* Instrument name. */</span><br><span class="line">  char *m_name;</span><br><span class="line">  /* Name length. */</span><br><span class="line">  uint m_name_length;</span><br><span class="line">  /** Enabled flag. */</span><br><span class="line">  bool m_enabled;</span><br><span class="line">  /** Timed flag. */</span><br><span class="line">  bool m_timed;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>有這麼多種類</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line">enum PFS_class_type &#123;</span><br><span class="line">  PFS_CLASS_NONE = 0,</span><br><span class="line">  PFS_CLASS_MUTEX = 1,</span><br><span class="line">  PFS_CLASS_RWLOCK = 2,</span><br><span class="line">  PFS_CLASS_COND = 3,</span><br><span class="line">  PFS_CLASS_FILE = 4,</span><br><span class="line">  PFS_CLASS_TABLE = 5,</span><br><span class="line">  PFS_CLASS_STAGE = 6,</span><br><span class="line">  PFS_CLASS_STATEMENT = 7,</span><br><span class="line">  PFS_CLASS_TRANSACTION = 8,</span><br><span class="line">  PFS_CLASS_SOCKET = 9,</span><br><span class="line">  PFS_CLASS_TABLE_IO = 10,</span><br><span class="line">  PFS_CLASS_TABLE_LOCK = 11,</span><br><span class="line">  PFS_CLASS_IDLE = 12,</span><br><span class="line">  PFS_CLASS_MEMORY = 13,</span><br><span class="line">  PFS_CLASS_METADATA = 14,</span><br><span class="line">  PFS_CLASS_ERROR = 15,</span><br><span class="line">  PFS_CLASS_THREAD = 16,</span><br><span class="line">  /* Reserve 17-29 for official mysql */</span><br><span class="line">  PFS_CLASS_PARALLEL_QUERY = 30,</span><br><span class="line"></span><br><span class="line">  PFS_CLASS_LAST = PFS_CLASS_PARALLEL_QUERY,</span><br><span class="line">  PFS_CLASS_MAX = PFS_CLASS_LAST + 1</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">// 做一些状态统计的</span><br><span class="line">struct PFS_single_stat &#123;</span><br><span class="line">  /** Count of values. */</span><br><span class="line">  ulonglong m_count;</span><br><span class="line">  /** Sum of values. */</span><br><span class="line">  ulonglong m_sum;</span><br><span class="line">  /** Minimum value. */</span><br><span class="line">  ulonglong m_min;</span><br><span class="line">  /** Maximum value. */</span><br><span class="line">  ulonglong m_max;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/** Instrumentation metadata for a mutex. */</span><br><span class="line">struct PFS_ALIGNED PFS_mutex_class : public PFS_instr_class &#123;</span><br><span class="line">  /** Mutex usage statistics. */</span><br><span class="line">  PFS_mutex_stat m_mutex_stat;</span><br><span class="line">  /** Singleton instance. */</span><br><span class="line">  PFS_mutex *m_singleton;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">/** Information for all instrumentation. */</span><br><span class="line">struct PFS_instr_class &#123;</span><br><span class="line">  /** Class type */</span><br><span class="line">  PFS_class_type m_type;</span><br><span class="line">  /** True if this instrument is enabled. */</span><br><span class="line">  bool m_enabled;</span><br><span class="line">  /** True if this instrument is timed. */</span><br><span class="line">  bool m_timed;</span><br><span class="line">  /** Instrument flags. */</span><br><span class="line">  uint m_flags;</span><br><span class="line">  /** Volatility index. */</span><br><span class="line">  int m_volatility;</span><br><span class="line">  /**</span><br><span class="line">    Instrument name index.</span><br><span class="line">    Self index in:</span><br><span class="line">    - EVENTS_WAITS_SUMMARY_*_BY_EVENT_NAME for waits</span><br><span class="line">    - EVENTS_STAGES_SUMMARY_*_BY_EVENT_NAME for stages</span><br><span class="line">    - EVENTS_STATEMENTS_SUMMARY_*_BY_EVENT_NAME for statements</span><br><span class="line">    - EVENTS_TRANSACTIONS_SUMMARY_*_BY_EVENT_NAME for transactions</span><br><span class="line">  */</span><br><span class="line">  uint m_event_name_index;</span><br><span class="line">  /** Instrument name. */</span><br><span class="line">  char m_name[PFS_MAX_INFO_NAME_LENGTH];</span><br><span class="line">  /** Length in bytes of @c m_name. */</span><br><span class="line">  uint m_name_length;</span><br><span class="line">  /** Documentation. */</span><br><span class="line">  char *m_documentation;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p>以mutex 为例，<br>在配置文件中，打开performance_schema， 对部分的配置进行单独设置</p><p>对于performance schema 关闭的情况下, psi_mutex_service 对应的就是psi_mutex_noop<br>对于打开performance schema情况下, 对应的是 pfs_mutex_service_v1<br>每个配置项是 performance_schema_instrument &#x3D; ‘ wait&#x2F;synch&#x2F;mutex  &#x3D;  ON  ‘ 是一行， 可以多项，表示enable 多个pfs 监控项, 如果不是精确匹配的话， 就建议增加正则匹配% 来代表所有</p><p>m_consumer_global_instrumentation_enabled  可以控制如锁之类（mutex&#x2F;lock&#x2F;rwlock&#x2F;cond）， 文件（file）， table 之类， 控制范围比较广， 默认为true<br>performance_schema_consumer_thread_instrumentation， thread 相关的都由他控制， 默认为true， </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">performance_schema = ON</span><br><span class="line">m_consumer_global_instrumentation_enabled = 1  </span><br><span class="line">// 对于一些配置，可以单独进行设置</span><br><span class="line">performance_schema_max_mutex_classes=1024</span><br><span class="line">performance_schema_instrument = &#x27; wait/synch/mutex/%  =  ON  &#x27;</span><br><span class="line">performance_schema_instrument = &#x27; wait/io/file/%  =  ON  &#x27;</span><br></pre></td></tr></table></figure><h3 id="使用接口"><a href="#使用接口" class="headerlink" title="使用接口"></a>使用接口</h3><p>对mutex 的使用完全和使用pthread mutex 行为基本一致， 可以参考components&#x2F;pfs_example&#x2F;pfs_example.cc</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">mysql_mutex_register  --&gt; psi_mutex_service-&gt;register_mutex  --&gt; pfs_register_mutex_v1</span><br><span class="line">mysql_mutex_init   --&gt; psi_mutex_service-&gt;init_mutex   /my_mutex_init</span><br><span class="line">mysql_mutex_destroy</span><br><span class="line">mysql_mutex_lock</span><br><span class="line">mysql_mutex_trylock</span><br><span class="line">mysql_mutex_unlock</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">static PSI_mutex_key key_mutex_x = 0;</span><br><span class="line">static PSI_mutex_key key_mutex_y = 0;</span><br><span class="line"></span><br><span class="line">static PSI_mutex_info all_example_mutex[] = &#123;</span><br><span class="line">    &#123;&amp;key_mutex_x, &quot;X&quot;, PSI_FLAG_SINGLETON, PSI_VOLATILITY_PERMANENT,</span><br><span class="line">     &quot;Example doc, permanent mutex, singleton.&quot;&#125;,</span><br><span class="line">    &#123;&amp;key_mutex_y, &quot;Y&quot;, 0, PSI_VOLATILITY_QUERY,</span><br><span class="line">     &quot;Example doc, very volatile mutexes.&quot;&#125;&#125;;</span><br><span class="line"></span><br><span class="line">static mysql_mutex_t my_mutex_x;</span><br><span class="line">static mysql_mutex_t my_mutex_y;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">mysql_mutex_register(&quot;pfs_example&quot;, all_example_mutex, 2);</span><br><span class="line"></span><br><span class="line">mysql_mutex_init(key_mutex_x, &amp;my_mutex_x, NULL);</span><br><span class="line">mysql_mutex_init(key_mutex_y, &amp;my_mutex_y, NULL);</span><br><span class="line"></span><br><span class="line">mysql_mutex_lock(&amp;my_mutex_x);</span><br><span class="line">mysql_mutex_trylock(&amp;my_mutex_y);</span><br><span class="line"></span><br><span class="line">mysql_mutex_unlock(&amp;my_mutex_y);</span><br><span class="line">mysql_mutex_unlock(&amp;my_mutex_x);</span><br><span class="line"></span><br><span class="line">mysql_mutex_destroy(&amp;my_mutex_x);</span><br><span class="line">mysql_mutex_destroy(&amp;my_mutex_y);</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>lock 的时候， 就是mysql_mutex_lock(&amp;m_thd-&gt;LOCK_thd_data);</p><h3 id="关键的数据结构"><a href="#关键的数据结构" class="headerlink" title="关键的数据结构"></a>关键的数据结构</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br></pre></td><td class="code"><pre><span class="line">struct mysql_mutex_t &#123;</span><br><span class="line">  /** The real mutex. */</span><br><span class="line">  my_mutex_t m_mutex;</span><br><span class="line">  /**</span><br><span class="line">    The instrumentation hook.</span><br><span class="line">    Note that this hook is not conditionally defined,</span><br><span class="line">    for binary compatibility of the @c mysql_mutex_t interface.</span><br><span class="line">  */</span><br><span class="line">  struct PSI_mutex *m_psi&#123;nullptr&#125;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">struct my_mutex_t &#123;</span><br><span class="line">  union u &#123;</span><br><span class="line">    native_mutex_t m_native;          ////////pthread_mutex_t</span><br><span class="line">    safe_mutex_t *m_safe_ptr;</span><br><span class="line">  &#125; m_u;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">/** Instrumented mutex implementation. @see PSI_mutex. */</span><br><span class="line">struct PFS_ALIGNED PFS_mutex : public PFS_instr &#123;</span><br><span class="line">  /** Mutex identity, typically a @c pthread_mutex_t. */</span><br><span class="line">  const void *m_identity;</span><br><span class="line">  /** Mutex class. */</span><br><span class="line">  PFS_mutex_class *m_class;</span><br><span class="line">  /** Instrument statistics. */</span><br><span class="line">  PFS_mutex_stat m_mutex_stat;</span><br><span class="line">  /** Current owner. */</span><br><span class="line">  PFS_thread *m_owner;</span><br><span class="line">  /**</span><br><span class="line">    Time stamp of the last lock.</span><br><span class="line">    This statistic is not exposed in user visible tables yet.</span><br><span class="line">  */</span><br><span class="line">  ulonglong m_last_locked;</span><br><span class="line">&#125;;</span><br><span class="line">struct PSI_mutex_info_v1 &#123;</span><br><span class="line">  /**</span><br><span class="line">    Pointer to the key assigned to the registered mutex.</span><br><span class="line">  */</span><br><span class="line">  PSI_mutex_key *m_key;</span><br><span class="line">  /**</span><br><span class="line">    The name of the mutex to register.</span><br><span class="line">  */</span><br><span class="line">  const char *m_name;</span><br><span class="line">  /**</span><br><span class="line">    The flags of the mutex to register.</span><br><span class="line">    @sa PSI_FLAG_SINGLETON</span><br><span class="line">  */</span><br><span class="line">  unsigned int m_flags;</span><br><span class="line">  /** Volatility index. */</span><br><span class="line">  int m_volatility;</span><br><span class="line">  /** Documentation. */</span><br><span class="line">  const char *m_documentation;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">struct PFS_ALIGNED PFS_mutex_class : public PFS_instr_class &#123;</span><br><span class="line">  /** Mutex usage statistics. */</span><br><span class="line">  PFS_mutex_stat m_mutex_stat;</span><br><span class="line">  /** Singleton instance. */</span><br><span class="line">  PFS_mutex *m_singleton;</span><br><span class="line">&#125;;</span><br><span class="line">struct PFS_mutex_stat &#123;</span><br><span class="line">  /** Wait statistics. */</span><br><span class="line">  PFS_single_stat m_wait_stat;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="使用流程"><a href="#使用流程" class="headerlink" title="使用流程"></a>使用流程</h3><ol><li>注册<br>使用上,需要先register psi, 类似这样</li></ol><p>先创建PSI_mutex_key&#x2F;PSI_mutex_info， 然后进行注册</p><p>参考之前的使用方式</p><p>注册函数解析</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">pfs_register_mutex_v1 &#123;</span><br><span class="line">  // 生成formatted_name  &lt;--   mutex_instrument_prefix.str + / + category + / + PSI_mutex_info_v1.m_name</span><br><span class="line">  key = register_mutex_class(formatted_name, (uint)full_length, info);   </span><br><span class="line">  *(PSI_mutex_info_v1-&gt;m_key) = key</span><br><span class="line"></span><br><span class="line">  //register_mutex_class 功能</span><br><span class="line">  // 从mutex_class_array 中找到一个空的 PFS_mutex_class, 这个index 后面存储到*(PSI_mutex_info_v1-&gt;m_key) </span><br><span class="line">  // init_instr_class， 初始化这个PFS_mutex_class， </span><br><span class="line">  // configure_instr_class  ， 从pfs_instr_config_array 中找有没有和PFS_mutex_class-&gt;m_name 正则匹配的， 则设置PFS_mutex_class</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="2"><li>init 操作</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">static inline int inline_mysql_mutex_init(</span><br><span class="line">    PSI_mutex_key key MY_ATTRIBUTE((unused)), mysql_mutex_t *that,</span><br><span class="line">    const native_mutexattr_t *attr, const char *src_file MY_ATTRIBUTE((unused)),</span><br><span class="line">    uint src_line MY_ATTRIBUTE((unused))) &#123;</span><br><span class="line">  that-&gt;m_psi = PSI_MUTEX_CALL(init_mutex)(key, &amp;that-&gt;m_mutex);   </span><br><span class="line">  // 这个地方调用 psi_mutex_service-&gt;init_mutex(key, &amp;that-&gt;m_mutex); --&gt; pfs_init_mutex_v1</span><br><span class="line">     </span><br><span class="line"></span><br><span class="line">  return my_mutex_init(&amp;that-&gt;m_mutex, attr);  // 这个就是原始pthread 的init</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">//從mutex_class_array 拿到對應key的PFS_mutex_class</span><br><span class="line">// pfs = create_mutex(klass, identity);   从global_mutex_container 中拿到PFS_mutex, 然后初始化PFS_mutex</span><br><span class="line">PSI_mutex *pfs_init_mutex_v1(PSI_mutex_key key, const void *identity) &#123;</span><br><span class="line">  PFS_mutex_class *klass;</span><br><span class="line">  PFS_mutex *pfs;</span><br><span class="line">  klass = find_mutex_class(key);</span><br><span class="line">  if (unlikely(klass == NULL)) &#123;</span><br><span class="line">    return NULL;</span><br><span class="line">  &#125;</span><br><span class="line">  pfs = create_mutex(klass, identity);</span><br><span class="line">  return reinterpret_cast&lt;PSI_mutex *&gt;(pfs);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><ol start="3"><li>lock&#x2F;unlock 的过程<br>当开始锁的时候， 如果enable 了time 跟踪， 会记录下申请锁 到获得锁 的时间戳， 在获得锁的时候， 会把等待时间累加进去， 并记录获得锁的时间， 如果enable 了thread， 会把时间累加到event_name_array[index]上，如果enable FLAG_EVENT, 会有一个PFS_events_waits event , 然后插入 insert_events_waits_history.<br>unlock 的时候，就直接把指针给指向空, 这个地方没有跟踪 lock的时间。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br></pre></td><td class="code"><pre><span class="line">static inline int inline_mysql_mutex_lock(</span><br><span class="line">    mysql_mutex_t *that, const char *src_file MY_ATTRIBUTE((unused)),</span><br><span class="line">    uint src_line MY_ATTRIBUTE((unused))) &#123;</span><br><span class="line">  int result;</span><br><span class="line"></span><br><span class="line">  if (that-&gt;m_psi != NULL) &#123;</span><br><span class="line">    /* Instrumentation start */</span><br><span class="line">    PSI_mutex_locker *locker;</span><br><span class="line">    PSI_mutex_locker_state state;</span><br><span class="line">    locker = PSI_MUTEX_CALL(start_mutex_wait)(</span><br><span class="line">        &amp;state, that-&gt;m_psi, PSI_MUTEX_LOCK, src_file, src_line);  --&gt; pfs_start_mutex_wait_v1</span><br><span class="line"></span><br><span class="line">    /* Instrumented code */</span><br><span class="line">    result = my_mutex_lock(&amp;that-&gt;m_mutex);</span><br><span class="line"></span><br><span class="line">    /* Instrumentation end */</span><br><span class="line">    if (locker != NULL) &#123;</span><br><span class="line">      PSI_MUTEX_CALL(end_mutex_wait)(locker, result);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    return result;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  /* Non instrumented code */</span><br><span class="line">  result = my_mutex_lock(&amp;that-&gt;m_mutex);</span><br><span class="line"></span><br><span class="line">  return result;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">PSI_mutex_locker *pfs_start_mutex_wait_v1(PSI_mutex_locker_state *state,</span><br><span class="line">                                          PSI_mutex *mutex,</span><br><span class="line">                                          PSI_mutex_operation op,</span><br><span class="line">                                          const char *src_file, uint src_line) &#123;</span><br><span class="line">  PFS_mutex *pfs_mutex = reinterpret_cast&lt;PFS_mutex *&gt;(mutex);</span><br><span class="line">  DBUG_ASSERT((int)op &gt;= 0);</span><br><span class="line">  DBUG_ASSERT((uint)op &lt; array_elements(mutex_operation_map));</span><br><span class="line">  DBUG_ASSERT(state != NULL);</span><br><span class="line"></span><br><span class="line">  DBUG_ASSERT(pfs_mutex != NULL);</span><br><span class="line">  DBUG_ASSERT(pfs_mutex-&gt;m_class != NULL);</span><br><span class="line"></span><br><span class="line">  if (!pfs_mutex-&gt;m_enabled) &#123;</span><br><span class="line">    return NULL;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  uint flags;</span><br><span class="line">  ulonglong timer_start = 0;</span><br><span class="line"></span><br><span class="line">  if (flag_thread_instrumentation) &#123;</span><br><span class="line">    PFS_thread *pfs_thread = my_thread_get_THR_PFS();</span><br><span class="line">    if (unlikely(pfs_thread == NULL)) &#123;</span><br><span class="line">      return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    if (!pfs_thread-&gt;m_enabled) &#123;</span><br><span class="line">      return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">    state-&gt;m_thread = reinterpret_cast&lt;PSI_thread *&gt;(pfs_thread);</span><br><span class="line">    flags = STATE_FLAG_THREAD;</span><br><span class="line"></span><br><span class="line">    if (pfs_mutex-&gt;m_timed) &#123;</span><br><span class="line">      timer_start = get_wait_timer();</span><br><span class="line">      state-&gt;m_timer_start = timer_start;</span><br><span class="line">      flags |= STATE_FLAG_TIMED;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (flag_events_waits_current) &#123;</span><br><span class="line">      if (unlikely(pfs_thread-&gt;m_events_waits_current &gt;=</span><br><span class="line">                   &amp;pfs_thread-&gt;m_events_waits_stack[WAIT_STACK_SIZE])) &#123;</span><br><span class="line">        locker_lost++;</span><br><span class="line">        return NULL;</span><br><span class="line">      &#125;</span><br><span class="line">      PFS_events_waits *wait = pfs_thread-&gt;m_events_waits_current;</span><br><span class="line">      state-&gt;m_wait = wait;</span><br><span class="line">      flags |= STATE_FLAG_EVENT;</span><br><span class="line"></span><br><span class="line">      PFS_events_waits *parent_event = wait - 1;</span><br><span class="line">      wait-&gt;m_event_type = EVENT_TYPE_WAIT;</span><br><span class="line">      wait-&gt;m_nesting_event_id = parent_event-&gt;m_event_id;</span><br><span class="line">      wait-&gt;m_nesting_event_type = parent_event-&gt;m_event_type;</span><br><span class="line"></span><br><span class="line">      wait-&gt;m_thread_internal_id = pfs_thread-&gt;m_thread_internal_id;</span><br><span class="line">      wait-&gt;m_class = pfs_mutex-&gt;m_class;</span><br><span class="line">      wait-&gt;m_timer_start = timer_start;</span><br><span class="line">      wait-&gt;m_timer_end = 0;</span><br><span class="line">      wait-&gt;m_object_instance_addr = pfs_mutex-&gt;m_identity;</span><br><span class="line">      wait-&gt;m_event_id = pfs_thread-&gt;m_event_id++;</span><br><span class="line">      wait-&gt;m_end_event_id = 0;</span><br><span class="line">      wait-&gt;m_operation = mutex_operation_map[(int)op];</span><br><span class="line">      wait-&gt;m_source_file = src_file;</span><br><span class="line">      wait-&gt;m_source_line = src_line;</span><br><span class="line">      wait-&gt;m_wait_class = WAIT_CLASS_MUTEX;</span><br><span class="line"></span><br><span class="line">      pfs_thread-&gt;m_events_waits_current++;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    if (pfs_mutex-&gt;m_timed) &#123;</span><br><span class="line">      timer_start = get_wait_timer();</span><br><span class="line">      state-&gt;m_timer_start = timer_start;</span><br><span class="line">      flags = STATE_FLAG_TIMED;</span><br><span class="line">      state-&gt;m_thread = NULL;</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      /*</span><br><span class="line">        Complete shortcut.</span><br><span class="line">      */</span><br><span class="line">      /* Aggregate to EVENTS_WAITS_SUMMARY_BY_INSTANCE (counted) */</span><br><span class="line">      pfs_mutex-&gt;m_mutex_stat.m_wait_stat.aggregate_counted();</span><br><span class="line">      return NULL;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  state-&gt;m_flags = flags;</span><br><span class="line">  state-&gt;m_mutex = mutex;</span><br><span class="line">  return reinterpret_cast&lt;PSI_mutex_locker *&gt;(state);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">void pfs_end_mutex_wait_v1(PSI_mutex_locker *locker, int rc) &#123;</span><br><span class="line">  PSI_mutex_locker_state *state =</span><br><span class="line">      reinterpret_cast&lt;PSI_mutex_locker_state *&gt;(locker);</span><br><span class="line">  DBUG_ASSERT(state != NULL);</span><br><span class="line"></span><br><span class="line">  ulonglong timer_end = 0;</span><br><span class="line">  ulonglong wait_time = 0;</span><br><span class="line"></span><br><span class="line">  PFS_mutex *mutex = reinterpret_cast&lt;PFS_mutex *&gt;(state-&gt;m_mutex);</span><br><span class="line">  DBUG_ASSERT(mutex != NULL);</span><br><span class="line">  PFS_thread *thread = reinterpret_cast&lt;PFS_thread *&gt;(state-&gt;m_thread);</span><br><span class="line"></span><br><span class="line">  uint flags = state-&gt;m_flags;</span><br><span class="line"></span><br><span class="line">  if (flags &amp; STATE_FLAG_TIMED) &#123;</span><br><span class="line">    timer_end = get_wait_timer();</span><br><span class="line">    wait_time = timer_end - state-&gt;m_timer_start;</span><br><span class="line">    /* Aggregate to EVENTS_WAITS_SUMMARY_BY_INSTANCE (timed) */</span><br><span class="line">    mutex-&gt;m_mutex_stat.m_wait_stat.aggregate_value(wait_time);</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    /* Aggregate to EVENTS_WAITS_SUMMARY_BY_INSTANCE (counted) */</span><br><span class="line">    mutex-&gt;m_mutex_stat.m_wait_stat.aggregate_counted();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  if (likely(rc == 0)) &#123;</span><br><span class="line">    mutex-&gt;m_owner = thread;</span><br><span class="line">    mutex-&gt;m_last_locked = timer_end;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  if (flags &amp; STATE_FLAG_THREAD) &#123;</span><br><span class="line">    PFS_single_stat *event_name_array;</span><br><span class="line">    event_name_array = thread-&gt;write_instr_class_waits_stats();</span><br><span class="line">    uint index = mutex-&gt;m_class-&gt;m_event_name_index;</span><br><span class="line"></span><br><span class="line">    DBUG_ASSERT(index &lt;= wait_class_max);</span><br><span class="line">    DBUG_ASSERT(sanitize_thread(thread) != NULL);</span><br><span class="line"></span><br><span class="line">    if (flags &amp; STATE_FLAG_TIMED) &#123;</span><br><span class="line">      /* Aggregate to EVENTS_WAITS_SUMMARY_BY_THREAD_BY_EVENT_NAME (timed) */</span><br><span class="line">      event_name_array[index].aggregate_value(wait_time);</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">      /* Aggregate to EVENTS_WAITS_SUMMARY_BY_THREAD_BY_EVENT_NAME (counted) */</span><br><span class="line">      event_name_array[index].aggregate_counted();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    if (flags &amp; STATE_FLAG_EVENT) &#123;</span><br><span class="line">      PFS_events_waits *wait =</span><br><span class="line">          reinterpret_cast&lt;PFS_events_waits *&gt;(state-&gt;m_wait);</span><br><span class="line">      DBUG_ASSERT(wait != NULL);</span><br><span class="line"></span><br><span class="line">      wait-&gt;m_timer_end = timer_end;</span><br><span class="line">      wait-&gt;m_end_event_id = thread-&gt;m_event_id;</span><br><span class="line">      if (thread-&gt;m_flag_events_waits_history) &#123;</span><br><span class="line">        insert_events_waits_history(thread, wait);</span><br><span class="line">      &#125;</span><br><span class="line">      if (thread-&gt;m_flag_events_waits_history_long) &#123;</span><br><span class="line">        insert_events_waits_history_long(wait);</span><br><span class="line">      &#125;</span><br><span class="line">      thread-&gt;m_events_waits_current--;</span><br><span class="line"></span><br><span class="line">      DBUG_ASSERT(wait == thread-&gt;m_events_waits_current);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">// unlock 逻辑比较简单， 把对于的psi 对象指针指向空</span><br><span class="line">void pfs_unlock_mutex_v1(PSI_mutex *mutex) &#123;</span><br><span class="line">  PFS_mutex *pfs_mutex = reinterpret_cast&lt;PFS_mutex *&gt;(mutex);</span><br><span class="line"></span><br><span class="line">  DBUG_ASSERT(pfs_mutex != NULL);</span><br><span class="line"></span><br><span class="line">  /*</span><br><span class="line">    Note that this code is still protected by the instrumented mutex,</span><br><span class="line">    and therefore is thread safe. See inline_mysql_mutex_unlock().</span><br><span class="line">  */</span><br><span class="line"></span><br><span class="line">  /* Always update the instrumented state */</span><br><span class="line">  pfs_mutex-&gt;m_owner = NULL;</span><br><span class="line">  pfs_mutex-&gt;m_last_locked = 0;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="查询performance"><a href="#查询performance" class="headerlink" title="查询performance"></a>查询performance</h3><pre><code>&gt; show tables from performance_schema;&gt; show tables like &#39;events_statement%&#39;;&gt; show tables like &#39;events_wait%&#39;;&gt; select * from events_statements_history;</code></pre>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/storage/performance_schema/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/storage/performance_schema/index/"/>
    <published>2019-08-09T11:42:57.000Z</published>
    <summary>MySQL 技术解读 -- performance schema 源码走读</summary>
    <title>performance schema 源码走读</title>
    <updated>2024-02-02T13:24:47.466Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 工具类","description":"MySQL 源码工具类索引：DBUG、无锁 hash、链表、list、多线程、PFS 容器与 print 等实用组件笔记","image":"https://ilongda.com/img/my.jpg","wordCount":7,"datePublished":"2019-08-07T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.958Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 工具类","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/tools/DBUG_EXECUTE_IF.html">DBUG_EXECUTE_IF</a></br></li><li><a href="/knowledge/mysql/source_code_reading/tools/lf_hash.html">lf_hash</a></br></li><li><a href="/knowledge/mysql/source_code_reading/tools/link_list.html">link_list</a></br></li><li><a href="/knowledge/mysql/source_code_reading/tools/list.html">list</a></br></li><li><a href="/knowledge/mysql/source_code_reading/tools/multi_thread.html">multi_thread</a></br></li><li><a href="/knowledge/mysql/source_code_reading/tools/pfs_buffer_container.html">pfs_buffer_container</a></br></li><li><a href="/knowledge/mysql/source_code_reading/tools/print.html">print</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/tools/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/tools/index/"/>
    <published>2019-08-07T11:42:57.000Z</published>
    <summary>MySQL 源码工具类索引：DBUG、无锁 hash、链表、list、多线程、PFS 容器与 print 等实用组件笔记</summary>
    <title>MySQL 源码解读 -- 工具类</title>
    <updated>2026-06-09T08:46:25.958Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 打印类","description":"MySQL 打印与告警源码笔记：push_warning、push_warning_printf 与 my_message_local 等日志输出路径","image":"https://ilongda.com/img/my.jpg","wordCount":46,"datePublished":"2019-08-07T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.959Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/print/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/print/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 打印类","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/print/"}]}</script><h1 id="打印类"><a href="#打印类" class="headerlink" title="打印类"></a>打印类</h1><p>在sql 中增加warning<br />push_warning(current_thd, Sql_condition::SL_WARNING, <br />            ER_WRONG_VALUE_FOR_VAR, wrong_msg);</p><p>push_warning_printf(thd, Sql_condition::SL_NOTE, ER_NO_SUCH_USER,<br />                        ER_THD(thd, ER_NO_SUCH_USER), lex-&gt;definer-&gt;user.str,<br />                        lex-&gt;definer-&gt;host.str);<br /><br><br />my_message_local  打印到master error log 中<br /><br><br /><br><br />my_getopt_error_reporter 打印到master error log<br /><br><br /></p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/tools/print/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/tools/print/"/>
    <published>2019-08-07T11:42:57.000Z</published>
    <summary>MySQL 打印与告警源码笔记：push_warning、push_warning_printf 与 my_message_local 等日志输出路径</summary>
    <title>MySQL 源码解读 -- 打印类</title>
    <updated>2026-06-09T08:46:25.959Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- PFS","description":"Performance Schema PFS_buffer_scalable_container 笔记：分页 buffer 容器及 mutex 分区容器实现思路","image":"https://ilongda.com/img/my.jpg","wordCount":925,"datePublished":"2019-08-06T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.959Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/pfs_buffer_container/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/pfs_buffer_container/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- PFS","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/pfs_buffer_container/"}]}</script><h2 id="pfs-buffer-container"><a href="#pfs-buffer-container" class="headerlink" title="pfs buffer container"></a>pfs buffer container</h2><p>很3个核心的类, </p><ol><li>template &lt;class T, int PFS_PAGE_SIZE, int PFS_PAGE_COUNT,<br>   class U &#x3D; PFS_buffer_default_array<T>,<br>   class V &#x3D; PFS_buffer_default_allocator<T>&gt;<br> class PFS_buffer_scalable_container</li><li>template <class T><br>class PFS_buffer_default_allocator</li><li>template <class T><br>class PFS_buffer_default_array</li></ol><p>PFS_buffer_scalable_container 数据结构<br>是一个类似buffer pool的算法, 其中存在n 个page, 每个page 有m 个row<br>一个page 就是由PFS_buffer_default_array 来管理<br>一个page 申请内存时, 就是调用PFS_buffer_default_allocator 来申请内存<br>一个row 就一个T </p><h2 id="PFS-buffer-scalable-container"><a href="#PFS-buffer-scalable-container" class="headerlink" title="PFS_buffer_scalable_container"></a>PFS_buffer_scalable_container</h2><p>類似的拓展類, PFS_buffer_container, PFS_partitioned_buffer_scalable_container</p><p>以mutex 为例, 进行解释</p><p>typedef PFS_buffer_scalable_container&lt;PFS_mutex, 1024, 1024&gt;<br>    PFS_mutex_basic_container;<br>typedef PFS_partitioned_buffer_scalable_container&lt;PFS_mutex_basic_container,<br>                                                  PFS_MUTEX_PARTITIONS&gt;<br>    PFS_mutex_container;<br>#define PFS_MUTEX_PARTITIONS 2</p><p>PFS_buffer_default_allocator<PFS_mutex> default_mutex_allocator(<br>    &amp;builtin_memory_mutex);<br>PFS_mutex_container global_mutex_container(&amp;default_mutex_allocator);</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">template &lt;class B, int PFS_PARTITION_COUNT&gt;</span><br><span class="line">class PFS_partitioned_buffer_scalable_container &#123;</span><br><span class="line">    // 设计非常简单, </span><br><span class="line">    B *m_partitions[PFS_PARTITION_COUNT];</span><br><span class="line"></span><br><span class="line">    所有操作都根据partition index 来找到对应的PFS_buffer_scalable_container 进行操作</span><br><span class="line">    如果需要求总值, 就将每个partition的对应值进行累加</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>PFS_buffer_scalable_container 数据结构<br>是一个类似buffer pool的算法, 其中存在n 个page, 每个page 1024 个row, 一个row就是一个B</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br></pre></td><td class="code"><pre><span class="line">template &lt;class T, int PFS_PAGE_SIZE, int PFS_PAGE_COUNT,</span><br><span class="line">          class U = PFS_buffer_default_array&lt;T&gt;,</span><br><span class="line">          class V = PFS_buffer_default_allocator&lt;T&gt;&gt;</span><br><span class="line">class PFS_buffer_scalable_container &#123;</span><br><span class="line">  bool m_initialized;</span><br><span class="line">  bool m_full;</span><br><span class="line">  size_t m_max;</span><br><span class="line">  PFS_cacheline_atomic_size_t m_monotonic;   // 当前申请row的那个page index</span><br><span class="line">  PFS_cacheline_atomic_size_t m_max_page_index; // 已经申请了多少page</span><br><span class="line">  size_t m_max_page_count;   // 可以申请最大页数</span><br><span class="line">  size_t m_last_page_size;   // 最后一页的page size</span><br><span class="line">  std::atomic&lt;array_type *&gt; m_pages[PFS_PAGE_COUNT];</span><br><span class="line">  allocator_type *m_allocator;</span><br><span class="line">  native_mutex_t m_critical_section;</span><br><span class="line"></span><br><span class="line">  /* dynamic shrink related */</span><br><span class="line">  /* now only PFS_thread_container allowed dynamic shrink */</span><br><span class="line">  bool m_dynamic_shrink;</span><br><span class="line">  /* each slot&#x27;s page refcount */</span><br><span class="line">  std::atomic&lt;size_t&gt; m_page_refcounts[PFS_PAGE_COUNT];</span><br><span class="line">  /* each slot&#x27;s page version snapshot */</span><br><span class="line">  size_t m_page_versions[PFS_PAGE_COUNT];</span><br><span class="line">  uint m_current_reclaim_begin;</span><br><span class="line">  uint m_current_reclaim_end;</span><br><span class="line"> public:</span><br><span class="line">  /* statistics of dynamic shrink */</span><br><span class="line">  std::atomic&lt;size_t&gt; page_spin_waits;</span><br><span class="line">  std::atomic&lt;size_t&gt; rc_spin_waits;</span><br><span class="line">  std::atomic&lt;size_t&gt; allocate_retry_count;</span><br><span class="line">  std::atomic&lt;size_t&gt; reclaim_fail_count;</span><br><span class="line">  std::atomic&lt;size_t&gt; reclaim_success_count;</span><br><span class="line"></span><br><span class="line">  int init(long max_size, bool dyn_shrink=false) &#123;</span><br><span class="line">    // 只是把对应的设置成员变量设置好, 但内存还没有申请, m_pages[PFS_PAGE_COUNT] 指向都为空</span><br><span class="line">    // 调用方式类似这样, param-&gt;m_mutex_sizing 由配置参数performance_schema_max_mutex_classes进行设置</span><br><span class="line">    //if (global_mutex_container.init(param-&gt;m_mutex_sizing)) &#123;</span><br><span class="line">    //    return 1;</span><br><span class="line">    //&#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  value_type *allocate(pfs_dirty_state *dirty_state) &#123;</span><br><span class="line">    // 第一步</span><br><span class="line">    // 根据m_monotonic 和m_max_page_index , 从已有的page中申请一个row 满足,</span><br><span class="line">    // pfs = safe_allocate_value(index, dirty_state);</span><br><span class="line">    // 如果可以申请,则返回, 否则 执行下一步</span><br><span class="line"></span><br><span class="line">    // 第二步</span><br><span class="line">    //array = load_page_no_pending(current_page_count);</span><br><span class="line">    // 其实就是m_pages[index].load();但如果对应的page 处在pending状态会等待</span><br><span class="line">    // 如果array 为空, 则array = allocate_one_array(current_page_count, true);</span><br><span class="line">    // 在allocate_one_array 函数中</span><br><span class="line">    // 如果设置了shrink, 则等待m_page_refcounts[index] 降为0</span><br><span class="line">    // 申请一个PFS_buffer_default_array, 并设置这个PFS_buffer_default_array</span><br><span class="line">    // 然后调用PFS_buffer_default_allocator&gt;alloc_array(PFS_buffer_default_array) 申请rows 的内存</span><br><span class="line">    // 申请一个page size个row</span><br><span class="line">    // </span><br><span class="line"></span><br><span class="line">    // 第三步</span><br><span class="line">    调用safe_allocate_value , 如果失败,从第二步再试</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="PFS-buffer-default-array"><a href="#PFS-buffer-default-array" class="headerlink" title="PFS_buffer_default_array"></a>PFS_buffer_default_array</h2><p>这个类核心提供一个allocate 和deallocate 接口<br>类成员变量</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">template &lt;class T&gt;</span><br><span class="line">class PFS_buffer_default_array &#123;</span><br><span class="line">  bool m_full;</span><br><span class="line">  PFS_cacheline_atomic_size_t m_monotonic;</span><br><span class="line">  T *m_ptr;</span><br><span class="line">  size_t m_max;</span><br><span class="line">  /** Container. */</span><br><span class="line">  PFS_opaque_container *m_container;</span><br><span class="line">  /*</span><br><span class="line">    version number for dynamic reclaim, each time array is used,</span><br><span class="line">    version increase.</span><br><span class="line">  */</span><br><span class="line">  PFS_cacheline_atomic_size_t m_version;</span><br><span class="line">  /*</span><br><span class="line">    indicating whether page is used, this is to avoid new</span><br><span class="line">    allocated page been freed without ever being used.</span><br><span class="line">  */</span><br><span class="line">  std::atomic&lt;bool&gt; m_used&#123;false&#125;;</span><br><span class="line">  /** page index */</span><br><span class="line">  uint m_page_index;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="PFS-buffer-default-array-1"><a href="#PFS-buffer-default-array-1" class="headerlink" title="PFS_buffer_default_array"></a>PFS_buffer_default_array</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">template &lt;class T&gt;</span><br><span class="line">class PFS_buffer_default_array &#123;</span><br><span class="line">  bool m_full;</span><br><span class="line">  PFS_cacheline_atomic_size_t m_monotonic;</span><br><span class="line">  T *m_ptr;</span><br><span class="line">  size_t m_max;</span><br><span class="line">  /** Container. */</span><br><span class="line">  PFS_opaque_container *m_container;</span><br><span class="line">  /*</span><br><span class="line">    version number for dynamic reclaim, each time array is used,</span><br><span class="line">    version increase.</span><br><span class="line">  */</span><br><span class="line">  PFS_cacheline_atomic_size_t m_version;</span><br><span class="line">  /*</span><br><span class="line">    indicating whether page is used, this is to avoid new</span><br><span class="line">    allocated page been freed without ever being used.</span><br><span class="line">  */</span><br><span class="line">  std::atomic&lt;bool&gt; m_used&#123;false&#125;;</span><br><span class="line">  /** page index */</span><br><span class="line">  uint m_page_index;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>唯一一个额外的地方,是alloc 函数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">value_type *allocate(pfs_dirty_state *dirty_state) &#123;</span><br><span class="line">    根据monotonic, 拿到对应位置的内存, 然后标记这个地方被申请</span><br><span class="line">    if (pfs-&gt;m_lock.free_to_dirty(dirty_state)) &#123;</span><br><span class="line">        /* increase version */</span><br><span class="line">        size_t version = m_version.m_size_t++;</span><br><span class="line">        if (unlikely(version == SIZE_MAX)) &#123;</span><br><span class="line">          m_version.m_size_t.store(1);</span><br><span class="line">        &#125;</span><br><span class="line">        m_used = true;</span><br><span class="line">        return pfs;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    以PFS_mutex 为例, </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/tools/pfs_buffer_container/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/tools/pfs_buffer_container/"/>
    <published>2019-08-06T11:42:57.000Z</published>
    <summary>Performance Schema PFS_buffer_scalable_container 笔记：分页 buffer 容器及 mutex 分区容器实现思路</summary>
    <title>MySQL 源码解读 -- PFS</title>
    <updated>2026-06-09T08:46:25.959Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 多线程","description":"MySQL 多线程 Mutex 源码笔记：mysql_mutex_t、safe_mutex 与 PSI_mutex instrumentation 抽象层","image":"https://ilongda.com/img/my.jpg","wordCount":578,"datePublished":"2019-08-05T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.959Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/multi_thread/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/multi_thread/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 多线程","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/multi_thread/"}]}</script><h1 id="多线程"><a href="#多线程" class="headerlink" title="多线程"></a>多线程</h1><p><a name="JWjTq"></a></p><h1 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h1><p><a name="aU1kR"></a></p><h3 id="Mutex"><a href="#Mutex" class="headerlink" title="Mutex"></a>Mutex</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">struct mysql_mutex_t &#123;</span><br><span class="line">  /** The real mutex. */</span><br><span class="line">  my_mutex_t m_mutex;</span><br><span class="line">  /**</span><br><span class="line">    The instrumentation hook.</span><br><span class="line">    Note that this hook is not conditionally defined,</span><br><span class="line">    for binary compatibility of the @c mysql_mutex_t interface.</span><br><span class="line">  */</span><br><span class="line">  struct PSI_mutex *m_psi&#123;nullptr&#125;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">typedef pthread_mutex_t native_mutex_t;</span><br><span class="line"></span><br><span class="line">struct my_mutex_t &#123;</span><br><span class="line">  union u &#123;</span><br><span class="line">    native_mutex_t m_native;</span><br><span class="line">    safe_mutex_t *m_safe_ptr; // 当在非linux下就使用safe_mutex_t, 同时会打开宏SAFE_MUTEX</span><br><span class="line">  &#125; m_u;</span><br><span class="line">&#125;;</span><br><span class="line">typedef struct my_mutex_t my_mutex_t;</span><br></pre></td></tr></table></figure><p>其中PSI_mutex 是一个抽象类， 会根据key的类型进行动态创建类型</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">PSI_mutex *pfs_init_mutex_v1(PSI_mutex_key key, const void *identity) &#123;</span><br><span class="line">  PFS_mutex_class *klass;</span><br><span class="line">  PFS_mutex *pfs;</span><br><span class="line">  klass = find_mutex_class(key);</span><br><span class="line">  if (unlikely(klass == NULL)) &#123;</span><br><span class="line">    return NULL;</span><br><span class="line">  &#125;</span><br><span class="line">  pfs = create_mutex(klass, identity);</span><br><span class="line">  return reinterpret_cast&lt;PSI_mutex *&gt;(pfs);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果key 为0， 是不会创建PSI_mutex</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">struct PSI_mutex_info_v1 &#123;</span><br><span class="line">  /**</span><br><span class="line">    Pointer to the key assigned to the registered mutex.</span><br><span class="line">  */</span><br><span class="line">  PSI_mutex_key *m_key;</span><br><span class="line">  /**</span><br><span class="line">    The name of the mutex to register.</span><br><span class="line">  */</span><br><span class="line">  const char *m_name;</span><br><span class="line">  /**</span><br><span class="line">    The flags of the mutex to register.</span><br><span class="line">    @sa PSI_FLAG_SINGLETON</span><br><span class="line">  */</span><br><span class="line">  unsigned int m_flags;</span><br><span class="line">  /** Volatility index. */</span><br><span class="line">  int m_volatility;</span><br><span class="line">  /** Documentation. */</span><br><span class="line">  const char *m_documentation;</span><br><span class="line">&#125;;</span><br><span class="line">typedef PSI_mutex_info_v1 PSI_mutex_info;</span><br></pre></td></tr></table></figure><p>举例来说<br />在thread pool</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">static PSI_mutex_key key_LOCK_thread_cache;</span><br><span class="line"></span><br><span class="line">static PSI_mutex_info all_per_thread_mutexes[] = &#123;</span><br><span class="line">    &#123;&amp;key_LOCK_thread_cache, &quot;LOCK_thread_cache&quot;, PSI_FLAG_SINGLETON, 0,</span><br><span class="line">     PSI_DOCUMENT_ME&#125;&#125;;</span><br></pre></td></tr></table></figure><p><a name="3GO78"></a></p><h4 id="mysql-mutex-register"><a href="#mysql-mutex-register" class="headerlink" title="mysql_mutex_register"></a>mysql_mutex_register</h4><p><strong>mysql_mutex_register</strong>(<strong>“sql”</strong>, all_per_thread_mutexes, count);<br />–&gt; psi_mutex_service.register_mutex(category, info, count);<br />      –&gt; pfs_register_mutex_v1(category, info, count)  {<br />               <strong>REGISTER_BODY_V1</strong>(PSI_mutex_key, mutex_instrument_prefix, register_mutex_class);<br />            }</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">#define REGISTER_BODY_V1(KEY_T, PREFIX, REGISTER_FUNC)                         \</span><br><span class="line">  KEY_T key;                                                                   \</span><br><span class="line">  char formatted_name[PFS_MAX_INFO_NAME_LENGTH];                               \</span><br><span class="line">  size_t prefix_length;                                                        \</span><br><span class="line">  size_t len;                                                                  \</span><br><span class="line">  size_t full_length;                                                          \</span><br><span class="line">                                                                               \</span><br><span class="line">  DBUG_ASSERT(category != NULL);                                               \</span><br><span class="line">  DBUG_ASSERT(info != NULL);                                                   \</span><br><span class="line">  if (unlikely(                                                                \</span><br><span class="line">          build_prefix(&amp;PREFIX, category, formatted_name, &amp;prefix_length)) ||  \</span><br><span class="line">          //formatted_name.append(PREFIX-&gt;str).append(&quot;/&quot;).append(category).append(&quot;/&quot;)</span><br><span class="line">      !pfs_initialized) &#123;                                                      \</span><br><span class="line">    for (; count &gt; 0; count--, info++) *(info-&gt;m_key) = 0;                     \</span><br><span class="line">    return;                                                                    \</span><br><span class="line">  &#125;                                                                            \</span><br><span class="line">                                                                               \</span><br><span class="line">  for (; count &gt; 0; count--, info++) &#123;                                         \</span><br><span class="line">    DBUG_ASSERT(info-&gt;m_key != NULL);                                          \</span><br><span class="line">    DBUG_ASSERT(info-&gt;m_name != NULL);                                         \</span><br><span class="line">    len = strlen(info-&gt;m_name);                                                \</span><br><span class="line">    full_length = prefix_length + len;                                         \</span><br><span class="line">    if (likely(full_length &lt;= PFS_MAX_INFO_NAME_LENGTH)) &#123;                     \</span><br><span class="line">      memcpy(formatted_name + prefix_length, info-&gt;m_name, len);               \</span><br><span class="line">      key = REGISTER_FUNC(formatted_name, (uint)full_length, info);            \</span><br><span class="line">    &#125; else &#123;                                                                   \</span><br><span class="line">      pfs_print_error(&quot;REGISTER_BODY_V1: name too long &lt;%s&gt; &lt;%s&gt;\n&quot;, category, \</span><br><span class="line">                      info-&gt;m_name);                                           \</span><br><span class="line">      key = 0;                                                                 \</span><br><span class="line">    &#125;                                                                          \</span><br><span class="line">                                                                               \</span><br><span class="line">    *(info-&gt;m_key) = 2;                                                      \</span><br><span class="line">  &#125;                                                                            \</span><br><span class="line">  return</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">typedef struct MYSQL_LEX_STRING LEX_STRING;</span><br><span class="line">typedef struct MYSQL_LEX_CSTRING LEX_CSTRING;</span><br><span class="line">struct MYSQL_LEX_STRING &#123;</span><br><span class="line">  char *str;</span><br><span class="line">  size_t length;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">struct MYSQL_LEX_CSTRING &#123;</span><br><span class="line">  const char *str;</span><br><span class="line">  size_t length;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><a name="iWFIw"></a></p><h3 id="cond"><a href="#cond" class="headerlink" title="cond"></a>cond</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">typedef pthread_cond_t native_cond_t;</span><br><span class="line"></span><br><span class="line">struct mysql_cond_t &#123;</span><br><span class="line">  /** The real condition */</span><br><span class="line">  native_cond_t m_cond;</span><br><span class="line">  /**</span><br><span class="line">    The instrumentation hook.</span><br><span class="line">    Note that this hook is not conditionally defined,</span><br><span class="line">    for binary compatibility of the @c mysql_cond_t interface.</span><br><span class="line">  */</span><br><span class="line">  struct PSI_cond *m_psi;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">static PSI_cond_key key_COND_thread_cache;</span><br><span class="line">static PSI_cond_key key_COND_flush_thread_cache;</span><br><span class="line"></span><br><span class="line">static PSI_cond_info all_per_thread_conds[] = &#123;</span><br><span class="line">    &#123;&amp;key_COND_thread_cache, &quot;COND_thread_cache&quot;, PSI_FLAG_SINGLETON, 0,</span><br><span class="line">     PSI_DOCUMENT_ME&#125;,</span><br><span class="line">    &#123;&amp;key_COND_flush_thread_cache, &quot;COND_flush_thread_cache&quot;,</span><br><span class="line">     PSI_FLAG_SINGLETON, 0, PSI_DOCUMENT_ME&#125;&#125;;</span><br><span class="line">     </span><br></pre></td></tr></table></figure><p><a name="sSTvY"></a></p><h1 id="Thread-pool"><a href="#Thread-pool" class="headerlink" title="Thread pool"></a>Thread pool</h1><p>Connection_handler_manager</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">switch (Connection_handler_manager::thread_handling) &#123;</span><br><span class="line">    case SCHEDULER_ONE_THREAD_PER_CONNECTION:</span><br><span class="line">      connection_handler = new (std::nothrow) Per_thread_connection_handler();</span><br><span class="line">      break;</span><br><span class="line">    case SCHEDULER_NO_THREADS:</span><br><span class="line">      connection_handler = new (std::nothrow) One_thread_connection_handler();</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/tools/multi_thread/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/tools/multi_thread/"/>
    <published>2019-08-05T11:42:57.000Z</published>
    <summary>MySQL 多线程 Mutex 源码笔记：mysql_mutex_t、safe_mutex 与 PSI_mutex instrumentation 抽象层</summary>
    <title>MySQL 源码解读 -- 多线程</title>
    <updated>2026-06-09T08:46:25.959Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- list","description":"MySQL Dynamic_array 与 DYNAMIC_ARRAY 源码笔记：类似内存池的数组扩容、append/pop 及 API 对比","image":"https://ilongda.com/img/my.jpg","wordCount":991,"datePublished":"2019-08-04T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.959Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/list/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/list/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- list","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/list/"}]}</script><h1 id="List-系列"><a href="#List-系列" class="headerlink" title="List 系列"></a>List 系列</h1><p><a name="OsPMM"></a></p><h1 id="Dynamic-array"><a href="#Dynamic-array" class="headerlink" title="Dynamic_array"></a>Dynamic_array</h1><p>Dynamic_array &amp; DYNAMIC_ARRAY 比较简单, 其核心就是类似内存池算法, 但比内存池重, 申请一块内存, 这块内存可以取出一个Elem, 如果释放一个Elem, 会将这个elem 后面的数据向前挪, 当elem 不够时, 会realloc 内存进行扩容.  DYNAMIC_STRING 可以 为DYNAMIC_ARRAY 的特殊类</p><p>Dynamic_array 有一个问题, 这里没有考虑线程安全性</p><p>在8.0 中, Dynamic_array 已经被取消, 取代它的是Prealloced_array<br /><br><br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">template &lt;class Elem&gt; class Dynamic_array</span><br><span class="line">&#123;</span><br><span class="line">  DYNAMIC_ARRAY  array;  核心类</span><br><span class="line">  //API</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">struct DYNAMIC_ARRAY &#123;</span><br><span class="line">  uchar *buffer&#123;nullptr&#125;;</span><br><span class="line">  uint elements&#123;0&#125;, max_element&#123;0&#125;;</span><br><span class="line">  uint alloc_increment&#123;0&#125;;</span><br><span class="line">  uint size_of_element&#123;0&#125;;</span><br><span class="line">  PSI_memory_key m_psi_key&#123;PSI_NOT_INSTRUMENTED&#125;;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">struct DYNAMIC_STRING &#123;</span><br><span class="line">  char *str;</span><br><span class="line">  size_t length, max_length, alloc_increment;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Dynamic_array api</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Elem&amp; at(int idx)    ---------&gt; 取内存池中第n 个elem</span><br><span class="line">Elem *front()        --&gt; 取出第一个elem</span><br><span class="line">Elem *back()         --&gt; 取出最后一个elem</span><br><span class="line">bool append(const Elem &amp;el)   --&gt; 插入一个elem, 核心调用 insert_dynamic(&amp;array, &amp;el);</span><br><span class="line">Elem&amp; pop()          --&gt; 弹出一个elem 位置, 核心调用 *((Elem*)pop_dynamic(&amp;array));</span><br><span class="line">del(uint idx)        --&gt; 删除某个elem, delete_dynamic(&amp;array);</span><br></pre></td></tr></table></figure><p>DYNAMIC_ARRAY api</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> 申请一块内存(init_alloc * element_size), 然后把相关变量设进DYNAMIC_ARRAY</span><br><span class="line"> */</span><br><span class="line">my_bool init_dynamic_array2(DYNAMIC_ARRAY *array, uint element_size,</span><br><span class="line">                            void *init_buffer, uint init_alloc, </span><br><span class="line">                            uint alloc_increment)</span><br><span class="line">/**</span><br><span class="line"> 如果原先内存池够, 将elem 设到对应的位置</span><br><span class="line"> 如果原先内存池不够, 对内存池进行扩容, 扩容alloc_increment个elem</span><br><span class="line"> */</span><br><span class="line">my_bool insert_dynamic(DYNAMIC_ARRAY *array, const void *element)</span><br><span class="line"></span><br><span class="line">/*扩容alloc_increment个elem */</span><br><span class="line">void *alloc_dynamic(DYNAMIC_ARRAY *array)</span><br><span class="line">/* 扩容到(max_elements + array-&gt;alloc_increment)/array-&gt;alloc_increment; 个elem*/</span><br><span class="line">my_bool allocate_dynamic(DYNAMIC_ARRAY *array, uint max_elements)</span><br><span class="line"></span><br><span class="line">/*把最后一个elem 返回*/</span><br><span class="line">void *pop_dynamic(DYNAMIC_ARRAY *array)</span><br><span class="line"></span><br><span class="line">/* 将第idx(从0开始)个elem 拷贝到element */</span><br><span class="line">void get_dynamic(DYNAMIC_ARRAY *array, void *element, uint idx)</span><br><span class="line"></span><br><span class="line">/*清空内存池*/</span><br><span class="line">void delete_dynamic(DYNAMIC_ARRAY *array)</span><br><span class="line"></span><br><span class="line">/*清除第idx个elem, 并把后面的elem 向前挪*/</span><br><span class="line">void delete_dynamic_element(DYNAMIC_ARRAY *array, uint idx)</span><br><span class="line"></span><br><span class="line">/* 把后面不用的elem的内存释放掉 */</span><br><span class="line">void freeze_size(DYNAMIC_ARRAY *array)</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Prealloced_array</span><br><span class="line">提前申请prealloc size大小的element 的内存, 然后如果使用过程中,</span><br><span class="line">如果element 不够用,再动态扩容, 可以支持push/pop/front/at 等操作</span><br></pre></td></tr></table></figure><p><a name="vMHeC"></a></p><h1 id="I-P-List"><a href="#I-P-List" class="headerlink" title="I_P_List"></a>I_P_List</h1><p>其实就是类似普通的List操作, mysql 这个实现做的比较general, 实现一个链表操作</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">template &lt;typename T, typename B, typename C = I_P_List_null_counter,</span><br><span class="line">          typename I = I_P_List_no_push_back&lt;T&gt;&gt;</span><br><span class="line">class I_P_List : public C, public I &#123;</span><br><span class="line">  T *m_first;</span><br><span class="line">  // api</span><br><span class="line">  </span><br><span class="line">  inline void push_front(T* a) --&gt; 插入a 到列表头</span><br><span class="line">  inline void push_back(T *a)  --&gt; 插入a 到列表尾</span><br><span class="line">  inline void insert_after(T *pos, T *a)  将a 插入到pos 之后</span><br><span class="line">  inline void remove(T *a)</span><br><span class="line">  inline T* pop_front()  --&gt;  把front 给弹出来, 然后在list 去掉front</span><br><span class="line">  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>typename B 必须实现类似如下接口</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">template &lt;typename T, T* T::*next, T** T::*prev&gt;</span><br><span class="line">struct I_P_List_adapter</span><br><span class="line">&#123;</span><br><span class="line">  static inline T **next_ptr(T *el) &#123; return &amp;(el-&gt;*next); &#125;</span><br><span class="line">  static inline const T* const* next_ptr(const T *el) &#123; return &amp;(el-&gt;*next); &#125;</span><br><span class="line">  static inline T ***prev_ptr(T *el) &#123; return &amp;(el-&gt;*prev); &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>I_P_List_null_counter  不会做统计, 推荐使用I_P_List_counter 会进行统计</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">class I_P_List_counter</span><br><span class="line">&#123;</span><br><span class="line">  uint m_counter;</span><br><span class="line">protected:</span><br><span class="line">  I_P_List_counter() : m_counter (0) &#123;&#125;</span><br><span class="line">  void reset() &#123;m_counter= 0;&#125;</span><br><span class="line">  void inc() &#123;m_counter++;&#125;</span><br><span class="line">  void dec() &#123;m_counter--;&#125;</span><br><span class="line">  void swap(I_P_List_counter &amp;rhs)</span><br><span class="line">  &#123; swap_variables(uint, m_counter, rhs.m_counter); &#125;</span><br><span class="line">public:</span><br><span class="line">  uint elements() const &#123; return m_counter; &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>如果使用I_P_List_no_push_back , 则不可以使用push back 操作, 只能插入到头</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">template &lt;typename T&gt;</span><br><span class="line">class I_P_List_fast_push_back &#123;</span><br><span class="line">  T **m_last;</span><br><span class="line"></span><br><span class="line"> protected:</span><br><span class="line">  I_P_List_fast_push_back(T **a) : m_last(a) &#123;&#125;</span><br><span class="line">  void set_last(T **a) &#123; m_last = a; &#125;</span><br><span class="line">  T **get_last() const &#123; return m_last; &#125;</span><br><span class="line">  void swap(I_P_List_fast_push_back&lt;T&gt; &amp;rhs) &#123; std::swap(m_last, rhs.m_last); &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><br />使用角度</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">typedef I_P_List&lt;MDL_ticket,</span><br><span class="line">                   I_P_List_adapter&lt;MDL_ticket,</span><br><span class="line">                                    &amp;MDL_ticket::next_in_context,</span><br><span class="line">                                    &amp;MDL_ticket::prev_in_context&gt; &gt;</span><br><span class="line">  Ticket_list;</span><br><span class="line"></span><br><span class="line">  typedef Ticket_list::Iterator Ticket_iterator;</span><br><span class="line">  </span><br><span class="line">  Ticket_list m_tickets[MDL_DURATION_END];</span><br><span class="line">  </span><br><span class="line">  template &lt;typename T, T* T::*next, T** T::*prev&gt;</span><br><span class="line">struct I_P_List_adapter</span><br><span class="line">&#123;</span><br><span class="line">  static inline T **next_ptr(T *el) &#123; return &amp;(el-&gt;*next); &#125;</span><br><span class="line">  static inline const T* const* next_ptr(const T *el) &#123; return &amp;(el-&gt;*next); &#125;</span><br><span class="line">  static inline T ***prev_ptr(T *el) &#123; return &amp;(el-&gt;*prev); &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">class MDL_ticket : public MDL_wait_for_subgraph</span><br><span class="line">&#123;</span><br><span class="line">public:</span><br><span class="line"></span><br><span class="line">  MDL_ticket *next_in_context;</span><br><span class="line">  MDL_ticket **prev_in_context;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/tools/list/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/tools/list/"/>
    <published>2019-08-04T11:42:57.000Z</published>
    <summary>MySQL Dynamic_array 与 DYNAMIC_ARRAY 源码笔记：类似内存池的数组扩容、append/pop 及 API 对比</summary>
    <title>MySQL 源码解读 -- list</title>
    <updated>2026-06-09T08:46:25.959Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 链表","description":"InnoDB ut_list 双向链表源码笔记：ut_list_node/base 模板设计与 buffer pool LRU 链使用示例","image":"https://ilongda.com/img/my.jpg","wordCount":853,"datePublished":"2019-08-03T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.959Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/link_list/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/link_list/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 链表","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/link_list/"}]}</script><h1 id="链表结构"><a href="#链表结构" class="headerlink" title="链表结构"></a>链表结构</h1><p><a name="KYI6I"></a></p><h2 id="链表结构-1"><a href="#链表结构-1" class="headerlink" title="链表结构"></a>链表结构</h2><p>innodb 的链表结构设计的比较巧妙，而且灵活， 一个基本的类型elem_type 可以是多个list的一个成员, 只要这个elem_type里面对应的node 对象<br><a name="klD9M"></a></p><h3 id="结构"><a href="#结构" class="headerlink" title="结构"></a>结构</h3><p>list node 节点, 一个指向 前node， 一个指向 后node</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">template &lt;typename Type&gt;</span><br><span class="line">struct ut_list_node &#123;</span><br><span class="line">  Type *prev; /*!&lt; pointer to the previous</span><br><span class="line">              node, NULL if start of list */</span><br><span class="line">  Type *next; /*!&lt; pointer to next node,</span><br><span class="line">              NULL if end of list */</span><br><span class="line"></span><br><span class="line">  void reverse() &#123;</span><br><span class="line">    Type *tmp = prev;</span><br><span class="line">    prev = next;</span><br><span class="line">    next = tmp;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>list 节点<br />一个指向list的头元素， 一个指向list的尾元素</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">template &lt;typename Type, typename NodePtr&gt;</span><br><span class="line">struct ut_list_base &#123;</span><br><span class="line">  typedef Type elem_type;</span><br><span class="line">  typedef NodePtr node_ptr;</span><br><span class="line">  typedef ut_list_node&lt;Type&gt; node_type;</span><br><span class="line"></span><br><span class="line">  ulint count&#123;0&#125;;            /*!&lt; count of nodes in list */</span><br><span class="line">  elem_type *start&#123;nullptr&#125;; /*!&lt; pointer to list start,</span><br><span class="line">                             NULL if empty */</span><br><span class="line">  elem_type *end&#123;nullptr&#125;;   /*!&lt; pointer to list end,</span><br><span class="line">                             NULL if empty */</span><br><span class="line">  node_ptr node&#123;nullptr&#125;;    /*!&lt; Pointer to member field</span><br><span class="line">                             that is used as a link node */</span><br><span class="line">#ifdef UNIV_DEBUG</span><br><span class="line">  ulint init&#123;0&#125;; /*!&lt; UT_LIST_INITIALISED if</span><br><span class="line">                 the list was initialised with</span><br><span class="line">                 UT_LIST_INIT() */</span><br><span class="line">#endif           /* UNIV_DEBUG */</span><br><span class="line"></span><br><span class="line">  void reverse() &#123;</span><br><span class="line">    Type *tmp = start;</span><br><span class="line">    start = end;</span><br><span class="line">    end = tmp;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>一般使用方式是</p><p><a name="ek11N"></a></p><h3 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h3><p><a name="mGrQE"></a></p><h4 id="获取对象"><a href="#获取对象" class="headerlink" title="获取对象"></a>获取对象</h4><p>举个例子， 以buffer_pool 中的LRU 为例<br />一个基本的类型elem_type 可以是多个list的一个成员, 只要这个elem_type里面对应的node 对象</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">#define UT_LIST_BASE_NODE_T(t) ut_list_base&lt;t, ut_list_node&lt;t&gt; t::*&gt;</span><br><span class="line">#define UT_LIST_NODE_T(t) ut_list_node&lt;t&gt;</span><br><span class="line">#define UT_LIST_INIT(b, pmf) \</span><br><span class="line">  &#123;                          \</span><br><span class="line">    (b).count = 0;           \</span><br><span class="line">    (b).start = 0;           \</span><br><span class="line">    (b).end = 0;             \</span><br><span class="line">    (b).node = pmf;          \</span><br><span class="line">    UT_LIST_INITIALISE(b);   \</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">struct buf_pool_t &#123;</span><br><span class="line">...</span><br><span class="line">UT_LIST_BASE_NODE_T(buf_page_t) LRU;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class buf_page_t &#123;</span><br><span class="line">...</span><br><span class="line">UT_LIST_NODE_T(buf_page_t) LRU; </span><br><span class="line">// 注意， 这个里面有一个要求，就是elem_type 里面得有ut_list_node， </span><br><span class="line">// 这样后续可以通过elem_type得到node 对象， 比如elem-&gt;*list.node</span><br><span class="line">...</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这个里面需要注意的就是如何从elem得到node的操作， 比如elem-&gt;*list.node<br />或者类似如下</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">/** Functor for accessing the embedded node within a list element. This is</span><br><span class="line">required because some lists can have the node emebedded inside a nested</span><br><span class="line">struct/union. See lock0priv.h (table locks) for an example. It provides a</span><br><span class="line">specialised functor to grant access to the list node. */</span><br><span class="line">template &lt;typename Type&gt;</span><br><span class="line">struct GenericGetNode &#123;</span><br><span class="line">  typedef ut_list_node&lt;Type&gt; node_type;</span><br><span class="line"></span><br><span class="line">  GenericGetNode(node_type Type::*node) : m_node(node) &#123;&#125;</span><br><span class="line"></span><br><span class="line">  node_type &amp;operator()(Type &amp;elem) &#123; return (elem.*m_node); &#125;</span><br><span class="line"></span><br><span class="line">  node_type Type::*m_node;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>使用上</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Functor get_node = GenericGetNode&lt;typename List::elem_type&gt;(list.node)</span><br><span class="line">typename List::node_type &amp;node = get_node(*elem);</span><br></pre></td></tr></table></figure><p><a name="1lwxV"></a></p><h4 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">UT_LIST_INIT(buf_pool-&gt;LRU, &amp;buf_page_t::LRU);</span><br><span class="line">UT_LIST_INIT(buf_pool-&gt;free, &amp;buf_page_t::list);</span><br><span class="line">UT_LIST_INIT(buf_pool-&gt;withdraw, &amp;buf_page_t::list);</span><br></pre></td></tr></table></figure><p><a name="APlqS"></a></p><h4 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">template &lt;typename List, typename Functor&gt;</span><br><span class="line">void ut_list_append(List &amp;list, typename List::elem_type *elem,</span><br><span class="line">                    Functor get_node) &#123;</span><br><span class="line">  typename List::node_type &amp;node = get_node(*elem);</span><br><span class="line"></span><br><span class="line">  UT_LIST_IS_INITIALISED(list);</span><br><span class="line"></span><br><span class="line">  node.next = 0;</span><br><span class="line">  node.prev = list.end;</span><br><span class="line"></span><br><span class="line">  if (list.end != 0) &#123;</span><br><span class="line">    typename List::node_type &amp;base_node = get_node(*list.end);</span><br><span class="line"></span><br><span class="line">    ut_ad(list.end != elem);</span><br><span class="line"></span><br><span class="line">    base_node.next = elem;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  list.end = elem;</span><br><span class="line"></span><br><span class="line">  if (list.start == 0) &#123;</span><br><span class="line">    list.start = elem;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ++list.count;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a name="5XvyM"></a></p><h4 id="删除一个节点"><a href="#删除一个节点" class="headerlink" title="删除一个节点"></a>删除一个节点</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">template &lt;typename List, typename Functor&gt;</span><br><span class="line">void ut_list_remove(List &amp;list, typename List::node_type &amp;node,</span><br><span class="line">                    Functor get_node) &#123;</span><br><span class="line">  ut_a(list.count &gt; 0);</span><br><span class="line">  UT_LIST_IS_INITIALISED(list);</span><br><span class="line"></span><br><span class="line">  if (node.next != NULL) &#123;</span><br><span class="line">    typename List::node_type &amp;next_node = get_node(*node.next);</span><br><span class="line"></span><br><span class="line">    next_node.prev = node.prev;</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    list.end = node.prev;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  if (node.prev != NULL) &#123;</span><br><span class="line">    typename List::node_type &amp;prev_node = get_node(*node.prev);</span><br><span class="line"></span><br><span class="line">    prev_node.next = node.next;</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    list.start = node.next;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  node.next = 0;</span><br><span class="line">  node.prev = 0;</span><br><span class="line"></span><br><span class="line">  --list.count;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a name="1hNxW"></a></p><h4 id="遍历list"><a href="#遍历list" class="headerlink" title="遍历list"></a>遍历list</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">template &lt;typename List&gt;</span><br><span class="line">void ut_list_reverse(List &amp;list) &#123;</span><br><span class="line">  UT_LIST_IS_INITIALISED(list);</span><br><span class="line"></span><br><span class="line">  for (typename List::elem_type *elem = list.start; elem != 0;</span><br><span class="line">       elem = (elem-&gt;*list.node).prev) &#123;</span><br><span class="line">    (elem-&gt;*list.node).reverse();</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  list.reverse();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/tools/link_list/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/tools/link_list/"/>
    <published>2019-08-03T11:42:57.000Z</published>
    <summary>InnoDB ut_list 双向链表源码笔记：ut_list_node/base 模板设计与 buffer pool LRU 链使用示例</summary>
    <title>MySQL 源码解读 -- 链表</title>
    <updated>2026-06-09T08:46:25.959Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 无锁hash","description":"MySQL 无锁 hash LF_HASH 源码笔记：LF_DYNARRAY、LF_ALLOCATOR 与 hazard pointer pin 机制分析","image":"https://cdn.nlark.com/yuque/0/2020/png/106206/1581951743012-926612a6-df5b-4130-90c0-2ff49d31819c.png#align=left&display=inline&height=678&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1356&originWidth=1270&size=109698&status=done&style=none&width=635","wordCount":1684,"datePublished":"2019-08-02T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.958Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/lf_hash/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/lf_hash/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 无锁hash","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/lf_hash/"}]}</script><h1 id="无锁hash"><a href="#无锁hash" class="headerlink" title="无锁hash"></a>无锁hash</h1><p><a name="SxkQ0"></a></p><h1 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h1><p><a name="ZKZkW"></a></p><h2 id="LF-HASH"><a href="#LF-HASH" class="headerlink" title="LF_HASH"></a>LF_HASH</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">typedef struct st_lf_hash &#123;</span><br><span class="line">  LF_DYNARRAY array;                    /* hash itself */ 这个内部的element size 是sizeof(LF_SLIST *)</span><br><span class="line">  LF_ALLOCATOR alloc;                   /* allocator for elements */</span><br><span class="line">  my_hash_get_key get_key;              /* see HASH */  --》query_cache_table_get_key</span><br><span class="line">  CHARSET_INFO *charset;                /* see HASH */</span><br><span class="line">  lf_hash_func *hash_function;          /* see HASH */</span><br><span class="line">  uint key_offset, key_length;          /* see HASH */</span><br><span class="line">  uint element_size;                    /* size of memcpy&#x27;ed area on insert */</span><br><span class="line">  uint flags;                           /* LF_HASH_UNIQUE, etc */</span><br><span class="line">  int32 volatile size;                  /* size of array */</span><br><span class="line">  int32 volatile count;                 /* number of elements in the hash */</span><br><span class="line">  /**</span><br><span class="line">    &quot;Initialize&quot; hook - called to finish initialization of object provided by</span><br><span class="line">     LF_ALLOCATOR (which is pointed by &quot;dst&quot; parameter) and set element key</span><br><span class="line">     from object passed as parameter to lf_hash_insert (pointed by &quot;src&quot;</span><br><span class="line">     parameter). Allows to use LF_HASH with objects which are not &quot;trivially</span><br><span class="line">     copyable&quot;.</span><br><span class="line">     NULL value means that element initialization is carried out by copying</span><br><span class="line">     first element_size bytes from object which provided as parameter to</span><br><span class="line">     lf_hash_insert.</span><br><span class="line">  */</span><br><span class="line">  lf_hash_init_func *initialize;</span><br><span class="line">&#125; LF_HASH;</span><br></pre></td></tr></table></figure><p>LF_DYNARRAY  是真正存放element 的地方<br />存放element后， 防止在使用的过程中， element 被释放了， 因此需要一个allocator, 里面关键是存放pinbox， 当使用一个元素时，申请一个lf_pins, 将这个指针放到lf_pins-&gt;pin中， 所有已回收的LF_PINS还是在数组中，不过使用stack来管理，pinstack_top_ver就是这个栈顶。前面说每个LF_PINS都有一个purgatory链表，链表中每个元素的next指针实际上是在addr的指定偏移位置，这个位置即由free_ptr_offset指定，free_func和free_func_arg则是当某个LF_PINS的purgatory等于10时，真正释放他们的回调函数及参数<br><a name="rFppx"></a></p><h2 id="LF-DYNARRAY"><a href="#LF-DYNARRAY" class="headerlink" title="LF_DYNARRAY"></a>LF_DYNARRAY</h2><p><a name="vGAmm"></a></p><h2 id="LF-DYNARRAY-1"><a href="#LF-DYNARRAY-1" class="headerlink" title="LF_DYNARRAY"></a>LF_DYNARRAY</h2><br /><a name="R9yAD"></a>## LF_DYNARRAY & LF_ALLOCATOR & LF_PINBOX<p><br /><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1581951743012-926612a6-df5b-4130-90c0-2ff49d31819c.png#align=left&display=inline&height=678&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1356&originWidth=1270&size=109698&status=done&style=none&width=635" alt="image.png"><br><a name="mVzu6"></a></p><h3 id="LF-DYNARRAY-2"><a href="#LF-DYNARRAY-2" class="headerlink" title="LF_DYNARRAY"></a>LF_DYNARRAY</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">#define LF_DYNARRAY_LEVEL_LENGTH 256</span><br><span class="line">#define LF_DYNARRAY_LEVELS       4</span><br><span class="line"></span><br><span class="line">typedef struct &#123;</span><br><span class="line">  void * volatile level[LF_DYNARRAY_LEVELS];</span><br><span class="line">  uint size_of_element;</span><br><span class="line">&#125; LF_DYNARRAY;</span><br><span class="line"></span><br><span class="line">level[0]：一个包含256个元素的数组，其中每个元素大小为size_of_element</span><br><span class="line">level[1]：一个包含256个指针的数组，其中每个指针指向1一个同level[0]定义的数组</span><br><span class="line">level[2]：一个包含256个指针的数组，其中每个指针指向1一个同level[1]定义的数组</span><br><span class="line">level[3]：一个包含256个指针的数组，其中每个指针指向1一个同level[2]定义的数组</span><br></pre></td></tr></table></figure><p><a name="uVlHf"></a></p><h3 id="LF-ALLOCATOR"><a href="#LF-ALLOCATOR" class="headerlink" title="LF_ALLOCATOR"></a>LF_ALLOCATOR</h3><p>LF_ALLOCATOR是一个简易的内存分配器，它里面包含一个LF_PINBOX，所有线程首先先从pinbox中申请一个LF_PINS，然后需要申请内存时就向LF_ALLOCATOR来申请（每次申请的大小固定），将申请到的地址pin在自己的LF_PINS当中。LF_ALLOCATOR将线程free掉的内存同样是用free stack来缓存起来，先看定义：<br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">typedef struct st_lf_allocator &#123;</span><br><span class="line">  LF_PINBOX pinbox; --》 这个这样初始化</span><br><span class="line">           //lf_pinbox_init(&amp;allocator-&gt;pinbox, </span><br><span class="line">           //      free_ptr_offset （实际上是offsetof(LF_SLIST, key)）,</span><br><span class="line">           //      (lf_pinbox_free_func *)alloc_free, allocator);</span><br><span class="line">                 </span><br><span class="line">  uchar * volatile top;</span><br><span class="line">  uint element_size;  --》 sizeof(LF_SLIST)+lf_hash-&gt;element_size</span><br><span class="line">  uint32 volatile mallocs;</span><br><span class="line">  lf_allocator_func *constructor; /* called, when an object is malloc()&#x27;ed */</span><br><span class="line">  lf_allocator_func *destructor;  /* called, when an object is free()&#x27;d    */</span><br><span class="line">&#125; LF_ALLOCATOR;</span><br></pre></td></tr></table></figure><p><a name="ShpbG"></a></p><h3 id="LF-PINBOX"><a href="#LF-PINBOX" class="headerlink" title="LF_PINBOX"></a>LF_PINBOX</h3><p>pinstack_top_ver 是要解决aba 的问题的</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">typedef struct &#123;</span><br><span class="line">  LF_DYNARRAY pinarray;  --&gt;&gt; lf_dynarray_init(&amp;pinbox-&gt;pinarray, sizeof(LF_PINS));</span><br><span class="line">  lf_pinbox_free_func *free_func;   --》 (lf_pinbox_free_func *)alloc_free</span><br><span class="line">  void *free_func_arg;    --》  //LF_HASH-&gt;alloc</span><br><span class="line">  uint free_ptr_offset;    --&gt; offsetof(LF_SLIST, key)</span><br><span class="line">  uint32 volatile pinstack_top_ver;         /* this is a versioned pointer */ </span><br><span class="line">  uint32 volatile pins_in_array;            /* number of elements in array */</span><br><span class="line">&#125; LF_PINBOX;</span><br></pre></td></tr></table></figure><p><a name="qhrKz"></a></p><h3 id="LF-PINS"><a href="#LF-PINS" class="headerlink" title="LF_PINS"></a>LF_PINS</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">typedef struct st_lf_pins &#123;</span><br><span class="line">  void * volatile pin[LF_PINBOX_PINS];</span><br><span class="line">  LF_PINBOX *pinbox;</span><br><span class="line">  void  *purgatory;</span><br><span class="line">  uint32 purgatory_count;</span><br><span class="line">  uint32 volatile link;</span><br><span class="line">/* we want sizeof(LF_PINS) to be 64 to avoid false sharing */</span><br><span class="line">#if SIZEOF_INT*2+SIZEOF_CHARP*(LF_PINBOX_PINS+2) != 64</span><br><span class="line">  char pad[64-sizeof(uint32)*2-sizeof(void*)*(LF_PINBOX_PINS+2)];</span><br><span class="line">#endif</span><br><span class="line">&#125; LF_PINS;</span><br></pre></td></tr></table></figure><p><a name="LV0pF"></a></p><h2 id=""><a href="#" class="headerlink" title=""></a></h2><p><a name="yeX0Q"></a></p><h2 id="LF-SLIST"><a href="#LF-SLIST" class="headerlink" title="LF_SLIST"></a>LF_SLIST</h2><br /><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">typedef struct &#123;</span><br><span class="line">  intptr volatile link; /* a pointer to the next element in a listand a flag */</span><br><span class="line">  uint32 hashnr;        /* reversed hash number, for sorting                 */</span><br><span class="line">  const uchar *key;</span><br><span class="line">  size_t keylen;</span><br><span class="line">  /*</span><br><span class="line">    data is stored here, directly after the keylen.</span><br><span class="line">    thus the pointer to data is (void*)(slist_element_ptr+1)</span><br><span class="line">  */</span><br><span class="line">&#125; LF_SLIST;</span><br></pre></td></tr></table></figure><a name="cbLF3"></a># <a name="syxlm"></a># 初始化来看一下如何使用它， <br />query cache 中有一个LF_HASH, <br />初始化时是这样：<br /><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">lf_hash_init(&amp;queries, sizeof(Query_cache_block *), LF_HASH_UNIQUE, 0, 0,</span><br><span class="line">               query_cache_query_get_key, &amp;my_charset_bin);</span><br></pre></td></tr></table></figure><p><br />真正使用上是这样<br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">#define lf_hash_init(A, B, C, D, E, F, G) \</span><br><span class="line">          lf_hash_init2(A, B, C, D, E, F, G, NULL, NULL, NULL, NULL)</span><br><span class="line">void lf_hash_init2(LF_HASH *hash, uint element_size, uint flags,</span><br><span class="line">                   uint key_offset, uint key_length, my_hash_get_key get_key,</span><br><span class="line">                   CHARSET_INFO *charset, lf_hash_func *hash_function,</span><br><span class="line">                   lf_allocator_func *ctor, lf_allocator_func *dtor,</span><br><span class="line">                   lf_hash_init_func *init);</span><br></pre></td></tr></table></figure><p><br />看看它的数据结构，基本就和初始化部分比较吻合<br /><br><br />初始化里面， 比较简单， 就几个注意点<br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">hash-&gt;charset= charset ? charset : &amp;my_charset_bin;</span><br><span class="line">hash-&gt;hash_function= hash_function ? hash_function : cset_hash_sort_adapter;</span><br></pre></td></tr></table></figure><p><br />介绍2个子数据结构的初始化 hash-&gt;array， 在lf_hash_init2函数中<br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">lf_dynarray_init(&amp;hash-&gt;array, sizeof(LF_SLIST *));</span><br><span class="line"></span><br><span class="line">void lf_dynarray_init(LF_DYNARRAY *array, uint element_size)</span><br><span class="line">&#123;</span><br><span class="line">  memset(array, 0, sizeof(*array));</span><br><span class="line">  array-&gt;size_of_element= element_size;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>数据结构如下<br /></p><p>第二个子数据结构 hash-&gt;alloc， 在函数 lf_hash_init2 中<br />初始化调用</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">lf_alloc_init2(&amp;hash-&gt;alloc, sizeof(LF_SLIST)+lf_hash-&gt;element_size,</span><br><span class="line">                 offsetof(LF_SLIST, key), ctor, dtor);</span><br></pre></td></tr></table></figure><p>再来看看 数据结构<br /><br><br />我们再来看pinbox 数据结构<br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">//初始化过程</span><br><span class="line">void lf_pinbox_init(LF_PINBOX *pinbox, uint free_ptr_offset,</span><br><span class="line">                    lf_pinbox_free_func *free_func, void *free_func_arg)</span><br><span class="line">&#123;</span><br><span class="line">  DBUG_ASSERT(free_ptr_offset % sizeof(void *) == 0);</span><br><span class="line">  compile_time_assert(sizeof(LF_PINS) == 64);</span><br><span class="line">  lf_dynarray_init(&amp;pinbox-&gt;pinarray, sizeof(LF_PINS));</span><br><span class="line">  pinbox-&gt;pinstack_top_ver= 0;</span><br><span class="line">  pinbox-&gt;pins_in_array= 0;</span><br><span class="line">  pinbox-&gt;free_ptr_offset= free_ptr_offset;</span><br><span class="line">  pinbox-&gt;free_func= free_func; </span><br><span class="line">  pinbox-&gt;free_func_arg= free_func_arg; </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br /><br /><p><a name="4ABIi"></a></p><h1 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h1><p><a name="ajxZI"></a></p><h2 id="获取pin"><a href="#获取pin" class="headerlink" title="获取pin"></a>获取pin</h2><p>lf_hash_get_pins(&amp;queries)<br /><br><br />先看一下pin的数据结构<br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br></pre></td><td class="code"><pre><span class="line">LF_PINS *lf_pinbox_get_pins(LF_PINBOX *pinbox) &#123;</span><br><span class="line">  uint32 pins, next, top_ver;</span><br><span class="line">  LF_PINS *el;</span><br><span class="line">  /*</span><br><span class="line">    We have an array of max. 64k elements.</span><br><span class="line">    The highest index currently allocated is pinbox-&gt;pins_in_array.</span><br><span class="line">    Freed elements are in a lifo stack, pinstack_top_ver.</span><br><span class="line">    pinstack_top_ver is 32 bits; 16 low bits are the index in the</span><br><span class="line">    array, to the first element of the list. 16 high bits are a version</span><br><span class="line">    (every time the 16 low bits are updated, the 16 high bits are</span><br><span class="line">    incremented). Versioning prevents the ABA problem.</span><br><span class="line">  */</span><br><span class="line">  // 先检查free stack中是否有之前别的线程还回来的</span><br><span class="line">  top_ver = pinbox-&gt;pinstack_top_ver;</span><br><span class="line">  do &#123;</span><br><span class="line">    // 因为pinbox-&gt;pinarray最多64K个元素，而pinbox-&gt;pinstack_top_ver是32bit，</span><br><span class="line">    // 所以他只有低16位是真正的stack top idx，高16位是version</span><br><span class="line">    // 所以这里给top_ver取模LF_PINBOX_MAX_PINS便可以拿到stack top idx</span><br><span class="line">    if (!(pins = top_ver % LF_PINBOX_MAX_PINS)) &#123;</span><br><span class="line">      /* the stack of free elements is empty */</span><br><span class="line">      // 如果stack top index为0代表当前free stack里没有，则扩展pinbox-&gt;pinarray</span><br><span class="line">      // 的元素个数，加1，取一个新的位置</span><br><span class="line">      pins = pinbox-&gt;pins_in_array.fetch_add(1) + 1;</span><br><span class="line">      if (unlikely(pins &gt;= LF_PINBOX_MAX_PINS)) &#123;</span><br><span class="line">        return 0;</span><br><span class="line">      &#125;</span><br><span class="line">      /*</span><br><span class="line">        note that the first allocated element has index 1 (pins==1).</span><br><span class="line">        index 0 is reserved to mean &quot;NULL pointer&quot;</span><br><span class="line">      */</span><br><span class="line">      拿到新位置对应在pinbox-&gt;pins_in_array中的元素地址</span><br><span class="line">      el = (LF_PINS *)lf_dynarray_lvalue(&amp;pinbox-&gt;pinarray, pins);</span><br><span class="line">      if (unlikely(!el)) &#123;</span><br><span class="line">        return 0;</span><br><span class="line">      &#125;</span><br><span class="line">      break;</span><br><span class="line">    &#125;</span><br><span class="line">    // 如果free stack里有元素，则弹出top位置的元素</span><br><span class="line">    el = (LF_PINS *)lf_dynarray_value(&amp;pinbox-&gt;pinarray, pins);</span><br><span class="line">    next = el-&gt;link;</span><br><span class="line">    // CAS更新pinbox-&gt;pinstack_top_ver，后面+LF_PINBOX_MAX_PINS是为了</span><br><span class="line">    // 更新前16位的version,这样，每次修改top位置之后，其version都会加1，然后</span><br><span class="line">    // 在CAS中同时比较top idx和version来避免ABA问题</span><br><span class="line">  &#125; while (!atomic_compare_exchange_strong(</span><br><span class="line">      &amp;pinbox-&gt;pinstack_top_ver, &amp;top_ver,</span><br><span class="line">      top_ver - pins + next + LF_PINBOX_MAX_PINS));</span><br><span class="line">  /*</span><br><span class="line">    set el-&gt;link to the index of el in the dynarray (el-&gt;link has two usages:</span><br><span class="line">    - if element is allocated, it&#x27;s its own index</span><br><span class="line">    - if element is free, it&#x27;s its next element in the free stack</span><br><span class="line">  */</span><br><span class="line">  // 当LF_PINS分配出去的时候，其link指向的是自己在pinbox-&gt;pins_in_array</span><br><span class="line">  // 中的index</span><br><span class="line">  // 当LF_PINS被还回来push到free stack中时，其link指向的是next element</span><br><span class="line">  el-&gt;link = pins;</span><br><span class="line">  el-&gt;purgatory_count = 0;</span><br><span class="line">  el-&gt;pinbox = pinbox;</span><br><span class="line">  return el;</span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/tools/lf_hash/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/tools/lf_hash/"/>
    <published>2019-08-02T11:42:57.000Z</published>
    <summary>MySQL 无锁 hash LF_HASH 源码笔记：LF_DYNARRAY、LF_ALLOCATOR 与 hazard pointer pin 机制分析</summary>
    <title>MySQL 源码解读 -- 无锁hash</title>
    <updated>2026-06-09T08:46:25.958Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 工具之DBUG_EXECUTE_IF","description":"MySQL 源码解读 -- 工具之DBUG_EXECUTE_IF","image":"https://ilongda.com/img/my.jpg","wordCount":283,"datePublished":"2019-08-01T11:42:57.000Z","dateModified":"2024-02-02T13:25:04.000Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/DBUG_EXECUTE_IF/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/DBUG_EXECUTE_IF/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 工具之DBUG_EXECUTE_IF","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/tools/DBUG_EXECUTE_IF/"}]}</script><h1 id="MySQL-调试-DBUG-EXECUTE-IF简介"><a href="#MySQL-调试-DBUG-EXECUTE-IF简介" class="headerlink" title="[MySQL 调试]DBUG_EXECUTE_IF简介"></a>[MySQL 调试]DBUG_EXECUTE_IF简介</h1><p>DEBUG_EXECUTE_IF主要用于当设置了某个关键字key时，执行后面的代码，<br /><br>可以简单的表示为：<br /><br>DEBUG_EXECUTE_IF(key,  code)<br /><br>例如在open_and_lock_tables函数中.<br /><br><code>&lt;br /&gt;5527 if (open_tables(thd, &amp;tables, &amp;counter, flags, prelocking_strategy))&lt;br /&gt;5528 goto err;&lt;br /&gt;5529&lt;br /&gt;5530 DBUG_EXECUTE_IF(&quot;sleep_open_and_lock_after_open&quot;, {&lt;br /&gt;5531 const char *old_proc_info= thd-&gt;proc_info;&lt;br /&gt;5532 thd-&gt;proc_info= &quot;DBUG sleep&quot;;&lt;br /&gt;5533 my_sleep(6000000);&lt;br /&gt;5534 thd-&gt;proc_info= old_proc_info;});&lt;br /&gt;5535&lt;br /&gt;5536 if (lock_tables(thd, tables, counter, flags))&lt;br /&gt;5537 goto err;&lt;br /&gt; </code><br /><br>那么如何让其生效呢，执行如下语句即可：<br /><br>set session debug&#x3D;”+d, sleep_open_and_lock_after_open”<br /> <br><br /><br>这时候当进入这个函数时，在执行完open_tables语句后，就会设置thd的状态为DBUG sleep，再sleep 6秒钟，然后再调用lock_tables函数<br /> <br />—————-<br />另外我们在写test case时，也可能用到用户层的锁，使用SQL FUNCTION的方法实现：<br />GET_LOCK(str, timeout)<br />—尝试获取一个名为str的锁，等待timeout，返回1表示获取锁成功<br />IS_FREE_LOCK(str)<br />—检查名为str的锁是否已经被释放<br />IS_USED_LOCK(str)<br />—检查名为str的锁是否已经被占用，如果是，返回占有该锁的线程ID，否则返回NULL<br />RELEASE_LOCK(str)<br />—释放名为str的锁</p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/tools/DBUG_EXECUTE_IF/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/tools/DBUG_EXECUTE_IF/"/>
    <published>2019-08-01T11:42:57.000Z</published>
    <summary>MySQL 源码解读 -- 工具之DBUG_EXECUTE_IF</summary>
    <title>MySQL 源码解读 -- 工具之DBUG_EXECUTE_IF</title>
    <updated>2024-02-02T13:25:04.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 --  存储引擎","description":"MySQL 存储引擎索引：目前收录 InnoDB 与 performance_schema 相关源码阅读笔记，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":2,"datePublished":"2019-07-29T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.958Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 --  存储引擎","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/storage/innodb/">innodb</a></br></li><li><a href="/knowledge/mysql/source_code_reading/storage/performance_schema/">performance_schema</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/storage/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/storage/index/"/>
    <published>2019-07-29T11:42:57.000Z</published>
    <summary>MySQL 存储引擎索引：目前收录 InnoDB 与 performance_schema 相关源码阅读笔记，更多细节与示例见正文。</summary>
    <title>MySQL 源码解读 --  存储引擎</title>
    <updated>2026-06-09T08:46:25.958Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 事务结构","description":"MySQL 事务结构源码笔记：Transaction_state、隔离级别枚举与 Attachable_trx 可附加事务封装机制","image":"https://ilongda.com/img/my.jpg","wordCount":616,"datePublished":"2019-07-29T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.958Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/transaction_structure/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/transaction_structure/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 事务结构","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/transaction_structure/"}]}</script><h2 id="basic-structure"><a href="#basic-structure" class="headerlink" title="basic structure"></a>basic structure</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">enum enum_tx_isolation : int &#123;</span><br><span class="line">  ISO_READ_UNCOMMITTED,</span><br><span class="line">  ISO_READ_COMMITTED,</span><br><span class="line">  ISO_REPEATABLE_READ,</span><br><span class="line">  ISO_SERIALIZABLE</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h2 id="Attachable-trx"><a href="#Attachable-trx" class="headerlink" title="Attachable_trx"></a>Attachable_trx</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">class Attachable_trx &#123;</span><br><span class="line">    THD *m_thd;</span><br><span class="line">    enum_reset_lex m_reset_lex; //RESET_LEX, DO_NOT_RESET_LEX </span><br><span class="line">    Attachable_trx *m_prev_attachable_trx;</span><br><span class="line">    Transaction_state m_trx_state; /// Transaction state data.</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="Transaction-state"><a href="#Transaction-state" class="headerlink" title="Transaction_state"></a>Transaction_state</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">struct Transaction_state &#123;</span><br><span class="line">    Transaction_state();</span><br><span class="line">    THD::Transaction_state::Transaction_state()</span><br><span class="line">    : m_query_tables_list(new Query_tables_list()),</span><br><span class="line">      m_ha_data(PSI_NOT_INSTRUMENTED, m_ha_data.initial_capacity) &#123;&#125;</span><br><span class="line">    ~Transaction_state();</span><br><span class="line">    void backup(THD *thd);</span><br><span class="line">    void restore(THD *thd);</span><br><span class="line"></span><br><span class="line">    /// SQL-command.</span><br><span class="line">    enum_sql_command m_sql_command;</span><br><span class="line"></span><br><span class="line">    Query_tables_list *m_query_tables_list;</span><br><span class="line"></span><br><span class="line">    /// Open-tables state.</span><br><span class="line">    Open_tables_backup m_open_tables_state;</span><br><span class="line"></span><br><span class="line">    /// SQL_MODE.</span><br><span class="line">    sql_mode_t m_sql_mode;</span><br><span class="line"></span><br><span class="line">    /// Transaction isolation level. RU/RC/RR/Serialization</span><br><span class="line">    enum_tx_isolation m_tx_isolation;</span><br><span class="line"></span><br><span class="line">    /// Ha_data array.</span><br><span class="line">    Prealloced_array&lt;Ha_data, PREALLOC_NUM_HA&gt; m_ha_data;</span><br><span class="line"></span><br><span class="line">    /// Transaction_ctx instance.</span><br><span class="line">    Transaction_ctx *m_trx;</span><br><span class="line"></span><br><span class="line">    /// Transaction read-only state.</span><br><span class="line">    bool m_tx_read_only;</span><br><span class="line"></span><br><span class="line">    /// THD options.</span><br><span class="line">    ulonglong m_thd_option_bits;</span><br><span class="line"></span><br><span class="line">    /// Current transaction instrumentation.</span><br><span class="line">    PSI_transaction_locker *m_transaction_psi;</span><br><span class="line"></span><br><span class="line">    /// Server status flags.</span><br><span class="line">    uint m_server_status;</span><br><span class="line"></span><br><span class="line">    /// THD::in_lock_tables value.</span><br><span class="line">    bool m_in_lock_tables;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">      Current time zone (i.e. @@session.time_zone) usage indicator.</span><br><span class="line"></span><br><span class="line">      Saving it allows data-dictionary code to read timestamp values</span><br><span class="line">      as datetimes from system tables without disturbing user&#x27;s statement.</span><br><span class="line">      TODO: We need to change DD code not to use @@session.time_zone at all and</span><br><span class="line">      stick to UTC for internal storage of timestamps in DD objects.</span><br><span class="line">    */</span><br><span class="line">    bool m_time_zone_used;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">      Transaction rollback request flag.</span><br><span class="line"></span><br><span class="line">      InnoDB can try to access table definition while rolling back regular</span><br><span class="line">      transaction. So we need to be able to start attachable transaction</span><br><span class="line">      without being affected by, and affecting, the rollback state of regular</span><br><span class="line">      transaction.</span><br><span class="line">    */</span><br><span class="line">    bool m_transaction_rollback_request;</span><br><span class="line"></span><br><span class="line">    PPI_transaction *m_ppi_transaction;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Transaction-ctx"><a href="#Transaction-ctx" class="headerlink" title="Transaction_ctx"></a>Transaction_ctx</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">class Transaction_ctx &#123;</span><br><span class="line">    SAVEPOINT *m_savepoints;</span><br><span class="line">    THD_TRANS m_scope_info[2];</span><br><span class="line"></span><br><span class="line">  XID_STATE m_xid_state;</span><br><span class="line"></span><br><span class="line">  MEM_ROOT m_mem_root;  // Transaction-life memory allocation pool</span><br><span class="line"></span><br><span class="line">  struct &#123;</span><br><span class="line">    bool enabled;      // see ha_enable_transaction()</span><br><span class="line">    bool xid_written;  // The session wrote an XID</span><br><span class="line">    bool real_commit;  // Is this a &quot;real&quot; commit?</span><br><span class="line">    bool commit_low;   // see MYSQL_BIN_LOG::ordered_commit</span><br><span class="line">    bool run_hooks;    // Call the after_commit hook</span><br><span class="line">#ifndef DBUG_OFF</span><br><span class="line">    bool ready_preempt;  // internal in MYSQL_BIN_LOG::ordered_commit</span><br><span class="line">#endif</span><br><span class="line">  &#125; m_flags;</span><br><span class="line">  /* Binlog-specific logical timestamps. */</span><br><span class="line">  /*</span><br><span class="line">    Store for the transaction&#x27;s commit parent sequence_number.</span><br><span class="line">    The value specifies this transaction dependency with a &quot;parent&quot;</span><br><span class="line">    transaction.</span><br><span class="line">    The member is assigned, when the transaction is about to commit</span><br><span class="line">    in binlog to a value of the last committed transaction&#x27;s sequence_number.</span><br><span class="line">    This and last_committed as numbers are kept ever incremented</span><br><span class="line">    regardless of binary logs being rotated or when transaction</span><br><span class="line">    is logged in multiple pieces.</span><br><span class="line">    However the logger to the binary log may convert them</span><br><span class="line">    according to its specification.</span><br><span class="line">  */</span><br><span class="line">  int64 last_committed;</span><br><span class="line">  /*</span><br><span class="line">    The transaction&#x27;s private logical timestamp assigned at the</span><br><span class="line">    transaction prepare phase. The timestamp enumerates transactions</span><br><span class="line">    in the binary log. The value is gained through incrementing (stepping) a</span><br><span class="line">    global clock.</span><br><span class="line">    Eventually the value is considered to increase max_committed_transaction</span><br><span class="line">    system clock when the transaction has committed.</span><br><span class="line">  */</span><br><span class="line">  int64 sequence_number;</span><br><span class="line"></span><br><span class="line">  Rpl_transaction_ctx m_rpl_transaction_ctx;</span><br><span class="line">  Rpl_transaction_write_set_ctx m_transaction_write_set_ctx;</span><br><span class="line">  bool trans_begin_hook_invoked;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Transaction-ro"><a href="#Transaction-ro" class="headerlink" title="Transaction_ro"></a>Transaction_ro</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">class Transaction_ro &#123;</span><br><span class="line"> public:</span><br><span class="line">  Transaction_ro(THD *thd, enum_tx_isolation isolation)</span><br><span class="line">      : otx(thd, TL_READ), m_thd(thd), m_kill_immunizer(thd) &#123;</span><br><span class="line">    thd-&gt;begin_attachable_ro_transaction();</span><br><span class="line">    thd-&gt;tx_isolation = isolation;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  ~Transaction_ro() &#123; m_thd-&gt;end_attachable_transaction(); &#125;</span><br><span class="line"></span><br><span class="line">  Open_dictionary_tables_ctx otx;</span><br><span class="line"></span><br><span class="line"> private:</span><br><span class="line">  THD *m_thd;</span><br><span class="line"></span><br><span class="line">  DD_kill_immunizer m_kill_immunizer;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">class Open_dictionary_tables_ctx &#123;</span><br><span class="line">  THD *m_thd;</span><br><span class="line">  thr_lock_type m_lock_type;</span><br><span class="line">  bool m_ignore_global_read_lock;</span><br><span class="line">  typedef std::map&lt;String_type, Raw_table *&gt; Object_table_map;</span><br><span class="line">  Object_table_map m_tables;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/transaction_structure/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/transaction_structure/"/>
    <published>2019-07-29T11:42:57.000Z</published>
    <summary>MySQL 事务结构源码笔记：Transaction_state、隔离级别枚举与 Attachable_trx 可附加事务封装机制</summary>
    <title>MySQL 源码解读 -- 事务结构</title>
    <updated>2026-06-09T08:46:25.958Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- innodb 架构","description":"InnoDB 存储引擎架构笔记：梳理 innodb 各子模块架构设计与组件关系，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://cdn.nlark.com/yuque/0/2020/png/106206/1600764293775-6625f49f-40fc-4ed0-bfe1-763be8d6ca72.png#align=left&display=inline&height=168&margin=%5Bobject%20Object%5D&name=image.png&originHeight=335&originWidth=362&size=58284&status=done&style=none&width=181","wordCount":1,"datePublished":"2019-07-28T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.958Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/architecture/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/architecture/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- innodb 架构","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/architecture/"}]}</script><h1 id="architech"><a href="#architech" class="headerlink" title="architech"></a>architech</h1><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1600764293775-6625f49f-40fc-4ed0-bfe1-763be8d6ca72.png#align=left&display=inline&height=168&margin=%5Bobject%20Object%5D&name=image.png&originHeight=335&originWidth=362&size=58284&status=done&style=none&width=181" alt="image.png"></p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/architecture/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/architecture/"/>
    <published>2019-07-28T11:42:57.000Z</published>
    <summary>InnoDB 存储引擎架构笔记：梳理 innodb 各子模块架构设计与组件关系，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>MySQL 源码解读 -- innodb 架构</title>
    <updated>2026-06-09T08:46:25.958Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- innodb","description":"InnoDB 专题索引：汇总 InnoDB 架构设计与事务结构 Transaction_state/Attachable_trx 等源码笔记","image":"https://ilongda.com/img/my.jpg","wordCount":2,"datePublished":"2019-07-28T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.958Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- innodb","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/storage/innodb/architecture.html">architecture</a></br></li><li><a href="/knowledge/mysql/source_code_reading/storage/innodb/transaction_structure.html">transaction_structure</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/storage/innodb/index/"/>
    <published>2019-07-28T11:42:57.000Z</published>
    <summary>InnoDB 专题索引：汇总 InnoDB 架构设计与事务结构 Transaction_state/Attachable_trx 等源码笔记</summary>
    <title>MySQL 源码解读 -- innodb</title>
    <updated>2026-06-09T08:46:25.958Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 连接管理","description":"MySQL 连接管理源码笔记：create_new_thread、thread_cache 复用与 handle_one_connection 登录验证处理流程","image":"https://ilongda.com/img/my.jpg","wordCount":769,"datePublished":"2019-07-27T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.956Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/connection/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/connection/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 连接管理","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/connection/"}]}</script><h1 id="连接处理"><a href="#连接处理" class="headerlink" title="连接处理"></a>连接处理</h1><p><a name="dSoQZ"></a></p><h2 id="创建连接"><a href="#创建连接" class="headerlink" title="创建连接"></a>创建连接</h2><p>主要代码在sql&#x2F;mysqld.cc中(create_new_thread&#x2F;create_thread_to_handle_connection)，精简后的代码如下：<br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">static void create_new_thread(THD *thd) &#123;</span><br><span class="line">  NET *net=&amp;thd-&gt;net;</span><br><span class="line">  if (connection_count &gt;= max_connections + 1 || abort_loop) &#123; // 看看当前连接数是不是超过了系统配置允许的最大值，如果是就断开连接。</span><br><span class="line">    close_connection(thd, ER_CON_COUNT_ERROR, 1);</span><br><span class="line">    delete thd;</span><br><span class="line">  &#125;</span><br><span class="line">  ++connection_count;</span><br><span class="line">  thread_scheduler.add_connection(thd); // 将新连接加入到thread_scheduler的连接队列中。</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a name="JOZPA"></a></p><h2 id="创建连接处理线程"><a href="#创建连接处理线程" class="headerlink" title="创建连接处理线程"></a>创建连接处理线程</h2><p>而thread_scheduler转而调用create_thread_to_handle_connection,精简后的代码如下：<br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">void create_thread_to_handle_connection(THD *thd) &#123;</span><br><span class="line">  if (cached_thread_count &gt; wake_thread) &#123; //看当前工作线程缓存(thread_cache)中有否空余的线程</span><br><span class="line">    thread_cache.append(thd);</span><br><span class="line">    pthread_cond_signal(&amp;COND_thread_cache); // 有的话则唤醒一个线程来用</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">    threads.append(thd);</span><br><span class="line">    pthread_create(&amp;thd-&gt;real_id,&amp;connection_attrib,   handle_one_connection,   (void*) thd))); //没有可用空闲线程则创建一个新的线程</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br /><p><a name="HB1Ax"></a></p><h2 id="连接处理-1"><a href="#连接处理-1" class="headerlink" title="连接处理"></a>连接处理</h2><p>连接使用handle_one_connection函数,精简代码如下<br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><span class="line">pthread_handler_t handle_one_connection(void *arg)</span><br><span class="line">&#123;</span><br><span class="line">thread_scheduler.init_new_connection_thread(); // 初始化线程预处理操作</span><br><span class="line">  </span><br><span class="line">setup_connection_thread_globals(thd); //载入一些Session级变量</span><br><span class="line">     </span><br><span class="line">  for (;;) &#123; </span><br><span class="line">          lex_start(thd); //初始化LEX词法解析器</span><br><span class="line">          login_connection(thd); // 进行连接身份验证</span><br><span class="line">          prepare_new_connection_state(thd); // 初始化线程Status,即show status看到的</span><br><span class="line">          do_command(thd); // 处理命令</span><br><span class="line">          end_connection(thd); //没事做了关闭连接,丢入线程池</span><br><span class="line">     </span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">void do_handle_one_connection(THD *thd_arg)</span><br><span class="line">&#123;</span><br><span class="line">  THD *thd= thd_arg;</span><br><span class="line"></span><br><span class="line">  thd-&gt;thr_create_utime= my_micro_time();  // 更新thd 时间</span><br><span class="line"></span><br><span class="line">   //// 初始化线程预处理操作</span><br><span class="line">  if (MYSQL_CALLBACK_ELSE(thd-&gt;scheduler, init_new_connection_thread, (), 0))</span><br><span class="line">  &#123;</span><br><span class="line">    close_connection(thd, ER_OUT_OF_RESOURCES);</span><br><span class="line">    statistic_increment(aborted_connects,&amp;LOCK_status);</span><br><span class="line">    MYSQL_CALLBACK(thd-&gt;scheduler, end_thread, (thd, 0));</span><br><span class="line">    return;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  /*</span><br><span class="line">    If a thread was created to handle this connection:</span><br><span class="line">    increment slow_launch_threads counter if it took more than</span><br><span class="line">    slow_launch_time seconds to create the thread.</span><br><span class="line">  */</span><br><span class="line">  if (thd-&gt;prior_thr_create_utime)</span><br><span class="line">  &#123;</span><br><span class="line">    ulong launch_time= (ulong) (thd-&gt;thr_create_utime -</span><br><span class="line">                                thd-&gt;prior_thr_create_utime);</span><br><span class="line">    if (launch_time &gt;= slow_launch_time*1000000L)</span><br><span class="line">      statistic_increment(slow_launch_threads, &amp;LOCK_status);</span><br><span class="line">    thd-&gt;prior_thr_create_utime= 0;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  /*</span><br><span class="line">    handle_one_connection() is normally the only way a thread would</span><br><span class="line">    start and would always be on the very high end of the stack ,</span><br><span class="line">    therefore, the thread stack always starts at the address of the</span><br><span class="line">    first local variable of handle_one_connection, which is thd. We</span><br><span class="line">    need to know the start of the stack so that we could check for</span><br><span class="line">    stack overruns.</span><br><span class="line">  */</span><br><span class="line">  thd-&gt;thread_stack= (char*) &amp;thd;</span><br><span class="line">  if (setup_connection_thread_globals(thd))</span><br><span class="line">    return;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  for (;;)</span><br><span class="line">  &#123;</span><br><span class="line">    bool rc;</span><br><span class="line"></span><br><span class="line">    thd-&gt;ppi_thread= PPI_THREAD_CALL(create_thread)();</span><br><span class="line">    thd-&gt;ppi_transaction=</span><br><span class="line">      PPI_THREAD_CALL(get_transaction_context)(thd-&gt;ppi_thread);</span><br><span class="line"></span><br><span class="line">    NET *net= &amp;thd-&gt;net;</span><br><span class="line">    mysql_socket_set_thread_owner(net-&gt;vio-&gt;mysql_socket);</span><br><span class="line"></span><br><span class="line">    //// 准备connection</span><br><span class="line">    rc= thd_prepare_connection(thd);</span><br><span class="line">    if (rc)</span><br><span class="line">      goto end_thread;</span><br><span class="line"></span><br><span class="line">    while (thd_is_connection_alive(thd))</span><br><span class="line">    &#123;</span><br><span class="line">      mysql_audit_release(thd);</span><br><span class="line">      if (do_command(thd))</span><br><span class="line">  break;</span><br><span class="line">    &#125;</span><br><span class="line">    end_connection(thd);</span><br><span class="line"></span><br><span class="line">end_thread:</span><br><span class="line">    close_connection(thd);</span><br><span class="line">    if (MYSQL_CALLBACK_ELSE(thd-&gt;scheduler, end_thread, (thd, 1), 0))</span><br><span class="line">      return;                                 // Probably no-threads</span><br><span class="line"></span><br><span class="line">    /*</span><br><span class="line">      If end_thread() returns, we are either running with</span><br><span class="line">      thread-handler=no-threads or this thread has been schedule to</span><br><span class="line">      handle the next connection.</span><br><span class="line">    */</span><br><span class="line">    thd= current_thd;</span><br><span class="line">    thd-&gt;thread_stack= (char*) &amp;thd;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>thd_prepare_connection</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">bool thd_prepare_connection(THD *thd)</span><br><span class="line">&#123;</span><br><span class="line">  bool rc;</span><br><span class="line">  lex_start(thd);                //初始化LEX词法解析器</span><br><span class="line">  rc= login_connection(thd);    //</span><br><span class="line">  if (rc)</span><br><span class="line">    return rc;</span><br><span class="line"></span><br><span class="line">  MYSQL_CONNECTION_START(thd-&gt;thread_id, &amp;thd-&gt;security_ctx-&gt;priv_user[0],</span><br><span class="line">                         (char *) thd-&gt;security_ctx-&gt;host_or_ip);</span><br><span class="line"></span><br><span class="line">  //關鍵這個函數</span><br><span class="line">  prepare_new_connection_state(thd);</span><br><span class="line">  return FALSE;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">login_connection</span><br><span class="line">    -- check_connection  -- 设置vio, 检查main_security_ctx, vio_keepalive, 申请thd-&gt;packet 内存, acl_authenticate</span><br><span class="line">    -- autit log 开始</span><br><span class="line">    -- thd-&gt;protocol-&gt;end_statement(); lex_end</span><br><span class="line">    </span><br><span class="line">// 初始化session的status</span><br><span class="line">prepare_new_connection_state</span><br><span class="line">    -- </span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/server/connection/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/server/connection/"/>
    <published>2019-07-27T11:42:57.000Z</published>
    <summary>MySQL 连接管理源码笔记：create_new_thread、thread_cache 复用与 handle_one_connection 登录验证处理流程</summary>
    <title>MySQL 源码解读 -- 连接管理</title>
    <updated>2026-06-09T08:46:25.956Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- Server 层","description":"MySQL Server 层专题索引：连接管理、DD、插入、查询解析、线程池与调度器等模块阅读笔记汇总，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":5,"datePublished":"2019-07-27T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.957Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- Server 层","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/server/connection.html">connection</a></br></li><li><a href="/knowledge/mysql/source_code_reading/server/dd/">dd</a></br></li><li><a href="/knowledge/mysql/source_code_reading/server/insert.html">insert</a></br></li><li><a href="/knowledge/mysql/source_code_reading/server/query/">query</a></br></li><li><a href="/knowledge/mysql/source_code_reading/server/thread_pool_and_scheduler.html">thread_pool_and_scheduler</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/server/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/server/index/"/>
    <published>2019-07-27T11:42:57.000Z</published>
    <summary>MySQL Server 层专题索引：连接管理、DD、插入、查询解析、线程池与调度器等模块阅读笔记汇总，更多细节与示例见正文。</summary>
    <title>MySQL 源码解读 -- Server 层</title>
    <updated>2026-06-09T08:46:25.957Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 插入流程","description":"MySQL INSERT 流程源码笔记：mysql_insert 到 write_record、ha_write_row 及 handler API 写行与 binlog 路径","image":"https://ilongda.com/img/my.jpg","wordCount":235,"datePublished":"2019-07-27T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.957Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/insert/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/insert/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 插入流程","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/insert/"}]}</script><h1 id="insert"><a href="#insert" class="headerlink" title="insert"></a>insert</h1><p><a name="o1mVk"></a></p><h2 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h2><p>我们看一个例子, mysql_insert (在sql&#x2F;sql_insert.cc),精简代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">bool mysql_insert(THD *thd,</span><br><span class="line">                   TABLE_LIST *table_list,      // 该INSERT要用到的表</span><br><span class="line">                   List&lt;Item&gt; &amp;fields,             // 使用的项</span><br><span class="line">                   ....) &#123;</span><br><span class="line">   </span><br><span class="line">    open_and_lock_tables(thd, table_list); // 这里的锁只是防止表结构修改</span><br><span class="line"></span><br><span class="line">    mysql_prepare_insert(...);</span><br><span class="line"></span><br><span class="line">    foreach value in values_list &#123;</span><br><span class="line">            write_record(...);</span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125; //里面还有很多处理trigger，错误，view之类的杂七杂八的东西，我们都忽略。</span><br></pre></td></tr></table></figure><br /><p><a name="lCZAp"></a></p><h2 id="insert-record"><a href="#insert-record" class="headerlink" title="insert_record"></a>insert_record</h2><p>我们接着看真正写数据的函数write_record (在sql&#x2F;sql_insert.cc),精简代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">int write_record(THD thd, TABLE table,COPY_INFO *info) &#123;  // 写数据记录</span><br><span class="line">    if (info-&gt;handle_duplicates == DUP_REPLACE || info-&gt;handle_duplicates == DUP_UPDATE) &#123; //如果是REPLACE或UPDATE则替换数据</span><br><span class="line">    table-&gt;file-&gt;ha_write_row(table-&gt;record[0]);</span><br><span class="line">    table-&gt;file-&gt;ha_update_row(table-&gt;record[1], table-&gt;record[0]));</span><br><span class="line">    &#125; else &#123;</span><br><span class="line">    table-&gt;file-&gt;ha_write_row(table-&gt;record[0]);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">    </span><br><span class="line">int handler::ha_write_row(uchar *buf) &#123; //这是啥? Handler API !</span><br><span class="line">    write_row(buf);   // 调用具体的实现</span><br><span class="line">    binlog_log_row(table, 0, buf, log_func));  // 写binlog</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/server/insert/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/server/insert/"/>
    <published>2019-07-27T11:42:57.000Z</published>
    <summary>MySQL INSERT 流程源码笔记：mysql_insert 到 write_record、ha_write_row 及 handler API 写行与 binlog 路径</summary>
    <title>MySQL 源码解读 -- 插入流程</title>
    <updated>2026-06-09T08:46:25.957Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 调度器","description":"MySQL 调度器与 Event 源码笔记：Event_scheduler、Event_queue 优先级队列与定时任务加载启动机制","image":"https://ilongda.com/assets/event_scheduler_start.jpg","wordCount":210,"datePublished":"2019-07-27T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.957Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/thread_pool_and_scheduler/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/thread_pool_and_scheduler/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 调度器","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/thread_pool_and_scheduler/"}]}</script><h1 id="Thread-pool-scheduler"><a href="#Thread-pool-scheduler" class="headerlink" title="Thread pool &amp; scheduler"></a>Thread pool &amp; scheduler</h1><h1 id="init"><a href="#init" class="headerlink" title="init"></a>init</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Event:init</span><br><span class="line">  --&gt; thd = new THD(); thd-&gt; store_globals();  // </span><br><span class="line">  --&gt; event_queue = new Event_queue</span><br><span class="line">  --&gt; scheduler = new Event_scheduler(event_queue)</span><br><span class="line">  --&gt; event_queue-&gt;init_queue() || load_events_from_db(thd, event_queue)</span><br><span class="line">  --&gt; scheduler-&gt;start(&amp;err_no) </span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>启动调度器  – Event_scheduler::start<br><img data-src="/assets/event_scheduler_start.jpg" alt="event_scheduler_start"></p><p>Event_scheduler::start  —&gt; 创建thd 和一个线程<br />event_scheduler_thread<br><img data-src="/assets/event_scheduler_thread.jpg" alt="event_scheduler_thread"></p><h1 id="Event-queue"><a href="#Event-queue" class="headerlink" title="Event_queue"></a>Event_queue</h1><p>Event_queue 核心类, </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">/* The sorted queue with the Event_queue_element objects */</span><br><span class="line">  Priority_queue&lt;Event_queue_element *,</span><br><span class="line">                 std::vector&lt;Event_queue_element *,</span><br><span class="line">                             Malloc_allocator&lt;Event_queue_element *&gt;&gt;,</span><br><span class="line">                 Event_queue_less&gt;</span><br><span class="line">      queue;</span><br><span class="line"></span><br><span class="line">使用</span><br><span class="line">queue(Event_queue_less(),</span><br><span class="line">            Malloc_allocator&lt;Event_queue_element *&gt;(</span><br><span class="line">                key_memory_Event_scheduler_scheduler_param))</span><br><span class="line"></span><br><span class="line">Priority_queue 定义</span><br><span class="line">template &lt;typename T, typename Container = std::vector&lt;T&gt;,</span><br><span class="line">          typename Less = std::less&lt;typename Container::value_type&gt;&gt;</span><br><span class="line">class Priority_queue : public Less &#123;</span><br><span class="line">...</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">构造函数</span><br><span class="line">Priority_queue(Less const &amp;less = Less(),</span><br><span class="line">                 const allocator_type &amp;alloc = allocator_type())</span><br><span class="line">      : Base(less), m_container(alloc) &#123;&#125;</span><br><span class="line"></span><br><span class="line">插入event</span><br><span class="line">bool Event_queue::create_event(THD *thd, Event_queue_element *new_element,</span><br><span class="line">                               bool *created) </span><br><span class="line">&#123;</span><br><span class="line">    new_element-&gt;compute_next_execution_time(thd);</span><br><span class="line">    *created = (queue.push(new_element) == false);</span><br><span class="line">    mysql_cond_broadcast(&amp;COND_queue_state);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="Event-scheduler"><a href="#Event-scheduler" class="headerlink" title="Event_scheduler"></a>Event_scheduler</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">类定义</span><br><span class="line">Event_scheduler &#123;</span><br><span class="line">    enum enum_state state; //enum enum_state &#123; INITIALIZED = 0, RUNNING, STOPPING &#125;;</span><br><span class="line">    THD *scheduler_thd;</span><br><span class="line">    Event_queue *queue;</span><br><span class="line">    ulonglong started_events;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/server/thread_pool_and_scheduler/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/server/thread_pool_and_scheduler/"/>
    <published>2019-07-27T11:42:57.000Z</published>
    <summary>MySQL 调度器与 Event 源码笔记：Event_scheduler、Event_queue 优先级队列与定时任务加载启动机制</summary>
    <title>MySQL 源码解读 -- 调度器</title>
    <updated>2026-06-09T08:46:25.957Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 查询过程","description":"MySQL 查询处理流程：do_command 接收包、dispatch_command 分发 COM_QUERY 至 mysql_parse 解析执行","image":"https://ilongda.com/assets/query_flow.png","wordCount":531,"datePublished":"2019-06-28T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.957Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/query_flow/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/query_flow/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 查询过程","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/query_flow/"}]}</script><h1 id="Query-流程"><a href="#Query-流程" class="headerlink" title="Query 流程"></a>Query 流程</h1><p>转载：<a href="/knowledge/mysql/source_code_reading/server/query/query_flow.html">query_flow</a><br><img data-src="/assets/query_flow.png" alt="query_flow"><br><img data-src="/assets/query_flow2.png" alt="query_flow2"><br>本文简单介绍， 详情参考<a href="/knowledge/mysql/source_code_reading/server/query/index.html">query</a></p><span id="more"></span><h1 id="query-入口"><a href="#query-入口" class="headerlink" title="query 入口"></a>query 入口</h1><p>do_command函数在sql&#x2F;sql_parse.cc定义,代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">bool do_command(THD *thd) &#123;</span><br><span class="line">  NET *net= &amp;thd-&gt;net;</span><br><span class="line">  packet_length = my_net_read(net);</span><br><span class="line">  packet = (char*) net-&gt;read_pos;</span><br><span class="line">  command = (enum enum_server_command) (uchar) packet[0]; // 解析客户端传过来的命令类型</span><br><span class="line">  dispatch_command(command, thd, packet+1, (uint) (packet_length-1));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="dispatch"><a href="#dispatch" class="headerlink" title="dispatch"></a>dispatch</h1><p>再看dispatch_command函数在sql&#x2F;sql_parse.cc定义,精简代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">bool dispatch_command(enum enum_server_command command, THD thd, char packet, uint packet_length) &#123;</span><br><span class="line">  NET *net = &amp;thd-&gt;net;</span><br><span class="line">  thd-&gt;command = command;</span><br><span class="line">  switch (command) &#123; //判断命令类型</span><br><span class="line">    case COM_INIT_DB: ...;</span><br><span class="line">    case COM_TABLE_DUMP: ...;</span><br><span class="line">    case COM_CHANGE_USER: ...;</span><br><span class="line">    ...</span><br><span class="line">    case COM_QUERY: //如果是Query</span><br><span class="line">    alloc_query(thd, packet, packet_length); //从网络数据包中读取Query, 并扩容内存存入thd-&gt;set_query, shrink thd-&gt;packet/thd-&gt;convert_buffer, </span><br><span class="line">    mysql_parse(thd, thd-&gt;query, thd-&gt;query_length, &amp;end_of_stmt); //送去解析</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="parser"><a href="#parser" class="headerlink" title="parser"></a>parser</h1><p>mysql_parse函数负责解析SQL(sql&#x2F;sql_parse.cc), 详情可以参考<a href="/knowledge/mysql/source_code_reading/server/query/parser.html">mysql_parser</a> 精简代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">void mysql_parse(THD *thd,</span><br><span class="line">    const</span><br><span class="line">    char *inBuf,</span><br><span class="line">    uint</span><br><span class="line">    length, const</span><br><span class="line">    char ** found_semicolon) &#123;</span><br><span class="line">       </span><br><span class="line">    lex_start(thd); //初始化线程解析描述符</span><br><span class="line">      </span><br><span class="line">    if (query_cache_send_result_to_client(thd, (char*) inBuf,length) &lt;= 0) &#123; </span><br><span class="line">         // 看query cache中有否命中，有就直接返回结果，否则进行查找</span><br><span class="line">            Parser_state parser_state(thd, inBuf, length);   </span><br><span class="line">            parse_sql(thd, &amp; parser_state, NULL); // 解析SQL语句</span><br><span class="line">            mysql_execute_command(thd); // 执行</span><br><span class="line">       </span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    mysql_rewrite_query(thd);</span><br><span class="line"></span><br><span class="line">    error = mysql_execute_command(thd, true);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="query-处理"><a href="#query-处理" class="headerlink" title="query 处理"></a>query 处理</h1><p>终于开始执行mysql_execute_command接近3k行, 优化阶段和执行阶段揉在一起， 可以参考<a href="/knowledge/mysql/source_code_reading/server/query/execute.html">execute</a>非常精简代码如下:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">int mysql_execute_command(THD *thd) &#123;</span><br><span class="line">  LEX  *lex= thd-&gt;lex;  // 解析过后的SQL语句的语法结构</span><br><span class="line">  TABLE_LIST *all_tables = lex-&gt;query_tables;   // 该语句要访问的表的列表</span><br><span class="line">  switch (lex-&gt;sql_command) &#123;</span><br><span class="line">      ...</span><br><span class="line">      case SQLCOM_INSERT:</span><br><span class="line">      insert_precheck(thd, all_tables);</span><br><span class="line">      mysql_insert(thd, all_tables, lex-&gt;field_list, lex-&gt;many_values, lex-&gt;update_list, lex-&gt;value_list, lex-&gt;duplicates, lex-&gt;ignore);</span><br><span class="line">      break; ...</span><br><span class="line">      case SQLCOM_SELECT:</span><br><span class="line">      select_precheck(thd,lex, all_tables, first_table));    // 检查用户对数据表的访问权限</span><br><span class="line">      execute_sqlcom_select(thd, all_tables);     // 执行select语句</span><br><span class="line">      break;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">bool select_precheck(THD *thd, LEX *lex, TABLE_LIST *tables,</span><br><span class="line">                     TABLE_LIST *first_table)</span><br><span class="line">&#123;</span><br><span class="line">  if (tables)</span><br><span class="line">  &#123;</span><br><span class="line">    res= check_table_access(thd,</span><br><span class="line">                            privileges_requested,</span><br><span class="line">                            tables, FALSE, UINT_MAX, FALSE) ||</span><br><span class="line">         (first_table &amp;&amp; first_table-&gt;schema_table_reformed &amp;&amp;</span><br><span class="line">          check_show_access(thd, first_table));</span><br><span class="line">  &#125;</span><br><span class="line">  else</span><br><span class="line">    res= check_access(thd, privileges_requested, any_db, NULL, NULL, 0, 0);</span><br><span class="line">    </span><br><span class="line">  return res;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/query_flow/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/query_flow/"/>
    <published>2019-06-28T11:42:57.000Z</published>
    <summary>MySQL 查询处理流程：do_command 接收包、dispatch_command 分发 COM_QUERY 至 mysql_parse 解析执行</summary>
    <title>MySQL 源码解读 -- 查询过程</title>
    <updated>2026-06-09T08:46:25.957Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 查询","description":"MySQL 查询模块索引：汇总 parser 词法语法分析与 query_flow 从 do_command 到执行的全链路笔记","image":"https://ilongda.com/img/my.jpg","wordCount":2,"datePublished":"2019-06-27T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.957Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 查询","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/server/query/parser.html">parser</a></br></li><li><a href="/knowledge/mysql/source_code_reading/server/query/query_flow.html">query_flow</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/index/"/>
    <published>2019-06-27T11:42:57.000Z</published>
    <summary>MySQL 查询模块索引：汇总 parser 词法语法分析与 query_flow 从 do_command 到执行的全链路笔记</summary>
    <title>MySQL 源码解读 -- 查询</title>
    <updated>2026-06-09T08:46:25.957Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- parser","description":"MySQL Parser 源码结构：Parser_state 含 Lex_input_stream 与 Yacc_state，控制词法与语法解析行为","image":"https://ilongda.com/img/my.jpg","wordCount":59,"datePublished":"2019-06-27T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.957Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/parser/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/parser/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- parser","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/parser/"}]}</script><h2 id="structure"><a href="#structure" class="headerlink" title="structure"></a>structure</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">//Internal state of the parser.</span><br><span class="line">class Parser_state &#123;</span><br><span class="line">  Parser_input m_input;  //control the parser behavior</span><br><span class="line">  Lex_input_stream m_lip; //state data used during lexical parsing</span><br><span class="line">  Yacc_state m_yacc; //state data used during syntactic parsing.</span><br><span class="line">  bool m_comment;  ///&lt; True if current query contains comments</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">struct Parser_input &#123;</span><br><span class="line">  bool m_compute_digest;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/parser/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/server/query/parser/"/>
    <published>2019-06-27T11:42:57.000Z</published>
    <summary>MySQL Parser 源码结构：Parser_state 含 Lex_input_stream 与 Yacc_state，控制词法与语法解析行为</summary>
    <title>MySQL 源码解读 -- parser</title>
    <updated>2026-06-09T08:46:25.957Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- Dictionary_client","description":"MySQL 源码解读 -- Dictionary_client","image":"https://ilongda.com/img/my.jpg","wordCount":132,"datePublished":"2019-06-26T11:42:57.000Z","dateModified":"2024-02-02T13:22:57.898Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/dd/Dictionary_client/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/dd/Dictionary_client/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- Dictionary_client","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/dd/Dictionary_client/"}]}</script><h1 id="结构介绍"><a href="#结构介绍" class="headerlink" title="结构介绍"></a>结构介绍</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">Dictionary_client &#123;</span><br><span class="line">  class Auto_releaser; // 子类</span><br><span class="line">  std::vector&lt;Entity_object *&gt; m_uncached_objects;  // Objects to be deleted.</span><br><span class="line">  Object_registry m_registry_committed;    // Registry of committed objects.</span><br><span class="line">  Object_registry m_registry_uncommitted;  // Registry of uncommitted objects.</span><br><span class="line">  Object_registry m_registry_dropped;      // Registry of dropped objects.</span><br><span class="line">  THD *m_thd;                        // Thread context, needed for cache misses.</span><br><span class="line">  Auto_releaser m_default_releaser;  // Default auto releaser.</span><br><span class="line">  Auto_releaser *m_current_releaser;  // Current auto releaser.</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">Auto_releaser &#123;</span><br><span class="line">    Dictionary_client *m_client;</span><br><span class="line">    Object_registry m_release_registry;</span><br><span class="line">    Auto_releaser *m_prev;</span><br><span class="line"></span><br><span class="line">    void auto_release(Cache_element&lt;T&gt; *element) //Register an object to be auto released.</span><br><span class="line">    void transfer_release(const T *object); //Transfer an object from the current to the previous auto releaser.</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">Dictionary_client::Auto_releaser::Auto_releaser(Dictionary_client *client)</span><br><span class="line">    : m_client(client), m_prev(client-&gt;m_current_releaser) &#123;</span><br><span class="line">  m_client-&gt;m_current_releaser = this;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/server/dd/Dictionary_client/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/server/dd/Dictionary_client/"/>
    <published>2019-06-26T11:42:57.000Z</published>
    <summary>MySQL 源码解读 -- Dictionary_client</summary>
    <title>MySQL 源码解读 -- Dictionary_client</title>
    <updated>2024-02-02T13:22:57.898Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- DD","description":"MySQL 数据字典 DD 模块索引：指向 Dictionary_client 相关源码阅读笔记，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":1,"datePublished":"2019-06-26T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.957Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/dd/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/dd/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- DD","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/server/dd/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/server/dd/Dictionary_client.html">Dictionary_client</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/server/dd/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/server/dd/index/"/>
    <published>2019-06-26T11:42:57.000Z</published>
    <summary>MySQL 数据字典 DD 模块索引：指向 Dictionary_client 相关源码阅读笔记，更多细节与示例见正文。</summary>
    <title>MySQL 源码解读 -- DD</title>
    <updated>2026-06-09T08:46:25.957Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 网络","description":"MySQL 源码网络模块索引：指向 workflow 笔记，介绍监听、accept 与 THD 创建的网络处理流程，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":1,"datePublished":"2019-06-25T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.956Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/network/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/network/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 网络","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/network/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/network/workflow.html">workflow</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/network/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/network/index/"/>
    <published>2019-06-25T11:42:57.000Z</published>
    <summary>MySQL 源码网络模块索引：指向 workflow 笔记，介绍监听、accept 与 THD 创建的网络处理流程，更多细节与示例见正文。</summary>
    <title>MySQL 源码解读 -- 网络</title>
    <updated>2026-06-09T08:46:25.956Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 网络","description":"MySQL 网络处理流程源码笔记：handle_connections_sockets 监听、my_net_init 与 do_command 命令分发链路","image":"https://ilongda.com/img/my.jpg","wordCount":253,"datePublished":"2019-06-25T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.956Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/network/workflow/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/network/workflow/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 网络","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/network/workflow/"}]}</script><h1 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h1><p>以下为老的代码，并非最新代码， 比如并没有使用thread_pool<br><a name="ocfQE"></a></p><h2 id="网络监听"><a href="#网络监听" class="headerlink" title="网络监听"></a>网络监听</h2><p>主要代码在sql&#x2F;mysqld.cc中(handle_connections_sockets)，精简后的代码如下：<br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">pthread_handler_t handle_connections_sockets(void *arg attribute((unused))) &#123;</span><br><span class="line">  FD_SET(unix_sock,&amp;clientFDs); // unix_socket在network_init中被打开</span><br><span class="line">  while (!abort_loop) &#123; // abort_loop是全局变量，在某些情况下被置为1表示要退出。</span><br><span class="line">    readFDs=clientFDs; // 需要监听的socket</span><br><span class="line">    select((int) max_used_connection,&amp;readFDs,0,0,0); // select异步(?科学家解释下是同步还是异步)监听，当接收到??以后返回。</span><br><span class="line">    new_sock = accept(sock, my_reinterpret_cast(struct sockaddr *) (&amp;cAddr),   &amp;length); // 接受请求</span><br><span class="line">    thd= new THD; // 创建mysqld任务线程描述符，它封装了一个客户端连接请求的所有信息</span><br><span class="line">    vio_tmp=vio_new(new_sock, VIO_TYPE_SOCKET, VIO_LOCALHOST); // 网络操作抽象层</span><br><span class="line">    my_net_init(&amp;thd-&gt;net,vio_tmp)); // 初始化任务线程描述符的网络操作</span><br><span class="line">    create_new_thread(thd); // 创建任务线程</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br /><br />网络相关代码 sql/net_serv.cc<br /><br />my_net_init<br />my_net_write   --> 基本将packet的数据 存到net->buffer 中(调用net_write_buff)<br />my_net_read<br /><br /><br /><br />net_write_buff -- 家]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/network/workflow/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/network/workflow/"/>
    <published>2019-06-25T11:42:57.000Z</published>
    <summary>MySQL 网络处理流程源码笔记：handle_connections_sockets 监听、my_net_init 与 do_command 命令分发链路</summary>
    <title>MySQL 源码解读 -- 网络</title>
    <updated>2026-06-09T08:46:25.956Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 插件系统","description":"MySQL 插件系统索引：目前收录 query_cache 插件相关源码阅读笔记，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://ilongda.com/img/my.jpg","wordCount":1,"datePublished":"2019-06-25T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.956Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 插件系统","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/plugin/query_cache/">query_cache</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/index/"/>
    <published>2019-06-25T11:42:57.000Z</published>
    <summary>MySQL 插件系统索引：目前收录 query_cache 插件相关源码阅读笔记，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>MySQL 源码解读 -- 插件系统</title>
    <updated>2026-06-09T08:46:25.956Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- Query Result Cache","description":"MySQL 源码解读 -- Query Result Cache","image":"https://ilongda.com/img/my.jpg","wordCount":1,"datePublished":"2019-06-25T11:42:57.000Z","dateModified":"2024-02-02T13:22:23.616Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/query_cache/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/query_cache/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- Query Result Cache","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/query_cache/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/plugin/query_cache/query_cache.html">query_cache</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/query_cache/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/query_cache/index/"/>
    <published>2019-06-25T11:42:57.000Z</published>
    <summary>MySQL 源码解读 -- Query Result Cache</summary>
    <title>MySQL 源码解读 -- Query Result Cache</title>
    <updated>2024-02-02T13:22:23.616Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- Query Result Cache","description":"MySQL 源码解读 -- Query Result Cache","image":"https://cdn.nlark.com/yuque/0/2020/png/106206/1581577537536-6aeaadbf-0a3f-4b78-9339-74ff47b58243.png#align=left&display=inline&height=530&name=image.png&originHeight=1060&originWidth=1544&size=284581&status=done&style=none&width=772","wordCount":1041,"datePublished":"2019-06-25T11:42:57.000Z","dateModified":"2024-02-02T13:22:34.145Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/query_cache/query_cache/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/query_cache/query_cache/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- Query Result Cache","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/query_cache/query_cache/"}]}</script><h1 id="Query-Result-Cache"><a href="#Query-Result-Cache" class="headerlink" title="Query Result Cache"></a>Query Result Cache</h1><p><a name="XqDhz"></a></p><h1 id="层级结构"><a href="#层级结构" class="headerlink" title="层级结构"></a>层级结构</h1><p>query_cache<br />query_cache 类是直接给mysql 内核使用， 外界使用接口namespace  qc 里面提供的接口<br />qc::send_result_to_client<br /><br><br /><br><br />sql_cache<br />sql_cache 是一个plugin， 对外暴露的接口是Query_cache_service_t sql_cache_service， 将这个sql_cache_service 注册到query_cache 当中 （在函数sql_cache_init），在sql_cache 插件内部， 有个全局类global_query_cache 它是Query_cache<br><a name="EotCq"></a></p><h1 id="sql-cache-基础类型"><a href="#sql-cache-基础类型" class="headerlink" title="sql cache 基础类型"></a>sql cache 基础类型</h1><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1581577537536-6aeaadbf-0a3f-4b78-9339-74ff47b58243.png#align=left&display=inline&height=530&name=image.png&originHeight=1060&originWidth=1544&size=284581&status=done&style=none&width=772" alt="image.png"></p><p><a name="Gq46v"></a></p><h1 id="query-cache-service"><a href="#query-cache-service" class="headerlink" title="query cache service"></a>query cache service</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">typedef uint64 (*resize_func)(uint64 size);</span><br><span class="line"></span><br><span class="line">typedef void (*result_size_limit_func)(uint64 limit);</span><br><span class="line"></span><br><span class="line">typedef void (*store_query_func)(THD *thd, TABLE_LIST *used_tables);</span><br><span class="line"></span><br><span class="line">typedef void (*insert_func)(Query_cache_tls *query_cache_tls,</span><br><span class="line">                            const char *packet, ulong length, unsigned pkt_nr);</span><br><span class="line"></span><br><span class="line">typedef int (*send_result_to_client_func)(THD *thd, const LEX_CSTRING &amp;sql);</span><br><span class="line"></span><br><span class="line">typedef void (*end_of_result_func)(THD *thd);</span><br><span class="line"></span><br><span class="line">typedef void (*abort_func)(Query_cache_tls *query_cache_tls);</span><br><span class="line"></span><br><span class="line">typedef void (*invalidate_table_func)(THD *thd, uchar *key,</span><br><span class="line">                                      size_t key_length);</span><br><span class="line"></span><br><span class="line">typedef void (*flush_func)();</span><br><span class="line"></span><br><span class="line">typedef void (*pack_func)();</span><br><span class="line"></span><br><span class="line">typedef void (*reset_status_func)();</span><br><span class="line"></span><br><span class="line">typedef struct Query_cache_service_t</span><br><span class="line">&#123;</span><br><span class="line">  resize_func resize;</span><br><span class="line">  result_size_limit_func result_size_limit;</span><br><span class="line">  store_query_func store_query;</span><br><span class="line">  insert_func insert;</span><br><span class="line">  send_result_to_client_func send_result_to_client;</span><br><span class="line">  end_of_result_func end_of_result;</span><br><span class="line">  abort_func abort;</span><br><span class="line">  invalidate_table_func invalidate_table;</span><br><span class="line">  flush_func flush;</span><br><span class="line">  pack_func pack;</span><br><span class="line">  reset_status_func reset_status;</span><br><span class="line">&#125; Query_cache_service_t;</span><br></pre></td></tr></table></figure><p>注册Query_cache_service 到系统中去<br />**extern **Query_cache_service_t *Query_cache_service;</p><p>在query_cache plugin 初始化的时候， 将sql_cache_service 注册进去<br />**static **Query_cache_service_t sql_cache_service &#x3D; {<br />    resize, result_size_limit,     store_query,<br />    insert, send_result_to_client, end_of_result,<br />    abort,  invalidate_table,      flush,<br />    pack,   reset_status};</p><p><a name="lOAa8"></a></p><h1 id="query-cache-实现类"><a href="#query-cache-实现类" class="headerlink" title="query cache 实现类"></a>query cache 实现类</h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line">class Query_cache &#123;</span><br><span class="line">public:</span><br><span class="line">  /* Info */</span><br><span class="line">  ulong query_cache_size, query_cache_limit;</span><br><span class="line">  /* statistics */</span><br><span class="line">  std::atomic&lt;uint64&gt; queries_in_cache, hits, inserts, refused, total_blocks,</span><br><span class="line">      lowmem_prunes;</span><br><span class="line">private:</span><br><span class="line">  Query_cache_alloc m_allocator;</span><br><span class="line">  /*</span><br><span class="line">   When read query cache result or store query result, just use read lock.</span><br><span class="line">   Use write lock to resize/flush query cache.</span><br><span class="line">  */</span><br><span class="line">  mysql_rwlock_t qc_rwlock;</span><br><span class="line">  Query_cache_block_lists m_queries_lru;</span><br><span class="line">  Query_cache_block_lists m_tables;</span><br><span class="line"></span><br><span class="line">  LF_HASH queries, tables;</span><br><span class="line">  /* options */</span><br><span class="line">  uint def_query_hash_size, def_table_hash_size;</span><br><span class="line"></span><br><span class="line">  my_bool initialized;</span><br><span class="line"></span><br><span class="line">  my_bool m_prune_thread_state;</span><br><span class="line">  bool m_query_cache_is_disabled;</span><br><span class="line"></span><br><span class="line"> public:</span><br><span class="line">  my_bool get_prune_state() &#123; return m_prune_thread_state; &#125;;</span><br><span class="line">  void set_prune_state(my_bool state) &#123; m_prune_thread_state = state; &#125;</span><br><span class="line"></span><br><span class="line">  uint64 get_free_memory_size();</span><br><span class="line">  uint8 get_memory_usage();</span><br><span class="line">  ulonglong get_time_lease_left(Query_cache_block *query_block);</span><br><span class="line"></span><br><span class="line">  void recycle_old_query();</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line">extern Query_cache *global_query_cache;</span><br></pre></td></tr></table></figure><p><a name="nsbqk"></a></p><h1 id="锁系统"><a href="#锁系统" class="headerlink" title="锁系统"></a>锁系统</h1><p>大锁， 平时操作， 都是读锁， 在flush时，进行写锁</p><h1 id="some-point"><a href="#some-point" class="headerlink" title="some point"></a>some point</h1><p>以前query cache是固定memory size。一启动就分配了。<br>现在的query cache用通过通用的malloc接口，背后实际jemalloc，是使用的时候才会分配。<br>内存用满了再申请的时候，就会主动释放链表(lru链表）上的old query.</p><p>同时原生的query cache, query block释放除了申请不到的情况，就是有dml语句的时候，invalidate释放。<br>我们现在query cache， 当有dml语句的时候，不会立即释放，除了内存不够的时候释放old query，还有就是匹配到对应query发现失效释放和prune线程回收释放。</p><p>prune线程目前是对性能不影响的，不会提升性能。 这个等后面做更多performance tunning后再去看。</p><p>prune线程是定位用来回收内存的。会主动释放无效query&#x2F;table，还有比较老的query，不断释放内存。但这个对sysbench压测没有什么影响。因为sysbench压测，内存一直在高水位运行。</p><p>Query_cache_block_lists m_queries_lru;<br>Query_cache_block_lists m_tables;<br>LF_HASH queries, tables;<br>这个Lists是有16个List，然后每个query上，和每个table也有读写锁和原子状态(标记是否有人在做释放呢）。 （原来的query cache query上就单独有锁，但是没有被充分利用）</p><p>然后两个无锁hash。无锁hash实际也有自己的保护位。每次查找后需要调用lf_hash_search_unpin，另外的线程才可以释放对应对象。</p><p>恩，这个主要是由之前query cache串行运行，改为并发安全。<br>内部结构，去掉关联一个表所有query的链表，原来的这个链表涉及表和query。<br>剩余的链表结构变成了简单加减节点操作，这个锁时间很短，而且query和table的链表都是分拆成16个，锁冲突很少。<br>hash结构全部变成无锁hash。 利用query table上的锁还有原子状态变量保证并发并发安全。</p><p>恩，这个主要是由之前query cache串行运行，改为并发安全。<br>内部结构，去掉关联一个表所有query的链表，原来的这个链表涉及表和query。<br>剩余的链表结构变成了简单加减节点操作，这个锁时间很短，而且query和table的链表都是分拆成16个，锁冲突很少。<br>hash结构全部变成无锁hash。 利用query table上的锁还有原子状态变量保证并发并发安全。</p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/query_cache/query_cache/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/plugin/query_cache/query_cache/"/>
    <published>2019-06-25T11:42:57.000Z</published>
    <summary>MySQL 源码解读 -- Query Result Cache</summary>
    <title>MySQL 源码解读 -- Query Result Cache</title>
    <updated>2024-02-02T13:22:34.145Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- server层内存管理","description":"MySQL Server 层 MEM_ROOT 内存管理：Block 链表申请、Claim/Clear/Reuse 及 PSI 内存追踪机制源码分析","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1567562806268-bee91937-1c91-4c8d-a64e-18f9cba15a69.png#align=left&display=inline&height=257&name=image.png&originHeight=257&originWidth=749&size=27141&status=done&width=749","wordCount":435,"datePublished":"2019-06-24T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.956Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/server/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/server/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- server层内存管理","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/server/"}]}</script><h1 id="server层内存管理"><a href="#server层内存管理" class="headerlink" title="server层内存管理"></a>server层内存管理</h1><p><a name="gz15o"></a></p><h3 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h3><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567562806268-bee91937-1c91-4c8d-a64e-18f9cba15a69.png#align=left&display=inline&height=257&name=image.png&originHeight=257&originWidth=749&size=27141&status=done&width=749" alt="image.png"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">struct Block &#123;</span><br><span class="line">    Block *prev&#123;nullptr&#125;; /** Previous block; used for freeing. */</span><br><span class="line">  &#125;;</span><br><span class="line">Block 是MEM_ROOT的子类</span><br></pre></td></tr></table></figure><p>Block 的prev 指的都是历史Block, 已经分配完内存的Block<br />m_max_capacity 为0，表示不限制<br />m_allocated_size表示的使用的block 合起来的size</p><p>Block 内部结构<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567585176100-273cb3d8-23a7-4954-844c-24f77c9f9c52.png#align=left&display=inline&height=163&name=image.png&originHeight=163&originWidth=398&size=13932&status=done&width=398" alt="image.png"><br />要求my_memory_head小于32个字节<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567697847668-9a631eb4-ab63-4526-8af5-31669f35d723.png#align=left&display=inline&height=289&name=image.png&originHeight=578&originWidth=334&size=26586&status=done&width=167" alt="image.png"></p><p><a name="uuyr2"></a></p><h3 id="内存申请流程"><a href="#内存申请流程" class="headerlink" title="内存申请流程"></a>内存申请流程</h3><p><img data-src="https://cdn.nlark.com/yuque/__flowchart/e4684dfc2293defb6b49506e3ce412c6.svg#lake_card_v2=eyJjb2RlIjoic3Q9PnN0YXJ0OiDlvIDlp4tcbmU9PmVuZDog57uT5p2fXG5cbmN1cnJlbnRJc0Vub3VnaD0-Y29uZGl0aW9uOiDlvZPliY3lhoXlrZjotrPlpJ8_XG5hc3NpZ25NZW09Pm9wZXJhdGlvbjog5YiG6YWN5YaF5a2YXG5hcHBseUJpZ01lbT0-Y29uZGl0aW9uOiDnlLPor7flhoXlrZjotoXov4dibG9ja3NpemU_XG5hbGxvY0Jsb2NrU2l6ZU1lbT0-b3BlcmF0aW9uOiDnlLPor7fmjIflrppibG9ja3NpemUg5YaF5a2YXG5hbGxvY0JpZ01lbT0-b3BlcmF0aW9uOiDnlLPor7for7fmsYLlpKflsI_lhoXlrZhcbmZpcnN0QmxvY2s9PmNvbmRpdGlvbjog56ys5LiA5qyh5YiG6YWNP1xuaW5zZXJ0QmxvY2tDaGFpbj0-b3BlcmF0aW9uOiDmj5LlhaXliLDnjrDmnIlibG9jayBjaGFpblxuaW5zZXJ0Q3VycmVudEJsb2NrQ2hhaW49Pm9wZXJhdGlvbjog6K6-572u5b2T5YmNYmxvY2tcblxuc3QtPmN1cnJlbnRJc0Vub3VnaFxuY3VycmVudElzRW5vdWdoKHllcywgcmlnaHQpLT5hc3NpZ25NZW0tPmVcbmN1cnJlbnRJc0Vub3VnaChubywgZG93biktPmFwcGx5QmlnTWVtXG5cbmFwcGx5QmlnTWVtKG5vLCByaWdodCktPmFsbG9jQmxvY2tTaXplTWVtLT5pbnNlcnRDdXJyZW50QmxvY2tDaGFpbi0-YXNzaWduTWVtLT5lXG5hcHBseUJpZ01lbSh5ZXMsIGRvd24pLT5hbGxvY0JpZ01lbS0-Zmlyc3RCbG9ja1xuXG5maXJzdEJsb2NrKHllcyktPmluc2VydEN1cnJlbnRCbG9ja0NoYWluLT5hc3NpZ25NZW0tPmVcbmZpcnN0QmxvY2sobm8pLT5pbnNlcnRCbG9ja0NoYWluLT5hc3NpZ25NZW0tPmVcblxuXG5cblxuXG5cblxuXG5cbiIsInR5cGUiOiJmbG93Y2hhcnQiLCJpZCI6IjZzajR5IiwidXJsIjoiaHR0cHM6Ly9jZG4ubmxhcmsuY29tL3l1cXVlL19fZmxvd2NoYXJ0L2U0Njg0ZGZjMjI5M2RlZmI2YjQ5NTA2ZTNjZTQxMmM2LnN2ZyIsImNhcmQiOiJkaWFncmFtIn0=" alt="MySQL 源码解读 -- server层内存管理"><br>申请block内部<br><img data-src="https://cdn.nlark.com/yuque/__flowchart/2073bb6192c9216e25ce62e2b2851f29.svg#lake_card_v2=eyJjb2RlIjoicz0-c3RhcnQ6IOeUs-ivt-WGheWtmFxuZj0-ZW5kOiDlpLHotKVcbnI9PmVuZDog6L-U5ZueXG5cbmV4Y2VlZE1heENhcGNpdHk9PmNvbmRpdGlvbjog6LaF6L-H6ZmQ6aKd77yfXG5hbGxvY01lbT0-Y29uZGl0aW9uOiDmiJDlip_nlLPor7flhoXlrZjvvJ9cbmFsbG9jTWVtU3VjY2VlZD0-b3BlcmF0aW9uOiDmiJDlip9cblxucy0-ZXhjZWVkTWF4Q2FwY2l0eVxuZXhjZWVkTWF4Q2FwY2l0eSh5ZXMsIHJpZ2h0KS0-ZlxuZXhjZWVkTWF4Q2FwY2l0eShubywgZG93biktPmFsbG9jTWVtXG5hbGxvY01lbSh5ZXMpLT5yXG5hbGxvY01lbShubyktPmZcbiIsInR5cGUiOiJmbG93Y2hhcnQiLCJpZCI6InZsMXljIiwidXJsIjoiaHR0cHM6Ly9jZG4ubmxhcmsuY29tL3l1cXVlL19fZmxvd2NoYXJ0LzIwNzNiYjYxOTJjOTIxNmUyNWNlNjJlMmIyODUxZjI5LnN2ZyIsImNhcmQiOiJkaWFncmFtIn0=" alt="MySQL 源码解读 -- server层内存管理"><br>其中“设置当前block” ,</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">char *new_mem =</span><br><span class="line">    pointer_cast&lt;char *&gt;(new_block) + ALIGN_SIZE(sizeof(*new_block));</span><br><span class="line"></span><br><span class="line">new_block-&gt;prev = m_current_block;</span><br><span class="line">m_current_block = new_block;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">m_current_free_start = new_mem + length;</span><br><span class="line">m_current_free_end = new_mem + new_block_size;</span><br></pre></td></tr></table></figure><p>插入现有blockchain</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">new_block-&gt;prev = m_current_block-&gt;prev;</span><br><span class="line">m_current_block-&gt;prev = new_block;</span><br></pre></td></tr></table></figure><p>其中有个细节， 当申请big内存时， 会将blocksize 增加1&#x2F;2</p><p>这个里面有一点内存浪费逻辑<br />当内存不够时，并且新申请的内存小于blocksize时， 当前block会变成历史block， 历史block 基本上内存用</p><p><a name="1saZi"></a></p><h3 id="Claim"><a href="#Claim" class="headerlink" title="Claim"></a>Claim</h3><p>MEM_ROOT Claim 会对每个Block 进行Claim<br />而Block-&gt;claim， 主要是设置PSI</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">void my_claim(const void *ptr) &#123;</span><br><span class="line">  my_memory_header *mh;</span><br><span class="line"></span><br><span class="line">  if (ptr == NULL) return;</span><br><span class="line"></span><br><span class="line">  mh = USER_TO_HEADER(ptr);</span><br><span class="line">  DBUG_ASSERT(mh-&gt;m_magic == MAGIC);</span><br><span class="line">  mh-&gt;m_key =</span><br><span class="line">      PSI_MEMORY_CALL(memory_claim)(mh-&gt;m_key, mh-&gt;m_size, &amp;mh-&gt;m_owner);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a name="F0i8C"></a></p><h3 id="Clear"><a href="#Clear" class="headerlink" title="Clear"></a>Clear</h3><p>如果设置了<strong>MY_MARK_BLOCKS_FREE或MY_KEEP_PREALLOC标记位， 则</strong>root-&gt;ClearForReuse();<br />否则直接调用root-&gt;Clear</p><p>ClearForReuse 和Clear 主要的区别就是， 对当前block的处理， Reuse 会重新使用当前Block, 而clear会清理当前Block<br />Clear</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Block *start = m_current_block;</span><br><span class="line"></span><br><span class="line">  m_current_block = nullptr;</span><br><span class="line">  m_block_size = m_orig_block_size;</span><br><span class="line">  m_current_free_start = &amp;s_dummy_target;</span><br><span class="line">  m_current_free_end = &amp;s_dummy_target;</span><br><span class="line">  m_allocated_size = 0;</span><br><span class="line"></span><br><span class="line">  FreeBlocks(start);</span><br></pre></td></tr></table></figure><p>ClearForReuse</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">m_current_free_start = pointer_cast&lt;char *&gt;(m_current_block) +</span><br><span class="line">                         ALIGN_SIZE(sizeof(*m_current_block));</span><br><span class="line">  Block *start = m_current_block-&gt;prev;</span><br><span class="line">  m_current_block-&gt;prev = nullptr;</span><br><span class="line">  m_allocated_size = m_current_free_end - m_current_free_start;</span><br><span class="line"></span><br><span class="line">  FreeBlocks(start);</span><br></pre></td></tr></table></figure><p><a name="zp7I5"></a></p><h3 id="Block-内部"><a href="#Block-内部" class="headerlink" title="Block 内部"></a>Block 内部</h3><p>内存申请&#x2F;释放都是走my_malloc&#x2F;my_free&#x2F;my_realloc, 这几个函数比较简单， 就是申请一块内存，然后将内存header设置相应的数据</p><p><a name="0Fb8q"></a></p><h2 id=""><a href="#" class="headerlink" title=""></a></h2>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/memory/server/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/memory/server/"/>
    <published>2019-06-24T11:42:57.000Z</published>
    <summary>MySQL Server 层 MEM_ROOT 内存管理：Block 链表申请、Claim/Clear/Reuse 及 PSI 内存追踪机制源码分析</summary>
    <title>MySQL 源码解读 -- server层内存管理</title>
    <updated>2026-06-09T08:46:25.956Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- innodb 内存","description":"InnoDB 内存管理源码笔记：mem_heap_t 堆分配、ut_allocator 封装与 block 对齐及 no-man land 调试机制","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1567608375891-e051f193-78b8-4d57-a45f-03f9d191cc08.png#align=left&display=inline&height=840&name=image.png&originHeight=840&originWidth=1618&size=131594&status=done&width=1618","wordCount":769,"datePublished":"2019-06-23T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.956Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/innodb/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/innodb/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- innodb 内存","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/innodb/"}]}</script><h1 id="innodb-内存"><a href="#innodb-内存" class="headerlink" title="innodb 内存"></a>innodb 内存</h1><p><a name="1JdVP"></a></p><h3 id="数据结构"><a href="#数据结构" class="headerlink" title="数据结构"></a>数据结构</h3><p>在innodb 内部， 用另外一个内存数据结构， mem_heap_t（也是mem_block_t）来表示<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567608375891-e051f193-78b8-4d57-a45f-03f9d191cc08.png#align=left&display=inline&height=840&name=image.png&originHeight=840&originWidth=1618&size=131594&status=done&width=1618" alt="image.png"><br />另外有allocator， </p><p>mem_heap_allocator 负责mem_heap_t block的申请和释放， 做一个封装</p><p>ut_allocator 负责从_std::* containers申请内存， 内容比较简单， 提供一个封装， 封装alloc&#x2F;dealloc_<br />_其中alloc时， 多申请_ut_new_pfx_t自己， 从而在ut_new_pfx_t 保存一些debug 信息</p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567677490173-6b992b9c-4ac9-42c9-867a-30727818470f.png#align=left&display=inline&height=128&name=image.png&originHeight=128&originWidth=371&size=10991&status=done&width=371" alt="image.png"></p><p>非buffer pool的内存情况（动态内存或者内存小于page_size&#x2F;2）<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567694528755-1406da58-09b8-43e6-b074-a110ffe1d689.png#align=left&display=inline&height=304&name=image.png&originHeight=608&originWidth=372&size=28859&status=done&width=186" alt="image.png"><br />mem_heap_t-&gt;start 指的位置是（char *）mem_heap_t + <strong>MEM_BLOCK_HEADER_SIZE</strong><br><a name="l0SOj"></a></p><h3 id="alignment"><a href="#alignment" class="headerlink" title="alignment"></a>alignment</h3><p>申请的（内存大小 + 32 个字节）然后向上取整8个字节</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">#define MEM_SPACE_NEEDED(N) \</span><br><span class="line">  ut_calc_align(N + 2 * MEM_NO_MANS_LAND, UNIV_MEM_ALIGNMENT)</span><br><span class="line">#define MEM_BLOCK_HEADER_SIZE \</span><br><span class="line">  ut_calc_align(sizeof(mem_block_info_t), UNIV_MEM_ALIGNMENT)</span><br><span class="line">  </span><br><span class="line">#define ut_calc_align(n, m) (((n) + ((m)-1)) &amp; ~((m)-1))</span><br><span class="line">#define UNIV_MEM_ALIGNMENT 8</span><br><span class="line">const int MEM_NO_MANS_LAND = 16;</span><br></pre></td></tr></table></figure><p>Block 内部有很多alignment，<br />比如Block 的大小是  ut_calc_align（sizeof(mem_heap_t)） + MEM_SPACE_NEEDED(用户len)<br />用户每使用一块内存， 都会前后各空MEM_NO_MANS_LAND 个字节<br><a name="YPdF2"></a></p><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><p><a name="MUHSW"></a></p><h3 id="内存申请"><a href="#内存申请" class="headerlink" title="内存申请"></a>内存申请</h3><p>函数mem_heap_alloc负责申请一块内存<br><img data-src="https://cdn.nlark.com/yuque/__flowchart/95268bd84792afede164c92907f40929.svg#lake_card_v2=eyJjb2RlIjoicz0-c3RhcnQ6IOeUs-ivt-WGheWtmFxuZj0-ZW5kOuWksei0pVxuZT5lbmQ6IOe7k-adn1xuXG5jdXJyZW50SXNFbm91Z2g9PmNvbmRpdGlvbjog5b2T5YmN5YaF5a2Y6Laz5aSfP1xubWVtX2hlYXBfYWRkX2Jsb2NrPT5vcGVyYXRpb246IOeUs-ivt-aWsOeahGJsb2NrXG5hc3NpZ25NZW09Pm9wZXJhdGlvbjog5YiG6YWN5YaF5a2YXG5cbnMtPmN1cnJlbnRJc0Vub3VnaFxuY3VycmVudElzRW5vdWdoKG5vLCByaWdodCktPm1lbV9oZWFwX2FkZF9ibG9jay0-YXNzaWduTWVtLT5lXG5jdXJyZW50SXNFbm91Z2goeWVzLCBkb3duKS0-YXNzaWduTWVtLT5lIiwidHlwZSI6ImZsb3djaGFydCIsImlkIjoiWmJpd2giLCJ1cmwiOiJodHRwczovL2Nkbi5ubGFyay5jb20veXVxdWUvX19mbG93Y2hhcnQvOTUyNjhiZDg0NzkyYWZlZGUxNjRjOTI5MDdmNDA5Mjkuc3ZnIiwiY2FyZCI6ImRpYWdyYW0ifQ==" alt="MySQL 源码解读 -- innodb 内存"><br>mem_heap_add_block 可以参考创建过程中申请流程<br />assign 内存片段, 用户的内存前后，各保留了16各字节（MEN_NO_MANS_LAND）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">free = mem_block_get_free(block);</span><br><span class="line">buf = (byte *)block + free + MEM_NO_MANS_LAND;</span><br><span class="line">mem_block_set_free(block, free + MEM_SPACE_NEEDED(n));</span><br><span class="line">return buf;</span><br></pre></td></tr></table></figure><p><a name="laDjP"></a></p><h3 id="创建"><a href="#创建" class="headerlink" title="创建"></a>创建</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">mem_heap_t * mem_heap_create_func() &#123;</span><br><span class="line">    block = mem_heap_create_block(NULL, size, type, file_name, line);</span><br><span class="line">    UT_LIST_INIT(block-&gt;base, &amp;mem_block_t::list);</span><br><span class="line">    UT_LIST_ADD_FIRST(block-&gt;base, block);</span><br><span class="line">    return block;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>mem_heap_add_block 核心逻辑</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">mem_block_t *mem_heap_add_block(mem_heap_t *heap, /*!&lt; in: memory heap */</span><br><span class="line">                                ulint n) /*!&lt; in: number of bytes user needs */</span><br><span class="line">&#123;</span><br><span class="line">   //set correct new size</span><br><span class="line">   new_size = xxxxx;</span><br><span class="line">   new_block = mem_heap_create_block(heap, new_size, heap-&gt;type, heap-&gt;file_name,</span><br><span class="line">                                    heap-&gt;line);</span><br><span class="line">   UT_LIST_INSERT_AFTER(heap-&gt;base, block, new_block);</span><br><span class="line">return (new_block);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>mem_heap_create_block 逻辑</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">mem_block_t *mem_heap_create_block_func(</span><br><span class="line">    mem_heap_t *heap, /*!&lt; in: memory heap or NULL if first block</span><br><span class="line">                      should be created */</span><br><span class="line">    ulint n,          /*!&lt; in: number of bytes needed for user data */</span><br><span class="line">#ifdef UNIV_DEBUG</span><br><span class="line">    const char *file_name, /*!&lt; in: file name where created */</span><br><span class="line">    ulint line,            /*!&lt; in: line where created */</span><br><span class="line">#endif                     /* UNIV_DEBUG */</span><br><span class="line">    ulint type)            /*!&lt; in: type of heap: MEM_HEAP_DYNAMIC or</span><br><span class="line">                           MEM_HEAP_BUFFER */</span><br><span class="line">&#123;</span><br><span class="line">    len = MEM_BLOCK_HEADER_SIZE + MEM_SPACE_NEEDED(n);</span><br><span class="line">    if (type == MEM_HEAP_DYNAMIC || len &lt; UNIV_PAGE_SIZE / 2) &#123;</span><br><span class="line">    block = static_cast&lt;mem_block_t *&gt;(ut_malloc_nokey(len));</span><br><span class="line">  &#125; else &#123;</span><br><span class="line">        block = 从buffer_pool 中取</span><br><span class="line">        len = ((ulint)srv_page_size);</span><br><span class="line">        if ((type &amp; MEM_HEAP_BTR_SEARCH) &amp;&amp; heap) &#123;</span><br><span class="line">          /* We cannot allocate the block from the</span><br><span class="line">          buffer pool, but must get the free block from</span><br><span class="line">          the heap header free block field */</span><br><span class="line">          buf_block = static_cast&lt;buf_block_t *&gt;(heap-&gt;free_block);</span><br><span class="line">          heap-&gt;free_block = NULL;</span><br><span class="line">        &#125; else &#123;</span><br><span class="line">          buf_block = buf_block_alloc(NULL);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        block = (mem_block_t *)buf_block-&gt;frame;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    // 设置block</span><br><span class="line">    block-&gt;buf_block = buf_block;</span><br><span class="line">    block-&gt;free_block = NULL;</span><br><span class="line">    block-&gt;magic_n = MEM_BLOCK_MAGIC_N;</span><br><span class="line">      mem_block_set_len(block, len);</span><br><span class="line">      mem_block_set_type(block, type);</span><br><span class="line">      mem_block_set_start(block, MEM_BLOCK_HEADER_SIZE);</span><br><span class="line">      mem_block_set_free(block, MEM_BLOCK_HEADER_SIZE);</span><br><span class="line">      </span><br><span class="line">      if (UNIV_UNLIKELY(heap == NULL)) &#123;</span><br><span class="line">        block-&gt;total_size = len;</span><br><span class="line">      &#125; else &#123;</span><br><span class="line">        heap-&gt;total_size += len;</span><br><span class="line">      &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><a name="iAogy"></a></p><h3 id="释放heap"><a href="#释放heap" class="headerlink" title="释放heap"></a>释放heap</h3><p>先把heap-&gt;free_block释放掉， 从最后一个block开始free， 直到整个heap全部释放<br />mem_heap_block_free(heap, block); 这个函数释放的block不是从buffer pool里面申请的内存</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">void mem_heap_block_free(mem_heap_t *heap,   /*!&lt; in: heap */</span><br><span class="line">                         mem_block_t *block)&#123;</span><br><span class="line">      UT_LIST_REMOVE(heap-&gt;base, block);</span><br><span class="line">      heap-&gt;total_size -= block-&gt;len;</span><br><span class="line">      block-&gt;magic_n = MEM_FREED_BLOCK_MAGIC_N;</span><br><span class="line">      if (type == MEM_HEAP_DYNAMIC || len &lt; UNIV_PAGE_SIZE / 2) &#123;</span><br><span class="line">        ut_allocator&lt;byte&gt;(PSI_NOT_INSTRUMENTED) \</span><br><span class="line">      .deallocate(reinterpret_cast&lt;byte *&gt;(block));</span><br><span class="line">          // 实际上就是把整个ut_new_pfx_t内存块给释放掉， 清掉了trace 字段</span><br><span class="line">      &#125; else &#123;</span><br><span class="line">        /* Make memory available again for buffer pool, as we set parts</span><br><span class="line">        of block to &quot;free&quot; state in heap allocator. */</span><br><span class="line">        UNIV_MEM_ALLOC(block, UNIV_PAGE_SIZE);</span><br><span class="line">        buf_block_free(buf_block);</span><br><span class="line">        ///** Frees a buffer block which does not contain a file page. */</span><br><span class="line">        //buf_LRU_block_free_non_file_page(block);</span><br><span class="line">      &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/memory/innodb/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/memory/innodb/"/>
    <published>2019-06-23T11:42:57.000Z</published>
    <summary>InnoDB 内存管理源码笔记：mem_heap_t 堆分配、ut_allocator 封装与 block 对齐及 no-man land 调试机制</summary>
    <title>MySQL 源码解读 -- innodb 内存</title>
    <updated>2026-06-09T08:46:25.956Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- Buffer Pool","description":"MySQL Buffer Pool 源码解读占位笔记：计划分析 InnoDB 缓冲池管理与页面缓存机制，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":1,"datePublished":"2019-06-22T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.955Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/buffer_pool/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/buffer_pool/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- Buffer Pool","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/buffer_pool/"}]}</script><p>TODO</p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/memory/buffer_pool/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/memory/buffer_pool/"/>
    <published>2019-06-22T11:42:57.000Z</published>
    <summary>MySQL Buffer Pool 源码解读占位笔记：计划分析 InnoDB 缓冲池管理与页面缓存机制，更多细节与示例见正文。</summary>
    <title>MySQL 源码解读 -- Buffer Pool</title>
    <updated>2026-06-09T08:46:25.955Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 内存管理","description":"MySQL 内存管理专题索引：汇总分配器对比、架构概述、Buffer Pool、InnoDB 与 Server 层内存笔记","image":"https://ilongda.com/img/my.jpg","wordCount":5,"datePublished":"2019-06-22T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.955Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 内存管理","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/memory/allocator.html">allocator</a></br></li><li><a href="/knowledge/mysql/source_code_reading/memory/architecture.html">architecture</a></br></li><li><a href="/knowledge/mysql/source_code_reading/memory/buffer_pool.html">buffer_pool</a></br></li><li><a href="/knowledge/mysql/source_code_reading/memory/innodb.html">innodb</a></br></li><li><a href="/knowledge/mysql/source_code_reading/memory/server.html">server</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/memory/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/memory/index/"/>
    <published>2019-06-22T11:42:57.000Z</published>
    <summary>MySQL 内存管理专题索引：汇总分配器对比、架构概述、Buffer Pool、InnoDB 与 Server 层内存笔记</summary>
    <title>MySQL 源码解读 -- 内存管理</title>
    <updated>2026-06-09T08:46:25.955Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 内存概述","description":"MySQL 内存管理概述：梳理 Server 层与 InnoDB 引擎各自的内存使用方式与接口划分，更多细节与示例见正文。","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1567585352721-537a9700-e92a-46e3-9122-98a3cc65217f.png#align=left&display=inline&height=269&name=image.png&originHeight=269&originWidth=811&size=66114&status=done&width=811","wordCount":19,"datePublished":"2019-06-21T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.955Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/architecture/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/architecture/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 内存概述","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/architecture/"}]}</script><h1 id="内存概述"><a href="#内存概述" class="headerlink" title="内存概述"></a>内存概述</h1><p><a name="RZZhu"></a></p><h1 id="内存使用"><a href="#内存使用" class="headerlink" title="内存使用"></a>内存使用</h1><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567585352721-537a9700-e92a-46e3-9122-98a3cc65217f.png#align=left&display=inline&height=269&name=image.png&originHeight=269&originWidth=811&size=66114&status=done&width=811" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567585366599-eea21d89-cb48-439b-a92d-5c5480a47163.png#align=left&display=inline&height=85&name=image.png&originHeight=85&originWidth=201&size=5047&status=done&width=201" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1568949772191-fdbb2727-b546-4a44-af02-b7e1c38dd078.png#align=left&display=inline&height=381&name=image.png&originHeight=381&originWidth=944&size=57930&status=done&width=944" alt="image.png"><br />server层使用方式</p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567585933151-44975492-89ae-46f0-81a3-cada4cf8cb20.png#align=left&display=inline&height=303&name=image.png&originHeight=303&originWidth=476&size=24781&status=done&width=476" alt="image.png">、</p><p>innodb 内存接口</p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567585948725-69b56e3c-1ef6-4b70-883c-2c8ac5654dce.png#align=left&display=inline&height=337&name=image.png&originHeight=337&originWidth=442&size=21120&status=done&width=442" alt="image.png"></p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/memory/architecture/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/memory/architecture/"/>
    <published>2019-06-21T11:42:57.000Z</published>
    <summary>MySQL 内存管理概述：梳理 Server 层与 InnoDB 引擎各自的内存使用方式与接口划分，更多细节与示例见正文。</summary>
    <title>MySQL 源码解读 -- 内存概述</title>
    <updated>2026-06-09T08:46:25.955Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 内存分配器","description":"MySQL 内存分配器对比笔记：ptmalloc/tcmalloc/jemalloc 在 PolarDB 压测下的内存占用与泄漏表现分析","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1567665279009-43315a17-d054-462e-99a5-a02de5cb6525.png#align=left&display=inline&height=755&name=image.png&originHeight=755&originWidth=1335&size=94913&status=done&width=1335","wordCount":4303,"datePublished":"2019-06-20T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.955Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/allocator/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/allocator/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 内存分配器","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/memory/allocator/"}]}</script><h1 id="内存分配器"><a href="#内存分配器" class="headerlink" title="内存分配器"></a>内存分配器</h1><p><a name="rHnCN"></a></p><h2 id="MySQL-内存分配器"><a href="#MySQL-内存分配器" class="headerlink" title="MySQL 内存分配器"></a>MySQL 内存分配器</h2><p>mysql 用过2钟内存分配器， ptmalloc&#x2F;jemalloc， 但尝试过非常多的版本， 目前来看， jemalloc 5.2.1 对内存更加友好， 不容易内存泄漏。</p><p>我们进行内存压力测试：<br><a name="AkKXg"></a></p><h3 id="测试环境"><a href="#测试环境" class="headerlink" title="测试环境"></a>测试环境</h3><p>PolarDB 8.0, buffer pool 2G<br />ptmalloc 2.17(glibc 2.17)<br />tcmalloc 2.7<br />jemalloc 5.2.0</p><p>测试脚本：<br />&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;<br />PORT&#x3D;8888<br />THREADS&#x3D;1000<br />TABLES&#x3D;$THREADS</p><p>for i in <code>seq 10</code> ; do<br />       echo “drop database aws;” | mysql -uroot -h127.0.0.1 –port&#x3D;$PORT<br />       echo “create database aws;” | mysql -uroot -h127.0.0.1 –port&#x3D;$PORT</p><p>       sysbench –db-driver&#x3D;mysql –mysql-user&#x3D;myadmin –mysql-password&#x3D;Passw0rd –mysql-db&#x3D;aws &lt;br &#x2F;&gt;–table_size&#x3D;25000 –tables&#x3D;$TABLES –events&#x3D;0 –time&#x3D;5 –report_interval&#x3D;1 &lt;br &#x2F;&gt;–rand-type&#x3D;uniform oltp_point_select –mysql-host&#x3D;127.0.0.1 –mysql-port&#x3D;$PORT &lt;br &#x2F;&gt;               –threads&#x3D;$THREADS –percentile&#x3D;95 –db-ps-mode&#x3D;disable prepare<br />done<br />&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;</p><p><a name="NDVwU"></a></p><h3 id="运行不同的allocator"><a href="#运行不同的allocator" class="headerlink" title="运行不同的allocator"></a>运行不同的allocator</h3><p>ptmalloc(glibc)，系统默认<br />&#x2F;home&#x2F;ming.lin&#x2F;polardb&#x2F;PolarDB_80&#x2F;bin&#x2F;mysqld –defaults-file&#x3D;&#x2F;home&#x2F;ming.lin&#x2F;polardb&#x2F;my.cnf</p><p>tcmalloc(<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2dwZXJmdG9vbHMvZ3BlcmZ0b29scy5naXQ=">https://github.com/gperftools/gperftools.git<i class="fa fa-external-link-alt"></i></span>)<br />LD_PRELOAD&#x3D;&#x2F;home&#x2F;ming.lin&#x2F;install&#x2F;gperftools&#x2F;lib&#x2F;libtcmalloc.so.4.5.3 &lt;br &#x2F;&gt;&#x2F;home&#x2F;ming.lin&#x2F;polardb&#x2F;PolarDB_80&#x2F;bin&#x2F;mysqld –defaults-file&#x3D;&#x2F;home&#x2F;ming.lin&#x2F;polardb&#x2F;my.cnf</p><p>jemalloc(<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2plbWFsbG9jL2plbWFsbG9jLmdpdA==">https://github.com/jemalloc/jemalloc.git<i class="fa fa-external-link-alt"></i></span>)<br />LD_PRELOAD&#x3D;&#x2F;home&#x2F;ming.lin&#x2F;install&#x2F;jemalloc&#x2F;lib&#x2F;libjemalloc.so.2 &lt;br &#x2F;&gt;&#x2F;home&#x2F;ming.lin&#x2F;polardb&#x2F;PolarDB_80&#x2F;bin&#x2F;mysqld –defaults-file&#x3D;&#x2F;home&#x2F;ming.lin&#x2F;polardb&#x2F;my.cnf</p><p><a name="NboBM"></a></p><h3 id="物理内存占用对比"><a href="#物理内存占用对比" class="headerlink" title="物理内存占用对比"></a>物理内存占用对比</h3><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567665279009-43315a17-d054-462e-99a5-a02de5cb6525.png#align=left&display=inline&height=755&name=image.png&originHeight=755&originWidth=1335&size=94913&status=done&width=1335" alt="image.png"></p><p>ptmalloc(蓝色线): 物理内存占用持续在约12G，测试停止后没有降下来<br />tcmalloc(橙色线): 物理内存占用持续在约9G，测试停止后没有降下来<br />jemalloc(灰色线): 最理想，平均物理内存占用约6.5G，且每轮sysbench停止后物理内存降回到约4G，最符合预期。</p><p>注：物理内存占用每秒采集一次: grep VmRSS &#x2F;proc&#x2F;<code>pidof mysqld</code>&#x2F;status</p><p><a name="tT54O"></a></p><h3 id="jemalloc-5-1-vs-5-2-测试"><a href="#jemalloc-5-1-vs-5-2-测试" class="headerlink" title="jemalloc 5.1 vs 5.2 测试"></a>jemalloc 5.1 vs 5.2 测试</h3><p>我们在实际使用过程中， 发现5.1 还是有内存不释放问题， 我们升级到最新版本，就发现情况改善了很多。<br />在5.2 中， 调整 extent 垃圾回收周期，调整为0， 保障了应用释放的内存及时释放给os， 避免内存泄漏。<br />下图所示是5.1， 急剧下降是oom了 <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567665473046-5448830f-1093-47da-bed4-f1188d116346.png#align=left&display=inline&height=190&name=image.png&originHeight=190&originWidth=459&size=18302&status=done&width=459" alt="image.png"><br />dirty_decay_ms &#x3D; 10000,<br />muzzy_decay_ms &#x3D; 10000,<br />narenas &#x3D; CPU 核心数 * 4</p><p>下图所示是5.2， 保持在一定水位，基本未oom <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567665505208-f326ff96-5cef-4268-b9b4-a1b38b4871ff.png#align=left&display=inline&height=217&name=image.png&originHeight=217&originWidth=540&size=15954&status=done&width=540" alt="image.png"><br />dirty_decay_ms &#x3D; 5000,<br />muzzy_decay_ms &#x3D; 5000,<br />narenas &#x3D; 32</p><p><a name="5cIGq"></a></p><h1 id="原理解析"><a href="#原理解析" class="headerlink" title="原理解析"></a>原理解析</h1><p>转载这篇文章<br /><span class="exturl" data-url="aHR0cDovL3d3dy5jbmhhbG8ubmV0LzIwMTYvMDYvMTMvbWVtb3J5LW9wdGltaXplLw==">http://www.cnhalo.net/2016/06/13/memory-optimize/<i class="fa fa-external-link-alt"></i></span><br />对部分内容进行了二次修改<br><a name="73e82552"></a></p><h2 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h2><p>内存管理可以分为三个层次，自底向上分别是：</p><ul><li>操作系统内核的内存管理</li><li>glibc层使用系统调用维护的内存管理算法</li><li>应用程序从glibc动态分配内存后，根据应用程序本身的程序特性进行优化， 比如使用引用计数std::shared_ptr，apache的内存池方式等等。<br />当然应用程序也可以直接使用系统调用从内核分配内存，自己根据程序特性来维护内存，但是会大大增加开发成本。</li></ul><p><strong>本文主要介绍了glibc malloc的实现，及其替代品</strong><br />一个优秀的通用内存分配器应具有以下特性:</p><ul><li>额外的空间损耗尽量少</li><li>分配速度尽可能快</li><li>尽量避免内存碎片</li><li>缓存本地化友好</li><li>通用性，兼容性，可移植性，易调试</li></ul><p><a name="84fba1b6"></a></p><h2 id="现状"><a href="#现状" class="headerlink" title="现状"></a>现状</h2><p>目前大部分服务端程序使用glibc提供的malloc&#x2F;free系列函数，而glibc使用的ptmalloc2在性能上远远弱后于google的tcmalloc和facebook的jemalloc。 而且后两者只需要使用LD_PRELOAD环境变量启动程序即可，甚至并不需要重新编译。<br><a name="ug1su"></a></p><h2 id="glibc-ptmalloc"><a href="#glibc-ptmalloc" class="headerlink" title="glibc ptmalloc"></a>glibc ptmalloc</h2><p>ptmalloc2即是我们当前使用的glibc malloc版本。<br><a name="d3ZVs"></a></p><h3 id="ptmalloc原理"><a href="#ptmalloc原理" class="headerlink" title="ptmalloc原理"></a>ptmalloc原理</h3><p><a name="tTFFf"></a></p><h4 id="系统调用接口"><a href="#系统调用接口" class="headerlink" title="系统调用接口"></a>系统调用接口</h4><p><a href="http://www.cnhalo.net/images/x86_process_address_space.png"><img data-src="https://cdn.nlark.com/yuque/0/2019/jpeg/106206/1567665773341-b63ba801-a545-43cb-80f8-2c9bd52aac97.jpeg#align=left&display=inline&height=397&originHeight=397&originWidth=552&size=0&status=done&width=552" alt="MySQL 源码解读 -- 内存分配器"></a><br />上图是 x86_64 下 Linux 进程的默认地址空间, 对 heap 的操作, 操作系统提供了brk()系统调用，设置了Heap的上边界； 对 mmap 映射区域的操作,操作系 统 供了 mmap()和 munmap()函数。<br />因为系统调用的代价很高，不可能每次申请内存都从内核分配空间，尤其是对于小内存分配。 而且因为mmap的区域容易被munmap释放，所以一般大内存采用mmap()，小内存使用brk()。<br><a name="ywCGr"></a></p><h4 id="多线程支持"><a href="#多线程支持" class="headerlink" title="多线程支持"></a>多线程支持</h4><ul><li>Ptmalloc2有一个主分配区(main arena)， 有多个非主分配区。 非主分配区只能使用mmap向操作系统批发申请HEAP_MAX_SIZE（64位系统为64MB）大小的虚拟内存。 当某个线程调用malloc的时候，会先查看线程私有变量中是否已经存在一个分配区，如果存在则尝试加锁，如果加锁失败则遍历arena链表试图获取一个没加锁的arena， 如果依然获取不到则创建一个新的非主分配区。</li><li>free()的时候也要获取锁。分配小块内存容易产生碎片，ptmalloc在整理合并的时候也要对arena做加锁操作。在线程多的时候，锁的开销就会增大。</li></ul><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567666042195-afc64ef3-a78a-4ab6-ae7e-ad9804652660.png#align=left&display=inline&height=341&name=image.png&originHeight=341&originWidth=502&size=27857&status=done&width=502" alt="image.png"><br><a name="95CLY"></a></p><h4 id="ptmalloc内存管理"><a href="#ptmalloc内存管理" class="headerlink" title="ptmalloc内存管理"></a>ptmalloc内存管理</h4><ul><li>用户请求分配的内存在ptmalloc中使用chunk表示， 每个chunk至少需要8个字节额外的开销。 用户free掉的内存不会马上归还操作系统，ptmalloc会统一管理heap和mmap区域的空闲chunk，避免了频繁的系统调用。</li><li>ptmalloc 将相似大小的 chunk 用双向链表链接起来, 这样的一个链表被称为一个 bin。Ptmalloc 一共 维护了 128 个 bin,并使用一个数组来存储这些 bin(如下图所示)。<br /><a href="http://www.cnhalo.net/images/ptmalloc-structure.png"><img data-src="https://cdn.nlark.com/yuque/0/2019/jpeg/106206/1567665773366-56c610b7-772e-4e63-b14e-2e0d3123d815.jpeg#align=left&display=inline&height=283&originHeight=283&originWidth=556&size=0&status=done&width=556" alt="MySQL 源码解读 -- 内存分配器"></a></li></ul><br /><ul><li><p>Unsorted bins：数组中的第一个为 unsorted bin, 不等大，不排序，回收后的内存fast<br>bins 内的 chunk 合并后进入 unsorted bins</p></li><li><p>Small bins：数组中从 2 开始编号的前 64 个 bin 称为 small bins， 等大 chunk，每种 size 一个 bin， </p></li><li><p>Large bins：small bins后面的bin被称作large bins， 不等大，按大小排序， </p></li><li><p>Fast bins：不等大，不排序，回收后的内存</p></li><li><p>当free一个chunk并放入bin的时候， ptmalloc 还会检查它前后的 chunk 是否也是空闲的, 如果是的话,ptmalloc会首先把它们合并为一个大的 chunk, 然后将合并后的 chunk 放到 unstored bin 中。 另外ptmalloc 为了提高分配的速度,会把一些小的(不大于64B) chunk先放到一个叫做 fast bins 的容器内。</p></li></ul><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567666728919-b069c729-b20c-4a23-a6d6-ff708d592a28.png#align=left&display=inline&height=282&name=image.png&originHeight=282&originWidth=512&size=56305&status=done&width=512" alt="image.png"></p><ul><li>在fast bins和bins都不能满足需求后，ptmalloc会设法在一个叫做top chunk的空间分配内存。 对于非主分配区会预先通过mmap分配一大块内存作为top chunk， 当bins和fast bins都不能满足分配需要的时候, ptmalloc会设法在top chunk中分出一块内存给用户, 如果top chunk本身不够大, 分配程序会重新mmap分配一块内存chunk, 并将 top chunk 迁移到新的chunk上，并用单链表链接起来。如果free()的chunk恰好 与 top chunk 相邻,那么这两个 chunk 就会合并成新的 top chunk，如果top chunk大小大于某个阈值才还给操作系统。主分配区类似，不过通过sbrk()分配和调整top chunk的大小，只有heap顶部连续内存空闲超过阈值的时候才能回收内存。</li></ul><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567666142989-e96ec686-f8ba-4b28-962a-717a81d37cae.png#align=left&display=inline&height=366&name=image.png&originHeight=366&originWidth=405&size=29217&status=done&width=405" alt="image.png"></p><ul><li>需要分配的 chunk 足够大,而且 fast bins 和 bins 都不能满足要求,甚至 top chunk 本身也不能满足分配需求时,ptmalloc 会使用 mmap 来直接使用内存映射来将页映射到进程空间。</li></ul><p><a name="pREQp"></a></p><h4 id="ptmalloc分配流程"><a href="#ptmalloc分配流程" class="headerlink" title="ptmalloc分配流程"></a>ptmalloc分配流程</h4><p><a href="http://www.cnhalo.net/images/ptmalloc-alloc-flow.png"><img data-src="https://cdn.nlark.com/yuque/0/2019/jpeg/106206/1567665773375-540945f6-01c8-4b42-b149-1c726e1a2d28.jpeg#align=left&display=inline&height=834&originHeight=834&originWidth=550&size=0&status=done&width=550" alt="MySQL 源码解读 -- 内存分配器"></a><br><a name="jwQJl"></a></p><h3 id="ptmalloc的缺陷"><a href="#ptmalloc的缺陷" class="headerlink" title="ptmalloc的缺陷"></a>ptmalloc的缺陷</h3><ul><li>后分配的内存先释放,因为 ptmalloc 收缩内存是从 top chunk 开始,如果与 top chunk 相邻的 chunk 不能释放, top chunk 以下的 chunk 都无法释放。</li><li>多线程锁开销大， 需要避免多线程频繁分配释放。</li><li>内存从thread的areana中分配， 内存不能从一个arena移动到另一个arena， 就是说如果多线程使用内存不均衡，容易导致内存的浪费。 比如说线程1使用了300M内存，完成任务后glibc没有释放给操作系统，线程2开始创建了一个新的arena， 但是线程1的300M却不能用了。</li><li>每个chunk至少8字节的开销很大</li><li>不定期分配长生命周期的内存容易造成内存碎片，不利于回收。 64位系统最好分配32M以上内存，这是使用mmap的阈值。</li></ul><p><a name="WKRFX"></a></p><h2 id="tcmalloc"><a href="#tcmalloc" class="headerlink" title="tcmalloc"></a>tcmalloc</h2><p>tcmalloc是Google开源的一个内存管理库， 作为glibc malloc的替代品。目前已经在chrome、safari等知名软件中运用。<br />根据官方测试报告，ptmalloc在一台2.8GHz的P4机器上（对于小对象）执行一次malloc及free大约需要300纳秒。而TCMalloc的版本同样的操作大约只需要50纳秒。<br><a name="zJPBO"></a></p><h3 id="小对象分配"><a href="#小对象分配" class="headerlink" title="小对象分配"></a>小对象分配</h3><ul><li>tcmalloc为每个线程分配了一个线程本地ThreadCache，小内存从ThreadCache分配，此外还有个中央堆（CentralCache），ThreadCache不够用的时候，会从CentralCache中获取空间放到ThreadCache中。</li><li>小对象（&lt;&#x3D;32K）从ThreadCache分配，大对象从CentralCache分配。大对象分配的空间都是4k页面对齐的，多个pages也能切割成多个小对象划分到ThreadCache中。<br /><a href="http://www.cnhalo.net/images/tcmalloc-smallsize-class.png"><img data-src="https://cdn.nlark.com/yuque/0/2019/jpeg/106206/1567665773357-3ec1b788-a18f-4446-9acf-c9ed0a2f221d.jpeg#align=left&display=inline&height=212&originHeight=212&originWidth=310&size=0&status=done&width=310" alt="MySQL 源码解读 -- 内存分配器"></a><br />小对象有将近170个不同的大小分类(class)，每个class有个该大小内存块的FreeList单链表，分配的时候先找到best fit的class，然后无锁的获取该链表首元素返回。如果链表中无空间了，则到CentralCache中划分几个页面并切割成该class的大小，放入链表中。</li></ul><p><a name="pt1RF"></a></p><h3 id="CentralCache分配管理"><a href="#CentralCache分配管理" class="headerlink" title="CentralCache分配管理"></a>CentralCache分配管理</h3><ul><li>大对象(&gt;32K)先4k对齐后，从CentralCache中分配。 CentralCache维护的PageHeap如下图所示， 数组中第256个元素是所有大于255个页面都挂到该链表中。<br /><a href="http://www.cnhalo.net/images/tcmalloc-pageheap.png"><img data-src="https://cdn.nlark.com/yuque/0/2019/jpeg/106206/1567665773371-efe6a41c-2c8e-421e-b7a2-c183bbdd75c1.jpeg#align=left&display=inline&height=308&originHeight=308&originWidth=449&size=0&status=done&width=449" alt="MySQL 源码解读 -- 内存分配器"></a></li><li>当best fit的页面链表中没有空闲空间时，则一直往更大的页面空间则，如果所有256个链表遍历后依然没有成功分配。 则使用sbrk, mmap, &#x2F;dev&#x2F;mem从系统中分配。</li><li>tcmalloc PageHeap管理的连续的页面被称为span.<br />如果span未分配， 则span是PageHeap中的一个链表元素<br />如果span已经分配，它可能是返回给应用程序的大对象， 或者已经被切割成多小对象，该小对象的size-class会被记录在span中</li><li>在32位系统中，使用一个中央数组(central array)映射了页面和span对应关系， 数组索引号是页面号，数组元素是页面所在的span。 在64位系统中，使用一个3-level radix tree记录了该映射关系。</li></ul><p><a name="e4QMq"></a></p><h3 id="回收"><a href="#回收" class="headerlink" title="回收"></a>回收</h3><ul><li>当一个object free的时候，会根据地址对齐计算所在的页面号，然后通过central array找到对应的span。</li><li>如果是小对象，span会告诉我们他的size class，然后把该对象插入当前线程的ThreadCache中。如果此时ThreadCache超过一个预算的值（默认2MB），则会使用垃圾回收机制把未使用的object从ThreadCache移动到CentralCache的central free lists中。</li><li>如果是大对象，span会告诉我们对象锁在的页面号范围。 假设这个范围是[p,q]， 先查找页面p-1和q+1所在的span，如果这些临近的span也是free的，则合并到[p,q]所在的span， 然后把这个span回收到PageHeap中。</li><li>CentralCache的central free lists类似ThreadCache的FreeList，不过它增加了一级结构，先根据size-class关联到spans的集合， 然后是对应span的object链表。如果span的链表中所有object已经free， 则span回收到PageHeap中。</li></ul><p><a name="7Z3gz"></a></p><h3 id="tcmalloc的改进"><a href="#tcmalloc的改进" class="headerlink" title="tcmalloc的改进"></a>tcmalloc的改进</h3><ul><li>ThreadCache会阶段性的回收内存到CentralCache里。 解决了ptmalloc2中arena之间不能迁移的问题。</li><li>Tcmalloc占用更少的额外空间。例如，分配N个8字节对象可能要使用大约8N * 1.01字节的空间。即，多用百分之一的空间。Ptmalloc2使用最少8字节描述一个chunk。</li><li>更快。小对象几乎无锁， &gt;32KB的对象从CentralCache中分配使用自旋锁。 并且&gt;32KB对象都是页面对齐分配，多线程的时候应尽量避免频繁分配，否则也会造成自旋锁的竞争和页面对齐造成的浪费。</li></ul><p><a name="0xir0"></a></p><h3 id="性能对比"><a href="#性能对比" class="headerlink" title="性能对比"></a>性能对比</h3><p>测试环境是2.4GHz dual Xeon，开启超线程，redhat9，glibc-2.3.2, 每个线程测试100万个操作。<br /><a href="http://www.cnhalo.net/images/tcmalloc-perf1.png"><img data-src="https://cdn.nlark.com/yuque/0/2019/jpeg/106206/1567665773374-8deadcd2-cd1c-4e44-bdee-72d8bf6bbd38.jpeg#align=left&display=inline&height=435&originHeight=435&originWidth=554&size=0&status=done&width=554" alt="MySQL 源码解读 -- 内存分配器"></a><br />上图中可以看到尤其是对于小内存的分配， tcmalloc有非常明显性能优势。<br /><a href="http://www.cnhalo.net/images/tcmalloc-perf2.png"><img data-src="https://cdn.nlark.com/yuque/0/2019/jpeg/106206/1567665773331-86821b81-28fe-4c81-b58b-7c471e821e5e.jpeg#align=left&display=inline&height=427&originHeight=427&originWidth=565&size=0&status=done&width=565" alt="MySQL 源码解读 -- 内存分配器"></a><br />上图可以看到随着线程数的增加，tcmalloc性能上也有明显的优势，并且相对平稳。<br><a name="dd20f91b"></a></p><h3 id="github-mysql优化"><a href="#github-mysql优化" class="headerlink" title="github mysql优化"></a>github mysql优化</h3><p><span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2Jsb2cvMTQyMi10Y21hbGxvYy1hbmQtbXlzcWw=">github使用tcmalloc后，mysql性能提升30%<i class="fa fa-external-link-alt"></i></span><br /></p><p><a name="dxUvl"></a></p><h2 id="Jemalloc"><a href="#Jemalloc" class="headerlink" title="Jemalloc"></a>Jemalloc</h2><p>jemalloc是facebook推出的， 最早的时候是freebsd的libc malloc实现。 目前在firefox、facebook服务器各种组件中大量使用。<br><a name="CbDhh"></a></p><h3 id="jemalloc原理"><a href="#jemalloc原理" class="headerlink" title="jemalloc原理"></a>jemalloc原理</h3><p><a name="JmNub"></a></p><h3 id="多线程"><a href="#多线程" class="headerlink" title="多线程"></a>多线程</h3><p>•线程与 arena 绑定<br />•以少量锁等待时间为代价，免去 arena<br>遍历</p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567667034582-01f5d226-dc85-4a07-889c-4db2c566d417.png#align=left&display=inline&height=184&name=image.png&originHeight=184&originWidth=306&size=19315&status=done&width=306" alt="image.png"><br /></p><p><a name="3Vnrj"></a></p><h4 id="分配"><a href="#分配" class="headerlink" title="分配"></a>分配</h4><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567667093024-e7a8de66-29d0-4ed5-883a-8c43d3358540.png#align=left&display=inline&height=220&name=image.png&originHeight=220&originWidth=492&size=24141&status=done&width=492" alt="image.png"></p><ul><li>与tcmalloc类似，每个线程同样在&lt;32KB的时候无锁使用线程本地cache。</li><li>Jemalloc在64bits系统上使用下面的size-class分类：<br />Small: [8], [16, 32, 48, …, 128], [192, 256, 320, …, 512], [768, 1024, 1280, …, 3840]<br />Large: [4 KiB, 8 KiB, 12 KiB, …, 4072 KiB]<br />Huge: [4 MiB, 8 MiB, 12 MiB, …]</li><li>small&#x2F;large对象查找metadata需要常量时间， huge对象通过全局红黑树在对数时间内查找。</li><li>虚拟内存被逻辑上分割成chunks（默认是4MB，1024个4k页），应用线程通过round-robin算法在第一次malloc的时候分配arena， 每个arena都是相互独立的，维护自己的chunks， chunk切割pages到small&#x2F;large对象。free()的内存总是返回到所属的arena中，而不管是哪个线程调用free()。</li></ul><p><a href="http://www.cnhalo.net/images/jemalloc-arena-chunk.png"><img data-src="https://cdn.nlark.com/yuque/0/2019/jpeg/106206/1567665773364-586e08b6-fbc9-4f7c-91d4-9a3861269d33.jpeg#align=left&display=inline&height=438&originHeight=438&originWidth=385&size=0&status=done&width=385" alt="MySQL 源码解读 -- 内存分配器"></a><br />上图可以看到每个arena管理的arena chunk结构， 开始的header主要是维护了一个page map（1024个页面关联的对象状态）， header下方就是它的页面空间。 Small对象被分到一起， metadata信息存放在起始位置。 large chunk相互独立，它的metadata信息存放在chunk header map中。</p><ul><li>通过arena分配的时候需要对arena bin（每个small size-class一个，细粒度）加锁，或arena本身加锁。<br />并且线程cache对象也会通过垃圾回收指数退让算法返回到arena中。<br /><a href="http://www.cnhalo.net/images/jemalloc-arena-thread-cache.png"><img data-src="https://cdn.nlark.com/yuque/0/2019/jpeg/106206/1567665773340-a19544d0-dde6-4b76-8407-46544edc453b.jpeg#align=left&display=inline&height=506&originHeight=506&originWidth=555&size=0&status=done&width=555" alt="MySQL 源码解读 -- 内存分配器"></a></li></ul><p><a name="tj32c"></a></p><h4 id="释放"><a href="#释放" class="headerlink" title="释放"></a>释放</h4><p>•Thread cache -&gt; Extent<br />•每进行一定次数（默认 228 次）的 malloc&#x2F;free，就对一个<br>cache_bin 进行 flush<br />•释放一定数量的 region 并调整单次获取的<br>region 数量<br />•Extent -&gt; Arena<br />•Extent 有四种状态：clean,<br>dirty, muzzy, retained<br />•达到一定的时间和 malloc&#x2F;free<br>次数后，进行垃圾回收<br />•相邻 extent_dirty 合并 à 加入 extend_muzzy à 相邻 extent_muzzy 合并<br />•相邻 extend_muzzy 合并 à 加入 extend_retained à mmap + PORT_NONE à 相邻 extend_muzzy 合并<br />•5.2 版本中， extend_muzzy 回收的默认时间间隔由<br>10s 改为 0</p><p><a name="Qqhnb"></a></p><h3 id="jemalloc的优化"><a href="#jemalloc的优化" class="headerlink" title="jemalloc的优化"></a>jemalloc的优化</h3><ul><li>Jmalloc小对象也根据size-class，但是它使用了低地址优先的策略，来降低内存碎片化。</li><li>Jemalloc大概需要2%的额外开销。（tcmalloc 1%， ptmalloc最少8B）</li><li>Jemalloc和tcmalloc类似的线程本地缓存，避免锁的竞争</li><li>相对未使用的页面，优先使用dirty page，提升缓存命中。</li></ul><p><a name="2uZsX"></a></p><h3 id="问题"><a href="#问题" class="headerlink" title="问题"></a>问题</h3><ul><li>高并发下的内存碎片依然没有完全解决</li><li>•只有时间间隔和 malloc&#x2F;free<br>都达到触发条件后，才有可能触发垃圾收集， <ul><li>eg. gc触发时间为10秒，malloc&#x2F;free 次数为 1000 次。 9 秒内进行了 100,000 次 malloc&#x2F;free，所以 gc 不会被触发，此<br>arena 缓存占用大量内存。复杂 query 产生的低次数，长周期内存占用可能引起此问题。</li><li>在5.2 版本中， 调整 extent 垃圾回收周期，调整为0， 保障了应用释放的内存彻底释放出去。</li></ul></li><li>•arena 之间内存无法共享，不同 arena<br>中的线程频繁进行内存申请与释放后，释放大量地址连续却不属于同一 arena （因此无法合并）的内存块。<ul><li>通过 ASAN 等内存检测工具无法发现此类情况</li></ul></li></ul><p><a name="Ot1d0"></a></p><h3 id="性能对比-1"><a href="#性能对比-1" class="headerlink" title="性能对比"></a>性能对比</h3><p><a href="http://www.cnhalo.net/images/jemalloc-throughput.png"><img data-src="https://cdn.nlark.com/yuque/0/2019/jpeg/106206/1567665773148-66459623-f15b-4314-8834-d710ef7b9b36.jpeg#align=left&display=inline&height=525&originHeight=525&originWidth=704&size=0&status=done&width=704" alt="MySQL 源码解读 -- 内存分配器"></a><br />上图是服务器吞吐量分别用6个malloc实现的对比数据，可以看到tcmalloc和jemalloc最好(facebook在2011年的测试结果，tcmalloc这里版本较旧)。<br />4.3.2 mysql优化<br />测试环境：2x Intel E5&#x2F;2.2Ghz with 8 real cores per socket，16 real cores， 开启hyper-threading， 总共32个vcpu。 16个table，每个5M row。<br />OLTP_RO测试包含5个select查询：select_ranges, select_order_ranges, select_distinct_ranges, select_sum_ranges,<br /><a href="http://www.cnhalo.net/images/jemalloc-mysql-perf.png"><img data-src="https://cdn.nlark.com/yuque/0/2019/jpeg/106206/1567665773338-fb25a731-a3c6-46ae-a996-f74dca690213.jpeg#align=left&display=inline&height=370&originHeight=370&originWidth=561&size=0&status=done&width=561" alt="MySQL 源码解读 -- 内存分配器"></a><br />可以看到在多核心或者多线程的场景下， jemalloc和tcmalloc带来的tps增加非常明显。<br><a name="d41d8cd9"></a></p><h1 id=""><a href="#" class="headerlink" title=""></a></h1><p><a name="ukL1y"></a></p><h1 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h1><p><span class="exturl" data-url="aHR0cDovL3d3dy52YWxsZXl0YWxrLm9yZy93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wMi9nbGliYyVFNSU4NiU4NSVFNSVBRCU5OCVFNyVBRSVBMSVFNyU5MCU4NnB0bWFsbG9jJUU2JUJBJTkwJUU0JUJCJUEzJUU3JUEwJTgxJUU1JTg4JTg2JUU2JTlFJTkwMS5wZGY=">glibc内存管理ptmalloc源代码分析<i class="fa fa-external-link-alt"></i></span><br /><span class="exturl" data-url="aHR0cDovL3d3dy5jbmJsb2dzLmNvbS92ZWN0b3IwMy9wLzUxODI3MzAuaHRtbA==">Inside jemalloc<i class="fa fa-external-link-alt"></i></span><br /><span class="exturl" data-url="aHR0cHM6Ly95cS5hbGl5dW4uY29tL2FydGljbGVzLzYwNDU=">tcmalloc浅析<i class="fa fa-external-link-alt"></i></span><br /><span class="exturl" data-url="aHR0cDovL2dvb2ctcGVyZnRvb2xzLnNvdXJjZWZvcmdlLm5ldC9kb2MvdGNtYWxsb2MuaHRtbA==">tcmalloc官方文档<i class="fa fa-external-link-alt"></i></span><br /><span class="exturl" data-url="aHR0cHM6Ly93d3cuZmFjZWJvb2suY29tL25vdGVzL2ZhY2Vib29rLWVuZ2luZWVyaW5nL3NjYWxhYmxlLW1lbW9yeS1hbGxvY2F0aW9uLXVzaW5nLWplbWFsbG9jLzQ4MDIyMjgwMzkxOQ==">Scalable memory allocation using jemalloc<i class="fa fa-external-link-alt"></i></span><br /><span class="exturl" data-url="aHR0cHM6Ly93d3cucGVyY29uYS5jb20vYmxvZy8yMDEzLzAzLzA4L215c3FsLXBlcmZvcm1hbmNlLWltcGFjdC1vZi1tZW1vcnktYWxsb2NhdG9ycy1wYXJ0LTIv">mysql-performance-impact-of-memory-allocators-part-2<i class="fa fa-external-link-alt"></i></span><br /><span class="exturl" data-url="aHR0cDovL3d3dy4zNjBkb2MuY29tL2NvbnRlbnQvMTMvMDkxNS8wOS84MzYzNTI3XzMxNDU0OTEyOC5zaHRtbA==">ptmalloc,tcmalloc和jemalloc内存分配策略研究<i class="fa fa-external-link-alt"></i></span><br /><span class="exturl" data-url="aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1SY1dwNXZ3R2xZVQ==">Tick Tock, malloc Needs a Clock<i class="fa fa-external-link-alt"></i></span><br><a name="25f9c7fa"></a></p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>在多线程环境使用tcmalloc和jemalloc效果非常明显。<br />当线程数量固定，不会频繁创建退出的时候， 可以使用jemalloc；反之使用tcmalloc可能是更好的选择。</p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/memory/allocator/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/memory/allocator/"/>
    <published>2019-06-20T11:42:57.000Z</published>
    <summary>MySQL 内存分配器对比笔记：ptmalloc/tcmalloc/jemalloc 在 PolarDB 压测下的内存占用与泄漏表现分析</summary>
    <title>MySQL 源码解读 -- 内存分配器</title>
    <updated>2026-06-09T08:46:25.955Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 初始化","description":"MySQL 源码初始化模块索引：指向参数加载 init_param 相关笔记，介绍系统变量初始化入口流程，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":1,"datePublished":"2019-06-19T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.954Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/init/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/init/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 初始化","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/init/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/init/init_param.html">init_param</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/init/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/init/index/"/>
    <published>2019-06-19T11:42:57.000Z</published>
    <summary>MySQL 源码初始化模块索引：指向参数加载 init_param 相关笔记，介绍系统变量初始化入口流程，更多细节与示例见正文。</summary>
    <title>MySQL 源码解读 -- 初始化</title>
    <updated>2026-06-09T08:46:25.954Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 参数初始化","description":"MySQL 参数初始化流程：load_defaults 读取配置与命令行，init_variable_default_paths 与 sys_var_init 等关键步骤","image":"https://ilongda.com/assets/init_param.svg","wordCount":127,"datePublished":"2019-06-19T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.955Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/init/init_param/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/init/init_param/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 参数初始化","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/init/init_param/"}]}</script><h1 id="参数初始化"><a href="#参数初始化" class="headerlink" title="参数初始化"></a>参数初始化</h1><p>整个加载参数的过程是, 读取配置文件, 加载到一个map中, 然后加载命令行参数, 覆盖到之前的map中, 然后生成global system variable&#x2F;status</p><p>讲解初始化函数 load_defaults 过程</p><p><img data-src="/assets/init_param.svg" alt="MySQL 源码解读 -- 参数初始化"></p><p>加载my_defaults_file 文件<br />search_default_file_with_ext</p><p>在最新的8.0.21 代码中</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">mysqld_main &#123;</span><br><span class="line">    init_variable_default_paths();   // 加载配置文件路径</span><br><span class="line"></span><br><span class="line">    ho_error = handle_early_options();  // 加载早期的参数</span><br><span class="line"></span><br><span class="line">    init_sql_statement_names();  // 填充System_status_var.com_stat</span><br><span class="line"></span><br><span class="line">    sys_var_init(); // 真正开始初始化各种参数</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/init/init_param/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/init/init_param/"/>
    <published>2019-06-19T11:42:57.000Z</published>
    <summary>MySQL 参数初始化流程：load_defaults 读取配置与命令行，init_variable_default_paths 与 sys_var_init 等关键步骤</summary>
    <title>MySQL 源码解读 -- 参数初始化</title>
    <updated>2026-06-09T08:46:25.955Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 基础结构之THD","description":"MySQL THD 线程描述符源码笔记：连接级核心结构，涵盖 DiagnosticsArea、MDL、资源组与事务状态等成员","image":"https://ilongda.com/img/my.jpg","wordCount":190,"datePublished":"2019-06-18T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.954Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/THD/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/THD/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 基础结构之THD","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/THD/"}]}</script><p>THD 是最复杂的数据结构</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">class THD : public MDL_context_owner,</span><br><span class="line">            public Query_arena,</span><br><span class="line">            public Open_tables_state &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>statement diagnoze area<br />thd-&gt;m_stmt_da</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">type = class Diagnostics_area &#123;</span><br><span class="line">  private:</span><br><span class="line">    Diagnostics_area *m_stacked_da;</span><br><span class="line">    MEM_ROOT m_condition_root;                 </span><br><span class="line">    Sql_condition_list m_conditions_list;             </span><br><span class="line">    List&lt;Sql_condition const&gt; m_preexisting_sql_conditions;       </span><br><span class="line">    bool m_is_sent;</span><br><span class="line">    bool m_can_overwrite_status;</span><br><span class="line">    bool m_allow_unlimited_conditions;          </span><br><span class="line">    Diagnostics_area::enum_diagnostics_status m_status;</span><br><span class="line">    char m_message_text[512];</span><br><span class="line">    char m_returned_sqlstate[6];                  </span><br><span class="line">    uint m_mysql_errno;</span><br><span class="line">    ulonglong m_affected_rows;</span><br><span class="line">    ulonglong m_last_insert_id;</span><br><span class="line">    uint m_last_statement_cond_count;</span><br><span class="line">    uint m_current_statement_cond_count;</span><br><span class="line">    uint m_current_statement_cond_count_by_sl[3];</span><br><span class="line">    ulong m_current_row_for_condition;</span><br><span class="line">    ulong m_saved_error_count;</span><br><span class="line">    ulong m_saved_warn_count;</span><br><span class="line">    </span><br><span class="line">    typedef I_P_List&lt;</span><br><span class="line">      Sql_condition,</span><br><span class="line">      I_P_List_adapter&lt;Sql_condition, &amp;Sql_condition::m_next_condition,</span><br><span class="line">                       &amp;Sql_condition::m_prev_condition&gt;,</span><br><span class="line">      I_P_List_counter, I_P_List_fast_push_back&lt;Sql_condition&gt;&gt;</span><br><span class="line">      Sql_condition_list;</span><br></pre></td></tr></table></figure><p>thd-&gt;m_resource_group_ctx</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">type = struct resourcegroups::Resource_group_ctx &#123;</span><br><span class="line">    resourcegroups::Resource_group *m_cur_resource_group;</span><br><span class="line">    char m_switch_resource_group_str[65];</span><br><span class="line">    int m_warn;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">type = class resourcegroups::Resource_group &#123;</span><br><span class="line">  private:</span><br><span class="line">    std::string m_name;</span><br><span class="line">    resourcegroups::Type m_type;</span><br><span class="line">    bool m_enabled;</span><br><span class="line">    resourcegroups::Thread_resource_control m_thread_resource_control;</span><br><span class="line">    std::set&lt;unsigned long long&gt; m_pfs_thread_id_set;</span><br><span class="line">    std::mutex m_set_mutex;</span><br><span class="line"></span><br><span class="line">  public:</span><br><span class="line">    Resource_group(const std::string &amp;, resourcegroups::Type, bool);</span><br><span class="line">。。。</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/THD/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/THD/"/>
    <published>2019-06-18T11:42:57.000Z</published>
    <summary>MySQL THD 线程描述符源码笔记：连接级核心结构，涵盖 DiagnosticsArea、MDL、资源组与事务状态等成员</summary>
    <title>MySQL 源码解读 -- 基础结构之THD</title>
    <updated>2026-06-09T08:46:25.954Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- MySQL 变量","description":"MySQL 源码变量梳理：以 xmind 思维导图整理 MySQL 各类系统变量与配置项关系，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://ilongda.com/img/my.jpg","wordCount":2,"datePublished":"2019-06-18T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.954Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/diagram/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/diagram/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- MySQL 变量","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/diagram/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/diagram/variable.xmind">variable.xmind</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/diagram/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/diagram/index/"/>
    <published>2019-06-18T11:42:57.000Z</published>
    <summary>MySQL 源码变量梳理：以 xmind 思维导图整理 MySQL 各类系统变量与配置项关系，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>MySQL 源码解读 -- MySQL 变量</title>
    <updated>2026-06-09T08:46:25.954Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 基础结构之Table","description":"MySQL 源码 Raw_table 结构笔记：封装 TABLE_LIST，提供对表对象的底层接口操作说明，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":25,"datePublished":"2019-06-17T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.953Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/TABLE/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/TABLE/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 基础结构之Table","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/TABLE/"}]}</script><h2 id="Raw-table"><a href="#Raw-table" class="headerlink" title="Raw_table"></a>Raw_table</h2><p>Raw_table 就只是存储 TABLE_LIST,  提供接口操作table, recorded</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">class Raw_table &#123;.</span><br><span class="line">    TABLE_LIST m_table_list;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/TABLE/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/TABLE/"/>
    <published>2019-06-17T11:42:57.000Z</published>
    <summary>MySQL 源码 Raw_table 结构笔记：封装 TABLE_LIST，提供对表对象的底层接口操作说明，更多细节与示例见正文。</summary>
    <title>MySQL 源码解读 -- 基础结构之Table</title>
    <updated>2026-06-09T08:46:25.953Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 基础结构之Schema","description":"MySQL 数据字典 Schema 源码笔记：Schema 接口与 Schema_impl 实现，及 Schemata 元数据表的创建与访问","image":"https://ilongda.com/img/my.jpg","wordCount":130,"datePublished":"2019-06-16T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.953Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/Schema/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/Schema/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 基础结构之Schema","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/Schema/"}]}</script><h2 id="Schema"><a href="#Schema" class="headerlink" title="Schema"></a>Schema</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Schema 是接口函数, 依赖实现</span><br><span class="line">class Schema : virtual public Entity_object &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Schema-impl"><a href="#Schema-impl" class="headerlink" title="Schema_impl"></a>Schema_impl</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">class Schema_impl : public Entity_object_impl, public Schema &#123;</span><br><span class="line">  // Fields</span><br><span class="line">  ulonglong m_created;</span><br><span class="line">  ulonglong m_last_altered;</span><br><span class="line">  enum_encryption_type m_default_encryption;</span><br><span class="line"></span><br><span class="line">  // The se_private_data column of a schema might be used by several storage</span><br><span class="line">  // engines at the same time as the schema is not associated with any specific</span><br><span class="line">  // engine. So to avoid any naming conflicts, we have the convention that the</span><br><span class="line">  // keys should be prefixed with the engine name.</span><br><span class="line">  Properties_impl m_se_private_data;</span><br><span class="line"></span><br><span class="line">  // References to other objects</span><br><span class="line">  Object_id m_default_collation_id;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="Schemata"><a href="#Schemata" class="headerlink" title="Schemata"></a>Schemata</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">class Schemata : public Entity_object_table_impl &#123;</span><br><span class="line">    virtual Schema *create_entity_object(const Raw_record &amp;) const;</span><br><span class="line">    static Object_key *create_key_by_catalog_id(Object_id catalog_id);</span><br><span class="line">    </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/Schema/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/Schema/"/>
    <published>2019-06-16T11:42:57.000Z</published>
    <summary>MySQL 数据字典 Schema 源码笔记：Schema 接口与 Schema_impl 实现，及 Schemata 元数据表的创建与访问</summary>
    <title>MySQL 源码解读 -- 基础结构之Schema</title>
    <updated>2026-06-09T08:46:25.953Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 基础结构之MDL","description":"MySQL 元数据锁 MDL 源码解读：MDL_context、ticket 存储及语句/事务/显式锁的作用域与等待机制","image":"https://ilongda.com/img/my.jpg","wordCount":804,"datePublished":"2019-06-15T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.953Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/MDL/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/MDL/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 基础结构之MDL","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/MDL/"}]}</script><h2 id="MDL-context"><a href="#MDL-context" class="headerlink" title="MDL_context"></a>MDL_context</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">class MDL_context &#123;</span><br><span class="line"></span><br><span class="line">  /**</span><br><span class="line">    If our request for a lock is scheduled, or aborted by the deadlock</span><br><span class="line">    detector, the result is recorded in this class.</span><br><span class="line">  */</span><br><span class="line">  MDL_wait m_wait;</span><br><span class="line"></span><br><span class="line">  MDL_ticket_store m_ticket_store;</span><br><span class="line"></span><br><span class="line">  MDL_context_owner *m_owner;</span><br><span class="line"></span><br><span class="line">  /**</span><br><span class="line">    Tell the deadlock detector what metadata lock or table</span><br><span class="line">    definition cache entry this session is waiting for.</span><br><span class="line">    In principle, this is redundant, as information can be found</span><br><span class="line">    by inspecting waiting queues, but we&#x27;d very much like it to be</span><br><span class="line">    readily available to the wait-for graph iterator.</span><br><span class="line">   */</span><br><span class="line">  MDL_wait_for_subgraph *m_waiting_for;</span><br><span class="line">  /**</span><br><span class="line">    Thread&#x27;s pins (a.k.a. hazard pointers) to be used by lock-free</span><br><span class="line">    implementation of MDL_map::m_locks container. NULL if pins are</span><br><span class="line">    not yet allocated from container&#x27;s pinbox.</span><br><span class="line">  */</span><br><span class="line">  LF_PINS *m_pins;</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="MDL-ticket-store"><a href="#MDL-ticket-store" class="headerlink" title="MDL_ticket_store"></a>MDL_ticket_store</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line">    Lists of all MDL tickets acquired by this connection.</span><br><span class="line"></span><br><span class="line">    Lists of MDL tickets:</span><br><span class="line">    ---------------------</span><br><span class="line">    The entire set of locks acquired by a connection can be separated</span><br><span class="line">    in three subsets according to their duration: locks released at</span><br><span class="line">    the end of statement, at the end of transaction and locks are</span><br><span class="line">    released explicitly.</span><br><span class="line"></span><br><span class="line">    Statement and transactional locks are locks with automatic scope.</span><br><span class="line">    They are accumulated in the course of a transaction, and released</span><br><span class="line">    either at the end of uppermost statement (for statement locks) or</span><br><span class="line">    on COMMIT, ROLLBACK or ROLLBACK TO SAVEPOINT (for transactional</span><br><span class="line">    locks). They must not be (and never are) released manually,</span><br><span class="line">    i.e. with release_lock() call.</span><br><span class="line"></span><br><span class="line">    Tickets with explicit duration are taken for locks that span</span><br><span class="line">    multiple transactions or savepoints.</span><br><span class="line">    These are: HANDLER SQL locks (HANDLER SQL is</span><br><span class="line">    transaction-agnostic), LOCK TABLES locks (you can COMMIT/etc</span><br><span class="line">    under LOCK TABLES, and the locked tables stay locked), user level</span><br><span class="line">    locks (GET_LOCK()/RELEASE_LOCK() functions) and</span><br><span class="line">    locks implementing &quot;global read lock&quot;.</span><br><span class="line"></span><br><span class="line">    Statement/transactional locks are always prepended to the</span><br><span class="line">    beginning of the appropriate list. In other words, they are</span><br><span class="line">    stored in reverse temporal order. Thus, when we rollback to</span><br><span class="line">    a savepoint, we start popping and releasing tickets from the</span><br><span class="line">    front until we reach the last ticket acquired after the savepoint.</span><br><span class="line"></span><br><span class="line">    Locks with explicit duration are not stored in any</span><br><span class="line">    particular order, and among each other can be split into</span><br><span class="line">    four sets:</span><br><span class="line">    - LOCK TABLES locks</span><br><span class="line">    - User-level locks</span><br><span class="line">    - HANDLER locks</span><br><span class="line">    - GLOBAL READ LOCK locks</span><br><span class="line">  */</span><br><span class="line">/**</span><br><span class="line">  Keep track of MDL_ticket for different durations. Maintains a</span><br><span class="line">  hash-based secondary index into the linked lists, to speed up access</span><br><span class="line">  by MDL_key.</span><br><span class="line"> */</span><br><span class="line">class MDL_ticket_store &#123;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="MDL-ticket"><a href="#MDL-ticket" class="headerlink" title="MDL_ticket"></a>MDL_ticket</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">class MDL_ticket : public MDL_wait_for_subgraph &#123;</span><br><span class="line">    /**</span><br><span class="line">    Pointers for participating in the list of lock requests for this context.</span><br><span class="line">    Context private.</span><br><span class="line">  */</span><br><span class="line">  MDL_ticket *next_in_context;</span><br><span class="line">  MDL_ticket **prev_in_context;</span><br><span class="line"></span><br><span class="line">  /**</span><br><span class="line">    Pointers for participating in the list of satisfied/pending requests</span><br><span class="line">    for the lock. Externally accessible.</span><br><span class="line">  */</span><br><span class="line">  MDL_ticket *next_in_lock;</span><br><span class="line">  MDL_ticket **prev_in_lock;</span><br><span class="line"></span><br><span class="line">  /** If current thread is worker thread, points to ticket of leader thread,</span><br><span class="line">   * NULL otherwise.</span><br><span class="line">   */</span><br><span class="line">  MDL_ticket *m_leader_ticket;</span><br><span class="line"></span><br><span class="line">  // 核心就这几个成员</span><br><span class="line">  /** Type of metadata lock. Externally accessible. */</span><br><span class="line">  enum enum_mdl_type m_type;</span><br><span class="line">#ifndef DBUG_OFF</span><br><span class="line">  /**</span><br><span class="line">    Duration of lock represented by this ticket.</span><br><span class="line">    Context private. Debug-only.</span><br><span class="line">  */</span><br><span class="line">  enum_mdl_duration m_duration;</span><br><span class="line">#endif</span><br><span class="line">  /**</span><br><span class="line">    Context of the owner of the metadata lock ticket. Externally accessible.</span><br><span class="line">  */</span><br><span class="line">  MDL_context *m_ctx;</span><br><span class="line"></span><br><span class="line">  /**</span><br><span class="line">    Pointer to the lock object for this lock ticket. Externally accessible.</span><br><span class="line">  */</span><br><span class="line">  MDL_lock *m_lock;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line">  A pending metadata lock request.</span><br><span class="line"></span><br><span class="line">  A lock request and a granted metadata lock are represented by</span><br><span class="line">  different classes because they have different allocation</span><br><span class="line">  sites and hence different lifetimes. The allocation of lock requests is</span><br><span class="line">  controlled from outside of the MDL subsystem, while allocation of granted</span><br><span class="line">  locks (tickets) is controlled within the MDL subsystem.</span><br><span class="line">*/</span><br><span class="line"></span><br><span class="line">class MDL_request &#123;</span><br><span class="line"> /** Type of metadata lock. */</span><br><span class="line">  enum_mdl_type type&#123;MDL_INTENTION_EXCLUSIVE&#125;;</span><br><span class="line">  /** Duration for requested lock. */</span><br><span class="line">  enum_mdl_duration duration&#123;MDL_STATEMENT&#125;;</span><br><span class="line"></span><br><span class="line">  /**</span><br><span class="line">    Pointers for participating in the list of lock requests for this context.</span><br><span class="line">  */</span><br><span class="line">  MDL_request *next_in_list&#123;nullptr&#125;;</span><br><span class="line">  MDL_request **prev_in_list&#123;nullptr&#125;;</span><br><span class="line">  /**</span><br><span class="line">    Pointer to the lock ticket object for this lock request.</span><br><span class="line">    Valid only if this lock request is satisfied.</span><br><span class="line">  */</span><br><span class="line">  MDL_ticket *ticket&#123;nullptr&#125;;</span><br><span class="line"></span><br><span class="line">  /** A lock is requested based on a fully qualified name and type. */</span><br><span class="line">  MDL_key key;</span><br><span class="line"></span><br><span class="line">  bool m_is_for_delegation&#123;false&#125;; // On if this request is for PQ leader</span><br><span class="line"></span><br><span class="line">  const char *m_src_file&#123;nullptr&#125;;</span><br><span class="line">  uint m_src_line&#123;0&#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/MDL/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/MDL/"/>
    <published>2019-06-15T11:42:57.000Z</published>
    <summary>MySQL 元数据锁 MDL 源码解读：MDL_context、ticket 存储及语句/事务/显式锁的作用域与等待机制</summary>
    <title>MySQL 源码解读 -- 基础结构之MDL</title>
    <updated>2026-06-09T08:46:25.953Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 基础结构之Lex","description":"MySQL 源码 LEX 结构笔记：梳理 SELECT_LEX、SELECT_LEX_UNIT 在 UNION 与子查询中的层次与嵌套关系","image":"https://ilongda.com/img/my.jpg","wordCount":1380,"datePublished":"2019-06-14T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.953Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/LEX/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/LEX/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 基础结构之Lex","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/LEX/"}]}</script><h2 id="SELECT-LEX-SELECT-LEX-UNIT-关系"><a href="#SELECT-LEX-SELECT-LEX-UNIT-关系" class="headerlink" title="SELECT_LEX &amp; SELECT_LEX_UNIT 关系"></a>SELECT_LEX &amp; SELECT_LEX_UNIT 关系</h2><p>select_lex_unit 表示一个expression， 在union的情况下可以包含多个select_lex<br>但在subquery的情况下， 一个新的subquery 会引入一个新的SELECT_LEX_UNIT</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line">SELECT * FROM t1;</span><br><span class="line"></span><br><span class="line">  unit</span><br><span class="line">   |</span><br><span class="line">   V</span><br><span class="line"> select t1</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> SELECT * FROM t1 UNION SELECT * FROM t2;</span><br><span class="line">                unit</span><br><span class="line">                 |</span><br><span class="line">                 V         next</span><br><span class="line">               select t1 --------&gt; select t2</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">SELECT * FROM t1 where c1 in (select * from t11 union select * from t12);</span><br><span class="line">                </span><br><span class="line">                unit</span><br><span class="line">                 |</span><br><span class="line">                 V</span><br><span class="line">              select t1</span><br><span class="line">                 |</span><br><span class="line">                 V</span><br><span class="line">               unit1.1</span><br><span class="line">                 |</span><br><span class="line">                 V         next</span><br><span class="line">              select t11 ---------&gt; select t12</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">SELECT * FROM t1 WHERE c1 in (SELECT * FROM t11) and c2 in (SELECT * FROM t12);</span><br><span class="line"></span><br><span class="line">               unit1</span><br><span class="line">                |</span><br><span class="line">                V</span><br><span class="line">              select t1</span><br><span class="line">                |</span><br><span class="line">                V         next</span><br><span class="line">                unit1.1  ------&gt; unit1.2</span><br><span class="line">                  |                |</span><br><span class="line">                  V                V</span><br><span class="line">               select t11       select t12</span><br><span class="line"></span><br><span class="line">At last, i give a sample in sql_lex.h.        </span><br><span class="line"></span><br><span class="line">   select *</span><br><span class="line">     from table1</span><br><span class="line">     where table1.field IN (select * from table1_1_1 union</span><br><span class="line">                            select * from table1_1_2)</span><br><span class="line">     union</span><br><span class="line">   select *</span><br><span class="line">     from table2</span><br><span class="line">     where table2.field=(select (select f1 from table2_1_1_1_1</span><br><span class="line">                                   where table2_1_1_1_1.f2=table2_1_1.f3)</span><br><span class="line">                           from table2_1_1</span><br><span class="line">                           where table2_1_1.f1=table2.f2)</span><br><span class="line">     union</span><br><span class="line">   select * from table3;</span><br><span class="line"></span><br><span class="line">                  unit</span><br><span class="line">                   |</span><br><span class="line">                   V</span><br><span class="line">                select table1------------&gt; select table2 ------------&gt;select table3</span><br><span class="line">                   |                             |</span><br><span class="line">                   V                             |__________</span><br><span class="line">                 unit1                                     |</span><br><span class="line">                   |                                       |</span><br><span class="line">                   V                                       |</span><br><span class="line">            select table 1_1_1---&gt;select table 1_1_2       V</span><br><span class="line">                                                          unit2</span><br><span class="line">                                                           |</span><br><span class="line">                                                           V</span><br><span class="line">                                                  select table2_1_1</span><br><span class="line">                                                           |</span><br><span class="line">                                                           V</span><br><span class="line">                                                         unit2.1</span><br><span class="line">                                                           |</span><br><span class="line">                                                           V</span><br><span class="line">                                                   select table2_1_1_1_1</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="lex-select-lex-select-lex-unit-关系"><a href="#lex-select-lex-select-lex-unit-关系" class="headerlink" title="lex &amp; select_lex &amp; select_lex_unit 关系"></a>lex &amp; select_lex &amp; select_lex_unit 关系</h2><p>select_lex-&gt;parent_lex &#x3D; lex;<br>lex-&gt;select_lex &#x3D; select_lex;<br>lex-&gt;unit &#x3D; select_lex-&gt;master;<br>lex-&gt;m_current_select &#x3D; lex-&gt;select_lex;</p><p>SELECT_LEX_UNIT-&gt;master &#x3D; outer_SELECT_LEX;<br>SELECT_LEX_UNIT-&gt;slave &#x3D; select_lex;<br>SELECT_LEX_UNIT-&gt;next &#x3D; out_SELECT_LEX-&gt;slave;  &#x2F;&#x2F; 就是将SELECT_LEX_UNIT 组成一个链表<br>SELECT_LEX_UNIT-&gt;prev &#x3D; &amp;out_SELECT_LEX-&gt;slave;</p><p>SELECT_LEX-&gt;master &#x3D; out_SLECT_LEX_UNIT;<br>out_SLECT_LEX_UNIT-&gt;slave &#x3D; SELECT_LEX;<br>SELECT_LEX-&gt;next &#x3D; out_SELECT_LEX_UNIT-&gt;next;<br>SELECT_LEX-&gt;prev &#x3D; &amp;out_SELECT_LEX_UNIT-&gt;slave;<br>SELECT_LEX-&gt;link_next &#x3D; LEX-&gt;all_selects_list;      &#x2F;&#x2F;在lex 下全部串起来<br>SELECT_LEX-&gt;link_prev &#x3D; &amp;LEX-&gt;all_slects_list;<br>LEX-&gt;all_slects_list &#x3D; 指向第一个SELECT_LEX</p><p>SELECT_LEX-&gt;prev_query_block &#x3D; LEX-&gt;all_query_blocks_last;  &#x2F;&#x2F;他们指向第一个SELECT_LEX<br>LEX-&gt;all_query_blocks_last &#x3D; SELECT_LEX-&gt;next_query_block;</p><p>class SELECT_LEX_UNIT {<br>  &#x2F;**<br>    Intrusive double-linked list of all query expressions<br>    immediately contained within the same query block.<br>  *&#x2F;<br>  SELECT_LEX_UNIT *next;<br>  SELECT_LEX_UNIT **prev;</p><p>  &#x2F;**<br>    The query block wherein this query expression is contained,<br>    NULL if the query block is the outer-most one.<br>  *&#x2F;<br>  SELECT_LEX *master;<br>  &#x2F;&#x2F;&#x2F; The first query block in this query expression.<br>  SELECT_LEX *slave;<br>  &#x2F;&#x2F;&#x2F; @return the query block this query expression belongs to as subquery<br>  SELECT_LEX *outer_select() const { return master; }<br>  &#x2F;&#x2F;&#x2F; @return the first query block inside this query expression<br>  SELECT_LEX *first_select() const { return slave; }</p><p>  &#x2F;**<br>    Helper query block for query expression with UNION or multi-level<br>    ORDER BY&#x2F;LIMIT<br>  <em>&#x2F;<br>  SELECT_LEX <em>fake_select_lex;<br>  &#x2F;</em></em><br>    SELECT_LEX that stores LIMIT and OFFSET for UNION ALL when no<br>    fake_select_lex is used.<br>  <em>&#x2F;<br>  SELECT_LEX <em>saved_fake_select_lex;<br>  &#x2F;</em></em><br>     Points to last query block which has UNION DISTINCT on its left.<br>     In a list of UNIONed blocks, UNION is left-associative; so UNION DISTINCT<br>     eliminates duplicates in all blocks up to the first one on its right<br>     included. Which is why we only need to remember that query block.<br>  *&#x2F;<br>  SELECT_LEX *union_distinct;</p><p>  &#x2F;**<br>     First query block (in this UNION) which references the CTE.<br>     NULL if not the query expression of a recursive CTE.<br>  *&#x2F;<br>  SELECT_LEX *first_recursive;</p><p>  …<br>}</p><p>SELECT_LEX{<br>  &#x2F;**<br>    Intrusive double-linked list of all query blocks within the same<br>    query expression.<br>  *&#x2F;<br>  SELECT_LEX *next;<br>  SELECT_LEX **prev;</p><p>  &#x2F;&#x2F;&#x2F; The query expression containing this query block.<br>  SELECT_LEX_UNIT *master;<br>  &#x2F;&#x2F;&#x2F; The first query expression contained within this query block.<br>  SELECT_LEX_UNIT *slave;</p><p>  &#x2F;&#x2F;&#x2F; Intrusive double-linked global list of query blocks.<br>  SELECT_LEX *link_next;<br>  SELECT_LEX <strong>link_prev;  &#x2F;&#x2F; 用于link LEX-&gt;all_selects_list<br>  &#x2F;</strong><br>   For plan clone, link each other between original SELECT_LEX and<br>   its clone<br>  *&#x2F;<br>  SELECT_LEX *m_clone_source;</p><p>  SELECT_LEX *next_query_block{nullptr};  &#x2F;&#x2F; 链表 指向LEX-&gt;all_query_blocks_last<br>  SELECT_LEX **prev_query_block{nullptr};</p><p>}</p><p>LEX {<br>  SELECT_LEX_UNIT *unit;  &#x2F;&#x2F;&#x2F;&lt; Outer-most query expression<br>  &#x2F;&#x2F;&#x2F; @todo: select_lex can be replaced with unit-&gt;first-select()<br>  SELECT_LEX *select_lex;        &#x2F;&#x2F;&#x2F;&lt; First query block<br>  SELECT_LEX *all_selects_list;  &#x2F;&#x2F;&#x2F;&lt; List of all query blocks<br>}</p><h2 id="LEX"><a href="#LEX" class="headerlink" title="LEX"></a>LEX</h2><p>推荐参考 <span class="exturl" data-url="aHR0cHM6Ly9kZXYubXlzcWwuY29tL2RvYy9kZXYvbXlzcWwtc2VydmVyLzguMC4yMC9zdHJ1Y3RMRVguaHRtbA==">https://dev.mysql.com/doc/dev/mysql-server/8.0.20/structLEX.html<i class="fa fa-external-link-alt"></i></span></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line">struct LEX : </span><br><span class="line">public Query_tables_list, public im::Query_blocks_list &#123;</span><br><span class="line"></span><br><span class="line">  SELECT_LEX_UNIT *unit;  ///&lt; Outer-most query expression</span><br><span class="line">  /// @todo: select_lex can be replaced with unit-&gt;first-select()</span><br><span class="line">  SELECT_LEX *select_lex;        ///&lt; First query block</span><br><span class="line">  SELECT_LEX *all_selects_list;  ///&lt; List of all query blocks</span><br><span class="line"> private:</span><br><span class="line">  /* current SELECT_LEX in parsing */</span><br><span class="line">  SELECT_LEX *m_current_select;</span><br><span class="line"></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class Query_blocks_list &#123;</span><br><span class="line"> public:</span><br><span class="line">  SELECT_LEX *all_query_blocks;</span><br><span class="line">  SELECT_LEX **all_query_blocks_last;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">class Query_tables_list &#123;</span><br><span class="line">  /**</span><br><span class="line">    SQL command for this statement. Part of this class since the</span><br><span class="line">    process of opening and locking tables for the statement needs</span><br><span class="line">    this information to determine correct type of lock for some of</span><br><span class="line">    the tables.</span><br><span class="line">  */</span><br><span class="line">  enum_sql_command sql_command;</span><br><span class="line">  /* Global list of all tables used by this statement */</span><br><span class="line">  TABLE_LIST *query_tables;</span><br><span class="line">  /* Pointer to next_global member of last element in the previous list. */</span><br><span class="line">  TABLE_LIST **query_tables_last;</span><br><span class="line">  /*</span><br><span class="line">    If non-0 then indicates that query requires prelocking and points to</span><br><span class="line">    next_global member of last own element in query table list (i.e. last</span><br><span class="line">    table which was not added to it as part of preparation to prelocking).</span><br><span class="line">    0 - indicates that this query does not need prelocking.</span><br><span class="line">  */</span><br><span class="line">  TABLE_LIST **query_tables_own_last;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="SELECT-LEX"><a href="#SELECT-LEX" class="headerlink" title="SELECT_LEX"></a>SELECT_LEX</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">ptype select_lex-&gt;context                </span><br><span class="line">type = struct Name_resolution_context &#123;</span><br><span class="line">   Name_resolution_context *outer_context;    </span><br><span class="line">   Name_resolution_context *next_context;    </span><br><span class="line">   TABLE_LIST *table_list;                        </span><br><span class="line">   TABLE_LIST *first_name_resolution_table;</span><br><span class="line">   TABLE_LIST *last_name_resolution_table;</span><br><span class="line">   SELECT_LEX *select_lex;</span><br><span class="line">   bool view_error_handler;</span><br><span class="line">   TABLE_LIST *view_error_handler_arg;  </span><br><span class="line">   bool resolve_in_select_list;              </span><br><span class="line">   Security_context *security_ctx;</span><br><span class="line"> public:                  </span><br><span class="line">   Name_resolution_context(void);      </span><br><span class="line">   void init(void);</span><br><span class="line">   void resolve_in_table_list_only(TABLE_LIST *);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>SELECT_LEX</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br></pre></td><td class="code"><pre><span class="line">class SELECT_LEX  :</span><br><span class="line">&#123;</span><br><span class="line">  private:</span><br><span class="line">    SELECT_LEX *next;      </span><br><span class="line">    SELECT_LEX **prev;</span><br><span class="line">    SELECT_LEX_UNIT *master;</span><br><span class="line">    SELECT_LEX_UNIT *slave;                       </span><br><span class="line">    SELECT_LEX *link_next;</span><br><span class="line">    SELECT_LEX **link_prev;                 </span><br><span class="line">    Query_result *m_query_result;     </span><br><span class="line">    ulonglong m_base_options;            </span><br><span class="line">    ulonglong m_active_options;</span><br><span class="line">  public:</span><br><span class="line">    uint8 uncacheable;   </span><br><span class="line">    bool skip_local_transforms;</span><br><span class="line">    sub_select_type linkage;</span><br><span class="line">    bool no_table_names_allowed;</span><br><span class="line">    Name_resolution_context context;</span><br><span class="line">    Name_resolution_context *first_context;             </span><br><span class="line">    SELECT_LEX::Resolve_place resolve_place;</span><br><span class="line">    TABLE_LIST *resolve_nest;</span><br><span class="line">    bool semijoin_disallowed;</span><br><span class="line">    char *db;</span><br><span class="line"> private:                   </span><br><span class="line">    Item *m_where_cond;</span><br><span class="line">    Item *m_having_cond;   </span><br><span class="line">  public:             </span><br><span class="line">    Item::cond_result cond_value;</span><br><span class="line">    Item::cond_result having_value;               </span><br><span class="line">    LEX *parent_lex;      </span><br><span class="line">    olap_type olap;                         </span><br><span class="line">    SQL_I_List&lt;TABLE_LIST&gt; table_list;</span><br><span class="line">    SQL_I_List&lt;ORDER&gt; group_list;        </span><br><span class="line">    Group_list_ptrs *group_list_ptrs;</span><br><span class="line">    List&lt;Window&gt; m_windows;</span><br><span class="line">    List&lt;Item&gt; item_list;</span><br><span class="line">    bool is_item_list_lookup;  </span><br><span class="line">    int hidden_group_field_count;</span><br><span class="line">    List&lt;Item&gt; &amp;fields_list;    </span><br><span class="line">    List&lt;Item&gt; all_fields;          </span><br><span class="line">    List&lt;Item&gt; all_inner_ref_fields;                    </span><br><span class="line">    List&lt;Item_func_match&gt; *ftfunc_list;     </span><br><span class="line">    List&lt;Item_func_match&gt; ftfunc_list_alloc;</span><br><span class="line">    JOIN *join;              </span><br><span class="line">    List&lt;TABLE_LIST&gt; top_join_list;</span><br><span class="line">    List&lt;TABLE_LIST&gt; *join_list;</span><br><span class="line">    TABLE_LIST *embedding;</span><br><span class="line">    List&lt;TABLE_LIST&gt; sj_nests;</span><br><span class="line">    TABLE_LIST *leaf_tables;</span><br><span class="line">    uint leaf_table_count;       </span><br><span class="line">    uint derived_table_count;                     </span><br><span class="line">    uint table_func_count;</span><br><span class="line">    uint materialized_derived_table_count;  </span><br><span class="line">    bool has_sj_nests;                </span><br><span class="line">    uint partitioned_table_count;        </span><br><span class="line">    SQL_I_List&lt;ORDER&gt; order_list;    </span><br><span class="line">    Group_list_ptrs *order_list_ptrs;</span><br><span class="line">    Item *select_limit;  </span><br><span class="line">    Item *offset_limit;        </span><br><span class="line">    Ref_item_array base_ref_items;</span><br><span class="line">    uint select_n_having_items; </span><br><span class="line">    uint cond_count;                </span><br><span class="line">    uint between_count;                                 </span><br><span class="line">    uint max_equal_elems;                   </span><br><span class="line">    uint select_n_where_fields;             </span><br><span class="line">    enum_parsing_context parsing_place;</span><br><span class="line">    uint in_sum_expr; </span><br><span class="line">    bool with_sum_func;                    </span><br><span class="line">    uint n_sum_items;     </span><br><span class="line">    uint n_child_sum_items;    </span><br><span class="line">    uint select_number;                </span><br><span class="line">    int nest_level;                  </span><br><span class="line">    Item_sum *inner_sum_func_list;     </span><br><span class="line">    uint with_wild;                     </span><br><span class="line">    bool braces;</span><br><span class="line">    bool having_fix_field;            </span><br><span class="line">    bool group_fix_field;              </span><br><span class="line">    List&lt;Item_outer_ref&gt; inner_refs_list; </span><br><span class="line">    bool explicit_limit;             </span><br><span class="line">    bool subquery_in_having;                                  </span><br><span class="line">    bool first_execution;              </span><br><span class="line">    bool sj_pullout_done;                         </span><br><span class="line">    bool exclude_from_table_unique_test;</span><br><span class="line">    bool allow_merge_derived;                     </span><br><span class="line">    TABLE_LIST *recursive_reference;       </span><br><span class="line">    SELECT_LEX_UNIT *recursive_dummy_unit;   </span><br><span class="line">    table_map select_list_tables;           </span><br><span class="line">    table_map outer_join;              </span><br><span class="line">    Opt_hints_qb *opt_hints_qb;  </span><br><span class="line">    </span><br><span class="line">    TABLE_LIST *end_lateral_table;         </span><br><span class="line">    bool is_parallel_unsafe;</span><br><span class="line">    StringBuffer&lt;1024&gt; printed_query_before_pqplan;</span><br><span class="line">  private:                             </span><br><span class="line">    bool m_agg_func_used;            </span><br><span class="line">    bool m_json_agg_func_used;         </span><br><span class="line">    bool m_empty_query;                 </span><br><span class="line">    static const char *type_str[8];</span><br><span class="line">    Mem_root_array&lt;Item_exists_subselect*&gt; *sj_candidates;</span><br><span class="line">  public:                              </span><br><span class="line">    int hidden_order_field_count;         </span><br><span class="line"></span><br><span class="line">    Item * where_cond(void) const;                            </span><br><span class="line">    void set_where_cond(Item *);       </span><br><span class="line">    Item * having_cond(void) const;               </span><br><span class="line">    void set_having_cond(Item *);       </span><br><span class="line">    void set_query_result(Query_result *);        </span><br><span class="line">    Query_result * query_result(void) const;</span><br><span class="line">    bool change_query_result(Query_result_interceptor *, Query_result_interceptor *);</span><br><span class="line">    void set_base_options(ulonglong);       </span><br><span class="line">    void add_base_options(ulonglong);  </span><br><span class="line">    void remove_base_options(ulonglong);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h1 id="SELECT-LEX-UNIT"><a href="#SELECT-LEX-UNIT" class="headerlink" title="SELECT_LEX_UNIT"></a>SELECT_LEX_UNIT</h1>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/LEX/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/LEX/"/>
    <published>2019-06-14T11:42:57.000Z</published>
    <summary>MySQL 源码 LEX 结构笔记：梳理 SELECT_LEX、SELECT_LEX_UNIT 在 UNION 与子查询中的层次与嵌套关系</summary>
    <title>MySQL 源码解读 -- 基础结构之Lex</title>
    <updated>2026-06-09T08:46:25.953Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 基础结构","description":"MySQL 源码基础结构索引：汇总 LEX、MDL、Schema、TABLE、THD 等 Server 层核心数据结构阅读笔记","image":"https://ilongda.com/img/my.jpg","wordCount":5,"datePublished":"2019-06-14T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.954Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 基础结构","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/index/"}]}</script><ul><li><a href="/knowledge/mysql/source_code_reading/basic_data_structure/LEX.html">LEX</a></br></li><li><a href="/knowledge/mysql/source_code_reading/basic_data_structure/MDL.html">MDL</a></br></li><li><a href="/knowledge/mysql/source_code_reading/basic_data_structure/Schema.html">Schema</a></br></li><li><a href="/knowledge/mysql/source_code_reading/basic_data_structure/TABLE.html">TABLE</a></br></li><li><a href="/knowledge/mysql/source_code_reading/basic_data_structure/THD.html">THD</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/basic_data_structure/index/"/>
    <published>2019-06-14T11:42:57.000Z</published>
    <summary>MySQL 源码基础结构索引：汇总 LEX、MDL、Schema、TABLE、THD 等 Server 层核心数据结构阅读笔记</summary>
    <title>MySQL 源码解读 -- 基础结构</title>
    <updated>2026-06-09T08:46:25.954Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读 -- 整体架构","description":"MySQL 源码整体架构笔记：介绍 server/sql/storage 等模块划分，以及 innodb 子目录 btr/buf/dict 等功能职责","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1568950157043-bf6379e6-cbd9-469c-acca-4f1af93f7864.png#align=left&display=inline&height=373&name=image.png&originHeight=373&originWidth=601&size=171739&status=done&width=601","wordCount":1125,"datePublished":"2019-06-07T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.952Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/architecture/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/architecture/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读 -- 整体架构","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/architecture/"}]}</script><h1 id="基本介绍-模块简介"><a href="#基本介绍-模块简介" class="headerlink" title="基本介绍 &amp; 模块简介"></a>基本介绍 &amp; 模块简介</h1><h1 id="Architecture"><a href="#Architecture" class="headerlink" title="Architecture"></a>Architecture</h1><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1568950157043-bf6379e6-cbd9-469c-acca-4f1af93f7864.png#align=left&display=inline&height=373&name=image.png&originHeight=373&originWidth=601&size=171739&status=done&width=601" alt="image.png"></p><h1 id="modules"><a href="#modules" class="headerlink" title="modules"></a>modules</h1><h2 id="模块介绍"><a href="#模块介绍" class="headerlink" title="模块介绍"></a>模块介绍</h2><ul><li>BUILD: 内含在各个平台、各种编译器下进行编译的脚本。如compile-pentium-debug表示在pentium架构上进行调试编译的脚本。</li><li>client: 客户端工具，如mysql,mysqladmin之类。</li><li>cmd-line-utils: readline,libedit工具。</li><li>config: 给aclocal使用的配置文件。</li><li>dbug: 提供一些调试用的宏定义。</li><li>Docs: MySQL在不同平台下的参考手册</li><li>extra: 提供innochecksum,resolveip等额外的小工具。<ul><li>regex: 正则表达式实现(来自多伦多大学Henry Spencer大牛的源码)。</li><li>zlib: zlib算法库(GNU)</li><li>pstack: GNU异步栈追踪工具。</li><li>ssl<ul><li>netware: 在netware平台上进行编译时需要的工具和库。</li></ul></li></ul></li><li>include: 包含的头文件</li><li>libmysql: 库文件，生产libmysqlclient.so。</li><li>libmysql_r: 线程安全的库文件，生成libmysqlclient_r.so。</li><li>libmysqld: 嵌入式MySQL Server库.</li><li>libservices: 5.5.0中新加的目录，实现了打印功能。</li><li>man: 适合man命令查看的帮助文件。</li><li>mysql-test: mysqld的测试工具套件。<ul><li>extra:<br>一些基准测试代码代码,主要是Perl程序(虽然后缀是sh)。</li></ul></li><li>mysys: 为实现跨平台，MySQL自己实现了一套常用的数据结构和算法，如string, hash等。还包含一些底层函数的跨平台封装,一般以my_开头。</li><li>plugin: MySQL 5.1开始支持一个插件式API接口,不需要重启mysqld即可动态载入插件,FullText就是一个例子。</li><li>scripts: 提供脚本工具，如mysql_install_db&#x2F;mysqld_safe等。</li><li>sql:<br>MySQL Server主要代码，将会生成mysqld文件。</li><li>sql-common:<br>存放部分服务器端和客户端都会用到的代码,有些地方的同名文件是这里lin过去的。</li><li>storage: 存储引擎所在目录。<ul><li>innobase<ul><li>btr:</li></ul></li></ul></li></ul><p>B+树的实现<br>      - buf: 缓冲池的实现,包括LRU算法,Flush刷新算法等<br>      - dict: InnoDB内存数据字典的实现<br>      - dyn: InnoDB动态数组的实现<br>      - fil: InnoDB文件数据结构以及对于文件的一些操作<br>      - fsp: 对InnoDB物理文件的管理,如页&#x2F;区&#x2F;段等(即File Space)<br>      - ha: 哈希算法的实现<br>      - handler: 继承与MySQL的handler,实现handler API与Server交互<br>      - ibuf: 插入缓冲(Insert Buffer)的实现<br>      - include: InnoDB所有头文件都放在这个目录,是查找结构定义的最佳地点<br>      - lock: InnoDB的锁实现及三种锁算法实现<br>      - log: 日志缓冲(Log Buffer)和重做日志组(Redo Log)的实现<br>      - mem: 辅助缓冲池(Additional Memory Pool)的实现,用来申请一些内部数据结构的内存<br>      - mtr: 事务的底层实现(日志,缓冲)<br>      - os: 封装一些对于操作系统的操作<br>      - page: 页的实现,研究InnoDB文件结构,这个目录至关重要<br>      - pars: 重载部分MySQL的SQL Parser(有待商榷)<br>      - que: Query graph,基本上没啥用<br>      - read: 读取游标的实现<br>      - rem: 行管理操作(比较操作,打印等)<br>      - row: 对于各种类型行数据操作的实现<br>      - srv: InnoDB后台线程,启动服务,Master Thread,SQL队列等<br>      - sync: InnoDB互斥变量(Mutex)的实现,基本同步机制<br>      - thr: InnoDB封装的可移植线程库<br>      - trx: 事务的实现<br>      - usr: Session管理<br>      - ut: 各种通用小工具</p><ul><li>strings: string库,包含很多字符串处理的函数。</li><li>support-files: my.cnf示例配置文件及编译所需的一些工具。</li><li>utilities： 封装一些简单函数</li><li>unittest: 单元测试文件。</li><li>vio: 虚拟io系统，是对network io的封装,把不同的协议封装成统一的IO函数。</li><li>win: 在windows平台编译所需的文件和一些说明。</li></ul><ol><li>SQL目录</li><li>SQL链路: 解析；优化；执行,并行执行。</li><li>SQL基础：Item;  server层TABLE; Field</li><li>解析 语法、Parser </li><li>优化:optimizer</li><li>执行:executor</li><li>SQL功能: outline&#x2F;CCL&#x2F;inventory&#x2F;statement queue; spm; 7. histograms; query cache; plan cache; trigger; </li><li>procedure; gis ； json； </li><li>dd ; </li><li>权限验证；TDE</li><li>paritiontion </li><li>event scheduler&#x2F;procedure </li><li>Server:</li><li>system&#x2F;session variables(很多影响执行) ;   </li><li>allocator;  </li><li>resourcegroups ; </li><li>handler</li><li>连接交互&#x2F;protocol&#x2F;client&#x2F;maxscale</li><li>基础组件及其他功能</li><li>binlog&#x2F;replication&#x2F;transaction-xa&#x2F;MDL</li><li>mysqld &#x2F; sql-polar</li></ol><h2 id="网络"><a href="#网络" class="headerlink" title="网络"></a>网络</h2><p>目前代码在sql&#x2F;protocol.h, sql&#x2F;protocol_classic.cc sql&#x2F;protocol_callback.cc, sql-common&#x2F;net_serv.cc, sql-common&#x2F;client.cc<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1567414414300-9fd2ea48-280c-4105-803b-12e3f637ff61.png#align=left&display=inline&height=235&name=image.png&originHeight=235&originWidth=413&size=27344&status=done&width=413" alt="image.png"></p><p>在最新的代码里面， 没有Protocol_binary, Protocol_local, Protocol_text  3个子类， 类型用Protocol::enum_protocol_type 来表示</p><p><a name="PMEWA"></a></p><h2 id="核心数据结构"><a href="#核心数据结构" class="headerlink" title="核心数据结构"></a>核心数据结构</h2><ul><li>THD: 线程类</li><li>Item: Item类(查询条目,函数,WHERE,ORDER,GROUP,ON子句等)</li><li>TABLE: 表描述符</li><li>TABEL_LIST: JOIN操作描述符</li><li>Field: 列数据类型及属性定义</li><li>LEX: 语法树</li><li>Protocol: 通讯协议</li><li>NET: 网络描述符</li><li>handler: 存储引擎接口</li></ul><p><a name="Jv7fq"></a></p><h1 id="流程"><a href="#流程" class="headerlink" title="流程"></a>流程</h1><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1568961394296-609be7e8-2809-4323-a239-aade4f6cf215.png#align=left&display=inline&height=1085&name=%E5%9B%BE%E7%89%87%201.png&originHeight=1085&originWidth=874&size=214106&status=done&width=874" alt="图片 1.png"></p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/architecture/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/architecture/"/>
    <published>2019-06-07T11:42:57.000Z</published>
    <summary>MySQL 源码整体架构笔记：介绍 server/sql/storage 等模块划分，以及 innodb 子目录 btr/buf/dict 等功能职责</summary>
    <title>MySQL 源码解读 -- 整体架构</title>
    <updated>2026-06-09T08:46:25.952Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 源码解读","description":"MySQL 源码解读系列索引：涵盖架构、基础结构、初始化、内存、网络、Server、存储引擎与工具模块，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":11,"datePublished":"2019-06-07T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.954Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/index/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 源码解读","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/index/"}]}</script><p><a href="/knowledge/mysql/source_code_reading/architecture.html">architecture</a></br><br><a href="/knowledge/mysql/source_code_reading/basic_data_structure/">basic_data_structure</a></br><br><a href="/knowledge/mysql/source_code_reading/diagram/">diagram</a></br><br><a href="/knowledge/mysql/source_code_reading/init/">init</a></br><br><a href="/knowledge/mysql/source_code_reading/memory/">memory</a></br><br><a href="/knowledge/mysql/source_code_reading/network/">network</a></br><br><a href="/knowledge/mysql/source_code_reading/plugin/">plugin</a></br><br><a href="/knowledge/mysql/source_code_reading/preparation.html">preparation</a></br><br><a href="/knowledge/mysql/source_code_reading/server/">server</a></br><br><a href="/knowledge/mysql/source_code_reading/storage/">storage</a></br><br><a href="/knowledge/mysql/source_code_reading/tools/">tools</a></br></p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/index/"/>
    <published>2019-06-07T11:42:57.000Z</published>
    <summary>MySQL 源码解读系列索引：涵盖架构、基础结构、初始化、内存、网络、Server、存储引擎与工具模块，更多细节与示例见正文。</summary>
    <title>MySQL 源码解读</title>
    <updated>2026-06-09T08:46:25.954Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/categories/Database/MySQL/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="MySQL 源码解读" scheme="https://ilongda.com/tags/MySQL-%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Mac 下 clion 单机debug mysql","description":"Mac 下 CLion 编译调试 MySQL 指南：环境变量清理、hosts 配置、CMake 参数与常见踩坑经验总结，更多细节与示例见正文。","image":"https://ilongda.com/img/clion_cmake.jpg","wordCount":571,"datePublished":"2019-05-20T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.956Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/source_code_reading/preparation/"},"url":"https://ilongda.com/2019/docs/mysql/source_code_reading/preparation/","inLanguage":"zh-CN","keywords":["Database","MySQL","MySQL 源码解读"],"articleSection":["Database","MySQL","MySQL 源码解读"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"Mac 下 clion 单机debug mysql","item":"https://ilongda.com/2019/docs/mysql/source_code_reading/preparation/"}]}</script><p>最近花了几天的时间搭建和编译MySQL 环境</p><p>因为电脑之前安装过很多软件， 很多环境已经有一些混乱， 给编译和debug MySQL 带来很多坑。 踩过的坑，远超过网上的描述，还好在同事的帮助下和自己摸索，慢慢搞定。</p><span id="more"></span><h1 id="前期准备："><a href="#前期准备：" class="headerlink" title="前期准备："></a>前期准备：</h1><ol><li>安装软件</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install autoconf automake m4 libtool make cmake bison gcc openssl</span><br></pre></td></tr></table></figure><ol start="2"><li>检查&#x2F;etc&#x2F;hosts<br>检查&#x2F;etc&#x2F;hosts 里面是否有localhost， 如果没有，增加127.0.0.1 localhost localhost.localdomain 到localhost， 否则， 在debug mysql 时，会出现 protobuffer 找不到host 错误。</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo vi /etc/hosts</span><br></pre></td></tr></table></figure><ol start="3"><li>检查环境变量<br> 将一些环境变量 CPPFLAGS&#x2F;LDFLAGS&#x2F;LIBRARY_PATHLD_LIBRARY_PATH&#x2F;LIBTOOL&#x2F;LIBTOOLIZE&#x2F;CC&#x2F;GCC 清空掉。<br>之前，因为安装Anaconda2 导致系统有一大堆变量。 另外因为编译过其他软件，修改了大量的编译相关的环境变量， 这些都给我们埋坑。</li></ol><p>检查一下文件</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">~/.bashrc</span><br><span class="line">~/.bash_profile</span><br><span class="line">/etc/profile</span><br><span class="line">/etc/bashrc</span><br></pre></td></tr></table></figure><p>找到这些环境设置文件，将这些环境变量给注释掉。</p><ol start="4"><li>升级xcode 到最新</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">lldb --version</span><br><span class="line">lldb-1001.0.12.1</span><br><span class="line">  Swift-5.0</span><br></pre></td></tr></table></figure><p>xcode 如果没有升级到最新， 编译时会出现protobuffer 编译错误。</p><h1 id="编译："><a href="#编译：" class="headerlink" title="编译："></a>编译：</h1><ol><li>下载源码</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone https://github.com/mysql/mysql-server</span><br></pre></td></tr></table></figure><p>可以选择一个稳定版， 比如8.0.16</p><ol start="2"><li><p>clion 导入 mysql 代码</p></li><li><p>配置cmake</p></li></ol><img data-src="/img/clion_cmake.jpg"  alt="Mac 下 clion 单机debug mysql"><img data-src="/img/clion_cmake2.jpg"  alt="Mac 下 clion 单机debug mysql"><p>cmake 配置</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-DCMAKE_INSTALL_PREFIX=&quot;/Users/longda/work/data/mysql/mysql&quot; -DSYSCONFDIR=&quot;/Users/longda/work/data/mysql/mysql&quot;  -DMYSQL_DATADIR=&quot;/Users/longda/work/data/mysql/data&quot;   -DWITH_DEBUG=1                -DWITH_DEBUG_SYNC=1      -DCMAKE_C_FLAGS_RELWITHDEBINFO=&quot;-O0 -g&quot; -DCMAKE_CXX_FLAGS_RELWITHDEBINFO=&quot;-O0 -g&quot; -DINSTALL_LAYOUT=STANDALONE        -DMYSQL_MAINTAINER_MODE=0          -DWITH_EMBEDDED_SERVER=0           -DWITH_EXTRA_CHARSETS=all          -DDEFAULT_CHARSET=utf8mb4          -DWITH_MYISAM_STORAGE_ENGINE=1     -DWITH_INNOBASE_STORAGE_ENGINE=1   -DWITH_PARTITION_STORAGE_ENGINE=0  -DWITH_CSV_STORAGE_ENGINE=0        -DWITH_ARCHIVE_STORAGE_ENGINE=0    -DWITH_BLACKHOLE_STORAGE_ENGINE=0  -DWITH_FEDERATED_STORAGE_ENGINE=0  -DWITH_PERFSCHEMA_STORAGE_ENGINE=0 -DWITH_EXAMPLE_STORAGE_ENGINE=0    -DWITH_TEMPTABLE_STORAGE_ENGINE=1  -DBUILD_TESTING=ON                 -DUSE_CTAGS=0                      -DENABLE_DTRACE=0                  -DENABLED_PROFILING=1              -DENABLED_LOCAL_INFILE=1 -DDOWNLOAD_BOOST=1 -DWITH_BOOST=/Users/longda/work/company/taobao/tools/boost/ -DOPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2r -DMYSQL_SERVER_SUFFIX=&quot;rds-dev&quot;</span><br></pre></td></tr></table></figure><img data-src="/img/clion_cmake3.png"  alt="Mac 下 clion 单机debug mysql"><img data-src="/img/clion_cmake4.jpg"  alt="Mac 下 clion 单机debug mysql">## 编译：<p>用 command + f9, 执行编译， 一般情况下 编译是成功的</p><h2 id="开始debug"><a href="#开始debug" class="headerlink" title="开始debug"></a>开始debug</h2><ol><li>初始化mysql data 目录<br>编译成功后， 在目录cmake-build-debug(或者cmake-build-cmake)下 执行</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./bin/mysqld --initialize-insecure --basedir=/Users/longda/work/data/mysql/mysq --datadir=/Users/longda/work/data/mysql/mysq/data</span><br></pre></td></tr></table></figure><p>以insecure的方式初始化MySQL密码为空</p><p>&#x2F;Users&#x2F;longda&#x2F;work&#x2F;data&#x2F;mysql&#x2F;mysq 为mysql 的binary 目录， 可以根据实际情况进行设置</p><ol start="2"><li>下断点调试<br>最后， 打开sql&#x2F;main.cc， 下断点 既可以调试</li></ol>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/source_code_reading/preparation/</id>
    <link href="https://ilongda.com/2019/docs/mysql/source_code_reading/preparation/"/>
    <published>2019-05-20T11:42:57.000Z</published>
    <summary>Mac 下 CLion 编译调试 MySQL 指南：环境变量清理、hosts 配置、CMake 参数与常见踩坑经验总结，更多细节与示例见正文。</summary>
    <title>Mac 下 clion 单机debug mysql</title>
    <updated>2026-06-09T08:46:25.956Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/categories/Database/MySQL/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/tags/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"深入浅出MySQL -- 字符集","description":"《深入浅出 MySQL》字符集笔记：ASCII/Unicode/UTF-8 编码、collation 规则及库表字段字符集设置方法","image":"https://cdn.nlark.com/yuque/0/2020/png/106206/1600777942226-f4fd30a7-4a9b-4fda-a2d8-1518cdc0fed5.png#align=left&display=inline&height=519&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1037&originWidth=1302&size=505565&status=done&style=none&width=651","wordCount":1048,"datePublished":"2019-03-07T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.959Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/characteristic/"},"url":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/characteristic/","inLanguage":"zh-CN","keywords":["Database","MySQL","深入浅出MySQL"],"articleSection":["Database","MySQL","深入浅出MySQL"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"深入浅出MySQL -- 字符集","item":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/characteristic/"}]}</script><h1 id="字符集"><a href="#字符集" class="headerlink" title="字符集"></a>字符集</h1><ol><li>ascii： 变成国标ISO-646, 由7位编码， 包括大小写字母， 阿拉伯数字和标点符号， 33个控制符号。 </li><li>unicode<ol><li>为了统一字符编码，iso 1984 推出iso-10646 字符集 又称ucs-4, 4个字节来表示字节， 分为组，面，行和格， 每个一个字节来代表。 </li><li>1988年， 美国计算机协会成立unicode ， 反对iso， 推出unicode 1.0, unicode 16, unicode-8<ol><li>utf – unicode transformation format 所写， 对于iso-10646 的0组0字面 字符（basic multi -lingual plan， bmp） 保持不变， 对于其他字符转化为2个16位的unicode 编码</li><li>utf-8 主要省字节数， 因为大部分场景下，都是ascii 编码， 因此utf8  1字节兼容 ascii 字符集， 2字节 可以转化0X0080 ～ 0X07FF UCS-4， 3字节 用于ucs-4  0x0800 ～0xFFFF，  4字节用于0x00010000 ～ 0001ffff 原始码。</li></ol></li><li><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1600777942226-f4fd30a7-4a9b-4fda-a2d8-1518cdc0fed5.png#align=left&display=inline&height=519&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1037&originWidth=1302&size=505565&status=done&style=none&width=651" alt="image.png"></li></ol></li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1600777957437-3bd6f2c4-c5d6-4978-ad7b-233cd969d589.png#align=left&display=inline&height=296&margin=%5Bobject%20Object%5D&name=image.png&originHeight=591&originWidth=1298&size=189921&status=done&style=none&width=649" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1600778043521-ba5b5b0d-23ba-4a07-8154-97e380991ee6.png#align=left&display=inline&height=352&margin=%5Bobject%20Object%5D&name=image.png&originHeight=704&originWidth=1278&size=362435&status=done&style=none&width=639" alt="image.png"><br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; show character set;</span><br></pre></td></tr></table></figure><p><br />字符集character： 定义mysql 存储字符串的方式<br />校对规则collation： 定义 比较字符串的方式。 一个字符集，可以对应多个校对规则， 比如<br /><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1600778257070-a74e323b-b463-4f5e-b8bd-f6200c749085.png#align=left&display=inline&height=222&margin=%5Bobject%20Object%5D&name=image.png&originHeight=443&originWidth=1203&size=57753&status=done&style=none&width=601.5" alt="image.png"><br />校对规则命名规范： 字符集开始， 通常包括一个语言名， 以ci（大小写不敏感）， cs（大小写敏感）， bin（基于字符编码的值）<br /><br><br />服务器默认字符集</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">可以在 my.cnf 中设置：</span><br><span class="line">[mysqld]</span><br><span class="line">default-character-set=gbk</span><br><span class="line">或者在启动选项中指定：</span><br><span class="line">mysqld --default-character-set=gbk</span><br><span class="line">或者在编译的时候指定：</span><br><span class="line">./configure --with-charset=gbk</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">mysql&gt; show variables like &#x27;charactersetserver&#x27;;</span><br><span class="line">&#x27;character_set_server&#x27;, &#x27;utf8&#x27;</span><br><span class="line"></span><br><span class="line">mysql&gt;show variables like &#x27;collation_server&#x27;;</span><br><span class="line">&#x27;collation_server&#x27;, &#x27;utf8_general_ci&#x27;</span><br><span class="line"></span><br><span class="line"></span><br></pre></td></tr></table></figure><p>不能对已经存在数据的database 进行修改 字符集进行修改数据内容</p><ol><li>如果指定了字符集和校对规则，则使用指定的字符集和校对规则；</li><li>如果指定了字符集没有指定校对规则，则使用指定字符集的默认校对规则；</li><li>如果没有指定字符集和校对规则，则使用服务器字符集和校对规则作为数据库的字</li></ol><p>符集和校对规则。<br /><br><br />显示当前数据库的字符集和校对规则</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">show variables like &#x27;character_set_database&#x27;;</span><br><span class="line">show variables like &#x27;collation_database&#x27;;</span><br><span class="line"></span><br></pre></td></tr></table></figure><p><br />表的字符集和校对规则， 类似数据库的定义方式<br />查看表的字符集和校对规则</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; show create table z1 </span><br></pre></td></tr></table></figure><p>可以对一个列 进行单独的设置<br /><br><br /><br><br />对于应用来说， 还有3个不同的参数， character_set_client, character_set_connection, character_set_result. <br /><br><br />通常情况下， 这3个参数应该一样， 否则会有乱码。 可以一次性全部设置</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SET NAMES UTF8；</span><br></pre></td></tr></table></figure><p><br />建议在my.cnf 中进行设置<br />[mysql]<br />default-character-set&#x3D;utf8<br /><br><br /><br><br />字符串常量的字符集 是由 character_set_connection 参数来指定。 <br />也可以手动强制进行设置</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">[_charset_name]&#x27;string&#x27; [COLLATE collation_name]</span><br><span class="line"></span><br><span class="line">例如</span><br><span class="line">mysql&gt; select _gbk &#x27;字符集&#x27;;</span><br></pre></td></tr></table></figure><p><br />修改空数据库或空表的字符集和校对规则</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">alter database character set ***</span><br><span class="line">alter table tablename character set ***</span><br></pre></td></tr></table></figure><p><br />如果对一个已经有数据的进行调整</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">比如将一个latin1的字符集修改为gbk的字符集</span><br><span class="line">1. 导出表结构 -- -d 只导出表结构， --default-character-set 设置怎么连接</span><br><span class="line">mysqldump -uroot -p --default-character-set=gbk -d databasename&gt; createtab.sql</span><br><span class="line"></span><br><span class="line">2. 修改createtab.sql 中的字符集定义</span><br><span class="line">3. 导出</span><br><span class="line">mysqldump -uroot -p --quick --no-create-info --extended-insert </span><br><span class="line">--default-character-set=latin1 databasename&gt; data.sql</span><br></pre></td></tr></table></figure><p>–quick：该选项用于转储大的表。它强制 mysqldump 从服务器一次一行地检索表中<br />的行而不是检索所有行，并在输出前将它缓存到内存中。<br /> –extended-insert：使用包括几个 VALUES 列表的多行 INSERT 语法。这样使转储文件<br />更小，重载文件时可以加速插入。<br /> –no-create-info：不写重新创建每个转储表的 CREATE TABLE 语句。<br /> –default-character-set&#x3D;latin1：按照原有的字符集导出所有数据，这样导出的文件中，<br />所有中文都是可见的，不会保存成乱码。<br /><br><br />（4）打开 data.sql，将 SET NAMES latin1 修改成 SET NAMES gbk。<br />（5）使用新的字符集创建新的数据库。<br />create database databasename default charset gbk;<br />（6）创建表，执行 createtab.sql。<br />mysql -uroot -p databasename &lt; createtab.sql<br />（7）导入数据，执行 data.sql。<br />mysql -uroot -p databasename &lt; data.sq</p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/characteristic/</id>
    <link href="https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/characteristic/"/>
    <published>2019-03-07T11:42:57.000Z</published>
    <summary>《深入浅出 MySQL》字符集笔记：ASCII/Unicode/UTF-8 编码、collation 规则及库表字段字符集设置方法</summary>
    <title>深入浅出MySQL -- 字符集</title>
    <updated>2026-06-09T08:46:25.959Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/categories/Database/MySQL/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/tags/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"深入浅出MySQL","description":"《深入浅出 MySQL》读书笔记索引：汇总字符集、数据类型、函数、存储过程、存储引擎与视图等章节链接，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":6,"datePublished":"2019-03-07T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.960Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/index/"},"url":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/index/","inLanguage":"zh-CN","keywords":["Database","MySQL","深入浅出MySQL"],"articleSection":["Database","MySQL","深入浅出MySQL"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"深入浅出MySQL","item":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/index/"}]}</script><ul><li><a href="/knowledge/mysql/深入迁出MySQL_reading_notes/characteristic.html">characteristic</a></br></li><li><a href="/knowledge/mysql/深入迁出MySQL_reading_notes/datatype.html">datatype</a></br></li><li><a href="/knowledge/mysql/深入迁出MySQL_reading_notes/function.html">function</a></br></li><li><a href="/knowledge/mysql/深入迁出MySQL_reading_notes/procedure.html">procedure</a></br></li><li><a href="/knowledge/mysql/深入迁出MySQL_reading_notes/storage.html">storage</a></br></li><li><a href="/knowledge/mysql/深入迁出MySQL_reading_notes/view.html">view</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/index/"/>
    <published>2019-03-07T11:42:57.000Z</published>
    <summary>《深入浅出 MySQL》读书笔记索引：汇总字符集、数据类型、函数、存储过程、存储引擎与视图等章节链接，更多细节与示例见正文。</summary>
    <title>深入浅出MySQL</title>
    <updated>2026-06-09T08:46:25.960Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"MySQL 学习","description":"MySQL 学习专题索引：包含《深入浅出 MySQL》读书笔记与 MySQL 源码解读两大系列文章入口，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":6,"datePublished":"2019-03-05T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.952Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/index/"},"url":"https://ilongda.com/2019/docs/mysql/index/","inLanguage":"zh-CN","keywords":["Database","MySQL"],"articleSection":["Database","MySQL"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"MySQL 学习","item":"https://ilongda.com/2019/docs/mysql/index/"}]}</script><p><a href="/knowledge/mysql/深入迁出MySQL_reading_notes/">深入迁出MySQL_reading_notes</a></br><br><a href="/knowledge/mysql/source_code_reading/">source_code_reading</a></br></p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/index/</id>
    <link href="https://ilongda.com/2019/docs/mysql/index/"/>
    <published>2019-03-05T11:42:57.000Z</published>
    <summary>MySQL 学习专题索引：包含《深入浅出 MySQL》读书笔记与 MySQL 源码解读两大系列文章入口，更多细节与示例见正文。</summary>
    <title>MySQL 学习</title>
    <updated>2026-06-09T08:46:25.952Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/categories/Database/MySQL/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/tags/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"深入浅出MySQL -- 存储引擎概述","description":"《深入浅出 MySQL》存储引擎概述占位笔记，计划梳理 InnoDB/MyISAM/Memory 等引擎特点对比，更多细节与示例见正文。","image":"https://cdn.nlark.com/yuque/0/2020/png/106206/1596022212561-22210495-304e-40f2-9803-ccb327fec307.png#align=left&display=inline&height=514&margin=%5Bobject%20Object%5D&name=image.png&originHeight=514&originWidth=823&size=85542&status=done&style=none&width=823","wordCount":6,"datePublished":"2019-03-05T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.960Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/storage/"},"url":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/storage/","inLanguage":"zh-CN","keywords":["Database","MySQL","深入浅出MySQL"],"articleSection":["Database","MySQL","深入浅出MySQL"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"深入浅出MySQL -- 存储引擎概述","item":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/storage/"}]}</script><h1 id="存储引擎概述"><a href="#存储引擎概述" class="headerlink" title="存储引擎概述"></a>存储引擎概述</h1><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1596022212561-22210495-304e-40f2-9803-ccb327fec307.png#align=left&display=inline&height=514&margin=%5Bobject%20Object%5D&name=image.png&originHeight=514&originWidth=823&size=85542&status=done&style=none&width=823" alt="image.png"><br /><br><br /><br><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1596023132078-8d4994cf-9639-411d-b8b9-c30bca678507.png#align=left&display=inline&height=225&margin=%5Bobject%20Object%5D&name=image.png&originHeight=225&originWidth=786&size=67944&status=done&style=none&width=786" alt="image.png"></p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/storage/</id>
    <link href="https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/storage/"/>
    <published>2019-03-05T11:42:57.000Z</published>
    <summary>《深入浅出 MySQL》存储引擎概述占位笔记，计划梳理 InnoDB/MyISAM/Memory 等引擎特点对比，更多细节与示例见正文。</summary>
    <title>深入浅出MySQL -- 存储引擎概述</title>
    <updated>2026-06-09T08:46:25.960Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/categories/Database/MySQL/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/tags/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"深入浅出MySQL -- 视图","description":"《深入浅出 MySQL》视图笔记：CREATE VIEW 算法选项、WITH CHECK OPTION 及 updatable view 限制说明","image":"https://ilongda.com/img/my.jpg","wordCount":3228,"datePublished":"2019-03-05T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.960Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/view/"},"url":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/view/","inLanguage":"zh-CN","keywords":["Database","MySQL","深入浅出MySQL"],"articleSection":["Database","MySQL","深入浅出MySQL"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"深入浅出MySQL -- 视图","item":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/view/"}]}</script><h1 id="视图"><a href="#视图" class="headerlink" title="视图"></a>视图</h1><p>视图是虚拟存在的表, 对客户来说是透明的. </p><ol><li>简单, 很多复杂条件已经提前组合好了</li><li>安全, 只能访问他们被允许的结果集</li><li>数据独立, 源表的变更对视图几乎无影响, 即使修改视图涉及的列名, 视图可以修改以适配.</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">CREATE [OR REPLACE] [ALGORITHM = &#123;UNDEFINED | MERGE | TEMPTABLE&#125;]</span><br><span class="line">VIEW view_name [(column_list)]</span><br><span class="line">AS select_statement</span><br><span class="line">[WITH [CASCADED | LOCAL] CHECK OPTION]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">ALTER [ALGORITHM = &#123;UNDEFINED | MERGE | TEMPTABLE&#125;]</span><br><span class="line">VIEW view_name [(column_list)]</span><br><span class="line">AS select_statement</span><br><span class="line">[WITH [CASCADED | LOCAL] CHECK OPTION]</span><br></pre></td></tr></table></figure><ol><li>目前不支持视图含有subquery</li><li>WITH [CASCADED | LOCAL] CHECK OPTION 决定了是否允许更新数据使记录不再满足视图的条</li></ol><p>件。这个选项与 Oracle 数据库中的选项是类似的，其中：<br /> LOCAL 是只要满足本视图的条件就可以更新；<br /> CASCADED 则是必须满足所有针对该视图的所有视图的条件才可以更新。<br />如果没有明确是 LOCAL 还是 CASCADED，则默认是 CASCADED。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; create or replace view payment_view as</span><br><span class="line"> -&gt; select payment_id,amount from payment </span><br><span class="line"> -&gt; where amount &lt; 10 WITH CHECK OPTION;</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; </span><br><span class="line">mysql&gt; create or replace view payment_view1 as</span><br><span class="line"> -&gt; select payment_id,amount from payment_view </span><br><span class="line"> -&gt; where amount &gt; 5 WITH LOCAL CHECK OPTION;</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; </span><br><span class="line">mysql&gt; create or replace view payment_view2 as</span><br><span class="line"> -&gt; select payment_id,amount from payment_view</span><br><span class="line">  -&gt; where amount &gt; 5 WITH CASCADED CHECK OPTION;</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; select * from payment_view1 limit 1;</span><br><span class="line">+------------+--------+ </span><br><span class="line"> ?| payment_id | amount |</span><br><span class="line">+------------+--------+| 3 | 5.99 |</span><br><span class="line">+------------+--------+1 row in set (0.00 sec)</span><br><span class="line">mysql&gt; update payment_view1 set amount=10 </span><br><span class="line">  -&gt; where payment_id = 3; </span><br><span class="line">Query OK, 1 row affected (0.03 sec)</span><br><span class="line">Rows matched: 1 Changed: 1 Warnings: 0</span><br><span class="line">mysql&gt; update payment_view2 set amount=10</span><br><span class="line">  -&gt; where payment_id = 3;</span><br><span class="line">ERROR 1369 (HY000): CHECK OPTION failed &#x27;sakila.paymentview2&#x27;</span><br></pre></td></tr></table></figure><br /><ol start="3"><li>可选的ALGORITHM子句是对标准SQL的MySQL扩展。ALGORITHM可取三个值：MERGE、TEMPTABLE或UNDEFINED。如果没有ALGORITHM子句，默认算法是UNDEFINED（未定义的）。算法会影响MySQL处理视图的方式。</li></ol><p>一、 MERGE算法<br />文档解释：</p><blockquote><p>对于MERGE，会将引用视图的语句的文本与视图定义合并起来，使得视图定义的某一部分取代语句的对应部分</p></blockquote><p>通俗的说，意思就是MERGE算法是一个合并算法，每当执行的时候,先将视图的sql语句与外部查询视图的sql语句,合并在一起,最终执行；这样操作对效率基本上没有什么影响，但是使用这种算法有一定限制，以下引自文档：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">MERGE算法要求视图中的行和基表中的行具有一对一的关系。如果不具有该关系。必须使用临时表取而代之。如果视图包含下述结构中的任何一种，将失去一对一的关系：</span><br><span class="line">·         聚合函数（SUM(), MIN(), MAX(), COUNT()等）。</span><br><span class="line">·         DISTINCT</span><br><span class="line">·         GROUP BY</span><br><span class="line">·         HAVING</span><br><span class="line">·         UNION或UNION ALL</span><br><span class="line">·         仅引用文字值（在该情况下，没有基本表）。</span><br><span class="line">• 1</span><br><span class="line">• 2</span><br><span class="line">• 3</span><br><span class="line">• 4</span><br><span class="line">• 5</span><br><span class="line">• 6</span><br><span class="line">• 7</span><br><span class="line">• 8</span><br><span class="line">• 9</span><br><span class="line">• 10</span><br><span class="line">• 11</span><br><span class="line">• 12</span><br><span class="line">• 13</span><br><span class="line">• 14</span><br></pre></td></tr></table></figure><p>举个例子</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">/** 定义两张表</span><br><span class="line">seller_sku表包括以下字段：id(主键),seller_id(门店ID),sku_id(商品sku的id,对应goods_sku的id),amount(商品的库存)</span><br><span class="line">goods_sku 表包括以下字段：id(主键),goods_name(sku的名称)</span><br><span class="line">**/</span><br><span class="line">#案例1 查询商品库存大于50的门店的商品所对应的成本</span><br><span class="line">CREATE OR REPLACE  VIEW amount_50_sku AS</span><br><span class="line">SELECT</span><br><span class="line">seller_id,</span><br><span class="line">price,</span><br><span class="line">sku_id,</span><br><span class="line">amount,</span><br><span class="line">(price*amount) AS sku_values </span><br><span class="line">FROM </span><br><span class="line">sellers_sku WHERE amount &gt; 50</span><br><span class="line">#方法A</span><br><span class="line">SELECT * FROM amount_50_sku  </span><br><span class="line">#方法B</span><br><span class="line">SELECT seller_id,price,sku_id,amount,(price*amount) AS sku_values FROM sellers_sku </span><br><span class="line">• 1</span><br><span class="line">• 2</span><br><span class="line">• 3</span><br><span class="line">• 4</span><br><span class="line">• 5</span><br><span class="line">• 6</span><br><span class="line">• 7</span><br><span class="line">• 8</span><br><span class="line">• 9</span><br><span class="line">• 10</span><br><span class="line">• 11</span><br><span class="line">• 12</span><br><span class="line">• 13</span><br><span class="line">• 14</span><br><span class="line">• 15</span><br><span class="line">• 16</span><br><span class="line">• 17</span><br><span class="line">• 18</span><br><span class="line">• 19</span><br><span class="line">• 20</span><br><span class="line">• 21</span><br><span class="line">• 22</span><br><span class="line">• 23</span><br></pre></td></tr></table></figure><p>方法A和方法B查询出结果的时间是差不多的，MERGE算法对效率的影响很小。<br />二、TEMPTABLE算法<br />文档解释</p><blockquote><p>对于TEMPTABLE，视图的结果将被置于临时表中，然后使用它执行语句。</p></blockquote><p>TEMPTABLE算法是将结果放置到临时表中，意味这要mysql要先创建好一个临时表，然后将结果放到临时表中去，然后再使用这个临时表进行相应的查询。为什么文档中说“果使用了临时表，视图是不可更新的。”就是因为这个算法生成的视图其实就是一个结果的临时表，是无法执行update语句的，mysql会报错：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">“错误代码： 1288 The target table seller_sku_amount of the UPDATE is not updatable&quot;</span><br><span class="line">• 1</span><br></pre></td></tr></table></figure><p>最最重要的是，TEMPTABLE算法会创建临时表，这个过程是会影响效率的，如以下案例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line">seller_sku表包括以下字段：id(主键),seller_id(门店ID),sku_id(商品sku的id,对应goods_sku的id),amount(商品的库存)</span><br><span class="line">goods_sku 表包括以下字段：id(主键),goods_name(sku的名称)</span><br><span class="line">**/</span><br><span class="line">#案例2 显示每个商品各个门店库存的总和</span><br><span class="line">#创建视图</span><br><span class="line">CREATE OR REPLACE VIEW seller_sku_amount AS SELECT</span><br><span class="line">sku_id,</span><br><span class="line">SUM(amount) AS amount_total</span><br><span class="line">FROM sellers_sku</span><br><span class="line">GROUP BY sku_id</span><br><span class="line">#使用视图查询</span><br><span class="line">SELECT </span><br><span class="line">seller_sku_amount.sku_id,</span><br><span class="line">seller_sku_amount.amount_total,</span><br><span class="line">goods_sku.*</span><br><span class="line"> FROM seller_sku_amount JOIN goods_sku ON goods_sku.`id` = seller_sku_amount.`sku_id`</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">#原生SQL查询</span><br><span class="line">SELECT</span><br><span class="line">sellers_sku.sku_id,</span><br><span class="line">SUM(sellers_sku.amount) AS amount_total,</span><br><span class="line">goods_sku.*</span><br><span class="line">FROM sellers_sku</span><br><span class="line">JOIN goods_sku ON sellers_sku.`sku_id` = goods_sku.`id`</span><br><span class="line">GROUP BY sku_id</span><br><span class="line">ORDER BY amount_total DESC</span><br><span class="line">• 1</span><br><span class="line">• 2</span><br><span class="line">• 3</span><br><span class="line">• 4</span><br><span class="line">• 5</span><br><span class="line">• 6</span><br><span class="line">• 7</span><br><span class="line">• 8</span><br><span class="line">• 9</span><br><span class="line">• 10</span><br><span class="line">• 11</span><br><span class="line">• 12</span><br><span class="line">• 13</span><br><span class="line">• 14</span><br><span class="line">• 15</span><br><span class="line">• 16</span><br><span class="line">• 17</span><br><span class="line">• 18</span><br><span class="line">• 19</span><br><span class="line">• 20</span><br><span class="line">• 21</span><br><span class="line">• 22</span><br><span class="line">• 23</span><br><span class="line">• 24</span><br><span class="line">• 25</span><br><span class="line">• 26</span><br><span class="line">• 27</span><br><span class="line">• 28</span><br><span class="line">• 29</span><br><span class="line">• 30</span><br><span class="line">• 31</span><br><span class="line">• 32</span><br></pre></td></tr></table></figure><p>以上两个查询，使用视图查询的速度为比使用原生SQL查询的效率慢50%，随着数据量的增大，这个效率还会更慢（数据量越大，需要往临时表填充更多的数据）；<br />但是TEMPTABLE算法也不是没有好处的，TEMPTABLE算法创建临时表之后、并在完成语句处理之前，能够释放基表上的锁定。与MERGE算法相比，锁定释放的速度更快，这样，使用视图的其他客户端不会被屏蔽过长时间。<br />三、UNDEFINED算法<br />UNDEFINED算法没啥好区分的，直接引用文档的：</p><blockquote><p>对于UNDEFINED，MySQL将选择所要使用的算法。如果可能，它倾向于MERGE而不是TEMPTABLE，这是因为MERGE通常更有效，而且如果使用了临时表，视图是不可更新的。</p></blockquote><h1 id="视图-1"><a href="#视图-1" class="headerlink" title="视图"></a>视图</h1><p>视图是虚拟存在的表, 对客户来说是透明的. </p><ol><li>简单, 很多复杂条件已经提前组合好了</li><li>安全, 只能访问他们被允许的结果集</li><li>数据独立, 源表的变更对视图几乎无影响, 即使修改视图涉及的列名, 视图可以修改以适配.</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">CREATE [OR REPLACE] [ALGORITHM = &#123;UNDEFINED | MERGE | TEMPTABLE&#125;]</span><br><span class="line">VIEW view_name [(column_list)]</span><br><span class="line">AS select_statement</span><br><span class="line">[WITH [CASCADED | LOCAL] CHECK OPTION]</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">ALTER [ALGORITHM = &#123;UNDEFINED | MERGE | TEMPTABLE&#125;]</span><br><span class="line">VIEW view_name [(column_list)]</span><br><span class="line">AS select_statement</span><br><span class="line">[WITH [CASCADED | LOCAL] CHECK OPTION]</span><br></pre></td></tr></table></figure><ol><li>目前不支持视图含有subquery</li><li>WITH [CASCADED | LOCAL] CHECK OPTION 决定了是否允许更新数据使记录不再满足视图的条</li></ol><p>件。这个选项与 Oracle 数据库中的选项是类似的，其中：<br /> LOCAL 是只要满足本视图的条件就可以更新；<br /> CASCADED 则是必须满足所有针对该视图的所有视图的条件才可以更新。<br />如果没有明确是 LOCAL 还是 CASCADED，则默认是 CASCADED。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; create or replace view payment_view as</span><br><span class="line"> -&gt; select payment_id,amount from payment </span><br><span class="line"> -&gt; where amount &lt; 10 WITH CHECK OPTION;</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; </span><br><span class="line">mysql&gt; create or replace view payment_view1 as</span><br><span class="line"> -&gt; select payment_id,amount from payment_view </span><br><span class="line"> -&gt; where amount &gt; 5 WITH LOCAL CHECK OPTION;</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; </span><br><span class="line">mysql&gt; create or replace view payment_view2 as</span><br><span class="line"> -&gt; select payment_id,amount from payment_view</span><br><span class="line">  -&gt; where amount &gt; 5 WITH CASCADED CHECK OPTION;</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; select * from payment_view1 limit 1;</span><br><span class="line">+------------+--------+ </span><br><span class="line"> ?| payment_id | amount |</span><br><span class="line">+------------+--------+| 3 | 5.99 |</span><br><span class="line">+------------+--------+1 row in set (0.00 sec)</span><br><span class="line">mysql&gt; update payment_view1 set amount=10 </span><br><span class="line">  -&gt; where payment_id = 3; </span><br><span class="line">Query OK, 1 row affected (0.03 sec)</span><br><span class="line">Rows matched: 1 Changed: 1 Warnings: 0</span><br><span class="line">mysql&gt; update payment_view2 set amount=10</span><br><span class="line">  -&gt; where payment_id = 3;</span><br><span class="line">ERROR 1369 (HY000): CHECK OPTION failed &#x27;sakila.paymentview2&#x27;</span><br></pre></td></tr></table></figure><br /><ol start="3"><li>可选的ALGORITHM子句是对标准SQL的MySQL扩展。ALGORITHM可取三个值：MERGE、TEMPTABLE或UNDEFINED。如果没有ALGORITHM子句，默认算法是UNDEFINED（未定义的）。算法会影响MySQL处理视图的方式。</li></ol><p>一、 MERGE算法<br />文档解释：</p><blockquote><p>对于MERGE，会将引用视图的语句的文本与视图定义合并起来，使得视图定义的某一部分取代语句的对应部分</p></blockquote><p>通俗的说，意思就是MERGE算法是一个合并算法，每当执行的时候,先将视图的sql语句与外部查询视图的sql语句,合并在一起,最终执行；这样操作对效率基本上没有什么影响，但是使用这种算法有一定限制，以下引自文档：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">MERGE算法要求视图中的行和基表中的行具有一对一的关系。如果不具有该关系。必须使用临时表取而代之。如果视图包含下述结构中的任何一种，将失去一对一的关系：</span><br><span class="line">·         聚合函数（SUM(), MIN(), MAX(), COUNT()等）。</span><br><span class="line">·         DISTINCT</span><br><span class="line">·         GROUP BY</span><br><span class="line">·         HAVING</span><br><span class="line">·         UNION或UNION ALL</span><br><span class="line">·         仅引用文字值（在该情况下，没有基本表）。</span><br><span class="line">• 1</span><br><span class="line">• 2</span><br><span class="line">• 3</span><br><span class="line">• 4</span><br><span class="line">• 5</span><br><span class="line">• 6</span><br><span class="line">• 7</span><br><span class="line">• 8</span><br><span class="line">• 9</span><br><span class="line">• 10</span><br><span class="line">• 11</span><br><span class="line">• 12</span><br><span class="line">• 13</span><br><span class="line">• 14</span><br></pre></td></tr></table></figure><p>举个例子</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">/** 定义两张表</span><br><span class="line">seller_sku表包括以下字段：id(主键),seller_id(门店ID),sku_id(商品sku的id,对应goods_sku的id),amount(商品的库存)</span><br><span class="line">goods_sku 表包括以下字段：id(主键),goods_name(sku的名称)</span><br><span class="line">**/</span><br><span class="line">#案例1 查询商品库存大于50的门店的商品所对应的成本</span><br><span class="line">CREATE OR REPLACE  VIEW amount_50_sku AS</span><br><span class="line">SELECT</span><br><span class="line">seller_id,</span><br><span class="line">price,</span><br><span class="line">sku_id,</span><br><span class="line">amount,</span><br><span class="line">(price*amount) AS sku_values </span><br><span class="line">FROM </span><br><span class="line">sellers_sku WHERE amount &gt; 50</span><br><span class="line">#方法A</span><br><span class="line">SELECT * FROM amount_50_sku  </span><br><span class="line">#方法B</span><br><span class="line">SELECT seller_id,price,sku_id,amount,(price*amount) AS sku_values FROM sellers_sku </span><br><span class="line">• 1</span><br><span class="line">• 2</span><br><span class="line">• 3</span><br><span class="line">• 4</span><br><span class="line">• 5</span><br><span class="line">• 6</span><br><span class="line">• 7</span><br><span class="line">• 8</span><br><span class="line">• 9</span><br><span class="line">• 10</span><br><span class="line">• 11</span><br><span class="line">• 12</span><br><span class="line">• 13</span><br><span class="line">• 14</span><br><span class="line">• 15</span><br><span class="line">• 16</span><br><span class="line">• 17</span><br><span class="line">• 18</span><br><span class="line">• 19</span><br><span class="line">• 20</span><br><span class="line">• 21</span><br><span class="line">• 22</span><br><span class="line">• 23</span><br></pre></td></tr></table></figure><p>方法A和方法B查询出结果的时间是差不多的，MERGE算法对效率的影响很小。<br />二、TEMPTABLE算法<br />文档解释</p><blockquote><p>对于TEMPTABLE，视图的结果将被置于临时表中，然后使用它执行语句。</p></blockquote><p>TEMPTABLE算法是将结果放置到临时表中，意味这要mysql要先创建好一个临时表，然后将结果放到临时表中去，然后再使用这个临时表进行相应的查询。为什么文档中说“果使用了临时表，视图是不可更新的。”就是因为这个算法生成的视图其实就是一个结果的临时表，是无法执行update语句的，mysql会报错：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">“错误代码： 1288 The target table seller_sku_amount of the UPDATE is not updatable&quot;</span><br><span class="line">• 1</span><br></pre></td></tr></table></figure><p>最最重要的是，TEMPTABLE算法会创建临时表，这个过程是会影响效率的，如以下案例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line">seller_sku表包括以下字段：id(主键),seller_id(门店ID),sku_id(商品sku的id,对应goods_sku的id),amount(商品的库存)</span><br><span class="line">goods_sku 表包括以下字段：id(主键),goods_name(sku的名称)</span><br><span class="line">**/</span><br><span class="line">#案例2 显示每个商品各个门店库存的总和</span><br><span class="line">#创建视图</span><br><span class="line">CREATE OR REPLACE VIEW seller_sku_amount AS SELECT</span><br><span class="line">sku_id,</span><br><span class="line">SUM(amount) AS amount_total</span><br><span class="line">FROM sellers_sku</span><br><span class="line">GROUP BY sku_id</span><br><span class="line">#使用视图查询</span><br><span class="line">SELECT </span><br><span class="line">seller_sku_amount.sku_id,</span><br><span class="line">seller_sku_amount.amount_total,</span><br><span class="line">goods_sku.*</span><br><span class="line"> FROM seller_sku_amount JOIN goods_sku ON goods_sku.`id` = seller_sku_amount.`sku_id`</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line">#原生SQL查询</span><br><span class="line">SELECT</span><br><span class="line">sellers_sku.sku_id,</span><br><span class="line">SUM(sellers_sku.amount) AS amount_total,</span><br><span class="line">goods_sku.*</span><br><span class="line">FROM sellers_sku</span><br><span class="line">JOIN goods_sku ON sellers_sku.`sku_id` = goods_sku.`id`</span><br><span class="line">GROUP BY sku_id</span><br><span class="line">ORDER BY amount_total DESC</span><br><span class="line">• 1</span><br><span class="line">• 2</span><br><span class="line">• 3</span><br><span class="line">• 4</span><br><span class="line">• 5</span><br><span class="line">• 6</span><br><span class="line">• 7</span><br><span class="line">• 8</span><br><span class="line">• 9</span><br><span class="line">• 10</span><br><span class="line">• 11</span><br><span class="line">• 12</span><br><span class="line">• 13</span><br><span class="line">• 14</span><br><span class="line">• 15</span><br><span class="line">• 16</span><br><span class="line">• 17</span><br><span class="line">• 18</span><br><span class="line">• 19</span><br><span class="line">• 20</span><br><span class="line">• 21</span><br><span class="line">• 22</span><br><span class="line">• 23</span><br><span class="line">• 24</span><br><span class="line">• 25</span><br><span class="line">• 26</span><br><span class="line">• 27</span><br><span class="line">• 28</span><br><span class="line">• 29</span><br><span class="line">• 30</span><br><span class="line">• 31</span><br><span class="line">• 32</span><br></pre></td></tr></table></figure><p>以上两个查询，使用视图查询的速度为比使用原生SQL查询的效率慢50%，随着数据量的增大，这个效率还会更慢（数据量越大，需要往临时表填充更多的数据）；<br />但是TEMPTABLE算法也不是没有好处的，TEMPTABLE算法创建临时表之后、并在完成语句处理之前，能够释放基表上的锁定。与MERGE算法相比，锁定释放的速度更快，这样，使用视图的其他客户端不会被屏蔽过长时间。<br />三、UNDEFINED算法<br />UNDEFINED算法没啥好区分的，直接引用文档的：</p><blockquote><p>对于UNDEFINED，MySQL将选择所要使用的算法。如果可能，它倾向于MERGE而不是TEMPTABLE，这是因为MERGE通常更有效，而且如果使用了临时表，视图是不可更新的。</p></blockquote>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/view/</id>
    <link href="https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/view/"/>
    <published>2019-03-05T11:42:57.000Z</published>
    <summary>《深入浅出 MySQL》视图笔记：CREATE VIEW 算法选项、WITH CHECK OPTION 及 updatable view 限制说明</summary>
    <title>深入浅出MySQL -- 视图</title>
    <updated>2026-06-09T08:46:25.960Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/categories/Database/MySQL/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/tags/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"深入浅出MySQL -- 函数","description":"《深入浅出 MySQL》函数章节占位笔记，计划整理 MySQL 常用内置函数与使用场景，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://cdn.nlark.com/yuque/0/2020/png/106206/1595926164567-00860ca0-0217-4144-920b-6c2556499689.png#align=left&display=inline&height=844&margin=%5Bobject%20Object%5D&name=image.png&originHeight=844&originWidth=1175&size=259655&status=done&style=none&width=1175","wordCount":2,"datePublished":"2019-03-04T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.960Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/function/"},"url":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/function/","inLanguage":"zh-CN","keywords":["Database","MySQL","深入浅出MySQL"],"articleSection":["Database","MySQL","深入浅出MySQL"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"深入浅出MySQL -- 函数","item":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/function/"}]}</script><h1 id="函数"><a href="#函数" class="headerlink" title="函数"></a>函数</h1><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595926164567-00860ca0-0217-4144-920b-6c2556499689.png#align=left&display=inline&height=844&margin=%5Bobject%20Object%5D&name=image.png&originHeight=844&originWidth=1175&size=259655&status=done&style=none&width=1175" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595926209847-d1ed3721-ea59-4afd-a3dd-1aa42bf4233f.png#align=left&display=inline&height=410&margin=%5Bobject%20Object%5D&name=image.png&originHeight=410&originWidth=1032&size=82628&status=done&style=none&width=1032" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595926390688-eb2e276f-8115-4abc-a8a1-e6e173f90d32.png#align=left&display=inline&height=660&margin=%5Bobject%20Object%5D&name=image.png&originHeight=660&originWidth=1006&size=142751&status=done&style=none&width=1006" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595927245457-39d4ab4b-a32a-4b2d-a4d5-8384b0f12085.png#align=left&display=inline&height=665&margin=%5Bobject%20Object%5D&name=image.png&originHeight=665&originWidth=1060&size=146159&status=done&style=none&width=1060" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595927351125-e7c92db9-3379-44a7-8600-7d9ec6eef73e.png#align=left&display=inline&height=897&margin=%5Bobject%20Object%5D&name=image.png&originHeight=897&originWidth=1098&size=200265&status=done&style=none&width=1098" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595927385816-25334990-18f3-43db-8627-b7d09b5f16cd.png#align=left&display=inline&height=606&margin=%5Bobject%20Object%5D&name=image.png&originHeight=606&originWidth=690&size=78147&status=done&style=none&width=690" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595927444093-89e91713-b56f-4d35-96ab-4c64d9c51e5d.png#align=left&display=inline&height=334&margin=%5Bobject%20Object%5D&name=image.png&originHeight=334&originWidth=1040&size=72396&status=done&style=none&width=1040" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595927576893-941379b8-5846-4f9c-b73a-e4b05b9fe0d2.png#align=left&display=inline&height=392&margin=%5Bobject%20Object%5D&name=image.png&originHeight=392&originWidth=971&size=76706&status=done&style=none&width=971" alt="image.png"></p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/function/</id>
    <link href="https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/function/"/>
    <published>2019-03-04T11:42:57.000Z</published>
    <summary>《深入浅出 MySQL》函数章节占位笔记，计划整理 MySQL 常用内置函数与使用场景，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>深入浅出MySQL -- 函数</title>
    <updated>2026-06-09T08:46:25.960Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/categories/Database/MySQL/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/tags/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"深入浅出MySQL -- 存储过程","description":"《深入浅出 MySQL》存储过程笔记：CREATE/ALTER/CALL 语法、权限要求、DEFINER/INVOKER 与 SQL SECURITY 特性","image":"https://ilongda.com/img/my.jpg","wordCount":1506,"datePublished":"2019-03-04T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.960Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/procedure/"},"url":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/procedure/","inLanguage":"zh-CN","keywords":["Database","MySQL","深入浅出MySQL"],"articleSection":["Database","MySQL","深入浅出MySQL"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"深入浅出MySQL -- 存储过程","item":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/procedure/"}]}</script><h1 id="存储过程"><a href="#存储过程" class="headerlink" title="存储过程"></a>存储过程</h1><p>权限检查</p><ol><li>用户需要有存储过程和函数的操作权限</li><li>创建 存储过程或函数, 需要 create routine 权限</li><li>修改 需要alter routine 权限</li><li>执行 需要 execute routine 权限</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">CREATE PROCEDURE sp_name ([proc_parameter[,...]])</span><br><span class="line">[characteristic ...] routine_body</span><br><span class="line"></span><br><span class="line">CREATE FUNCTION sp_name ([func_parameter[,...]])</span><br><span class="line">  RETURNS type</span><br><span class="line">  [characteristic ...] routine_body</span><br><span class="line">  proc_parameter:</span><br><span class="line">  [ IN | OUT | INOUT ] param_name type</span><br><span class="line">  func_parameter:</span><br><span class="line">  param_name type</span><br><span class="line"></span><br><span class="line">type:</span><br><span class="line">Any valid MySQL data type</span><br><span class="line"></span><br><span class="line">characteristic:</span><br><span class="line">LANGUAGE SQL</span><br><span class="line">  | [NOT] DETERMINISTIC</span><br><span class="line">  | &#123; CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA &#125;</span><br><span class="line">  | SQL SECURITY &#123; DEFINER | INVOKER &#125;</span><br><span class="line">  | COMMENT &#x27;string&#x27;</span><br><span class="line"></span><br><span class="line">routine_body:</span><br><span class="line">Valid SQL procedure statement or statements</span><br><span class="line"></span><br><span class="line">ALTER &#123;PROCEDURE | FUNCTION&#125; sp_name [characteristic ...]</span><br><span class="line"></span><br><span class="line">characteristic:</span><br><span class="line">  &#123; CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA &#125;</span><br><span class="line">  | SQL SECURITY &#123; DEFINER | INVOKER &#125;</span><br><span class="line">  | COMMENT &#x27;string&#x27;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">CALL spname([parameter[,...]</span><br></pre></td></tr></table></figure><ol><li>存储过程, 允许DDL, 提交(COMMIT&#x2F;ROLLBACK)</li><li>不允许 LOAD DATA INFILE</li><li>只支持create, 不支持create OR REPLACE, 否则执行alter</li><li>LANGUAGE SQL：说明下面过程的 BODY 是使用 SQL 语言编写，这条是系统默认的，</li></ol><p>为今后 MySQL 会支持的除 SQL 外的其他语言支持的存储过程而准备。</p><ol start="5"><li>[NOT] DETERMINISTIC：DETERMINISTIC 确定的，即每次输入一样输出也一样的程序，</li></ol><p>NOT DETERMINISTIC 非确定的，默认是非确定的。当前，这个特征值还没有被优化<br />程序使用。</p><ol start="6"><li>{ CONTAINS SQL | NO SQL | READS SQL DATA | MODIFIES SQL DATA }：这些特征值提供</li></ol><p>子程序使用数据的内在信息，这些特征值目前只是提供给服务器，并没有根据这些<br />特征值来约束过程实际使用数据的情况。CONTAINS SQL 表示子程序不包含读或写<br />数据的语句。NO SQL 表示子程序不包含 SQL 语句。READS SQL DATA 表示子程序包<br />含读数据的语句，但不包含写数据的语句。MODIFIES SQL DATA 表示子程序包含写<br />数据的语句。如果这些特征没有明确给定，默认使用的值是 CONTAINS SQL。</p><ol start="7"><li>SQL SECURITY { DEFINER | INVOKER }：可以用来指定子程序该用创建子程序者的许</li></ol><p>可来执行，还是使用调用者的许可来执行。默认值是 DEFINER。</p><ol start="8"><li>COMMENT ‘string’：存储过程或者函数的注释信息。</li></ol><p><br />EXAMPLE</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; DELIMITER $$</span><br><span class="line">mysql&gt; </span><br><span class="line">mysql&gt; CREATE PROCEDURE film_in_stock(IN p_film_id INT, IN p_store_id INT, OUT p_film_count </span><br><span class="line">INT)</span><br><span class="line"> -&gt; READS SQL DATA</span><br><span class="line"> -&gt; BEGIN</span><br><span class="line"> -&gt; SELECT inventory_id</span><br><span class="line"> -&gt; FROM inventory</span><br><span class="line"> -&gt; WHERE film_id = p_film_id</span><br><span class="line"> -&gt; AND store_id = p_store_id</span><br><span class="line"> -&gt; AND inventory_in_stock(inventory_id);</span><br><span class="line"> -&gt; </span><br><span class="line"> -&gt; SELECT FOUND_ROWS() INTO p_film_count;</span><br><span class="line"> -&gt; END $$</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; </span><br><span class="line">mysql&gt; DELIMITER ;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">mysql&gt; CALL film_in_stock(2,2,@a);</span><br><span class="line">  +--------------+</span><br><span class="line">  | inventory_id |</span><br><span class="line">  +--------------+</span><br><span class="line">  | 10 |</span><br><span class="line">  | 11 |</span><br><span class="line">  +--------------+</span><br><span class="line">  2 rows in set (0.00 sec)</span><br><span class="line">  Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; select @a;</span><br><span class="line">  +------+</span><br><span class="line">  | @a |</span><br><span class="line">  +------+</span><br><span class="line">  | 2 |</span><br><span class="line">  +------+</span><br><span class="line">  1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure><p>SHOW CREATE {PROCEDURE | FUNCTION} sp_name<br />mysql&gt; select * from routines where ROUTINE_NAME &#x3D; ‘film_in_stock’ \G<br /></p><p><a name="I7MyZ"></a></p><h2 id="变量"><a href="#变量" class="headerlink" title="变量"></a>变量</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">DECLARE var_name[,...] type [DEFAULT value]</span><br><span class="line">DECLARE last_month_start DATE;</span><br><span class="line">SET var_name = expr [, var_name = expr] ...</span><br><span class="line">SET last_month_start = DATE_SUB(CURRENT_DATE(), INTERVAL 1 MONTH);</span><br><span class="line">SELECT col_name[,...] INTO var_name[,...] table_expr</span><br><span class="line"></span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">CREATE FUNCTION get_customer_balance(p_customer_id INT, </span><br><span class="line">p_effective_date DATETIME) </span><br><span class="line">RETURNS DECIMAL(5,2)</span><br><span class="line">DETERMINISTIC</span><br><span class="line">READS SQL DATA</span><br><span class="line">BEGIN</span><br><span class="line"> …</span><br><span class="line"> DECLARE v_payments DECIMAL(5,2); #SUM OF PAYMENTS MADE PREVIOUSLY</span><br><span class="line"> …</span><br><span class="line"> SELECT IFNULL(SUM(payment.amount),0) INTO v_payments</span><br><span class="line"> FROM payment</span><br><span class="line"> WHERE payment.payment_date &lt;= p_effective_date</span><br><span class="line"> AND payment.customer_id = p_customer_id;</span><br><span class="line"> …</span><br><span class="line"> RETURN v_rentfees + v_overfees - v_payments;</span><br><span class="line">END $$</span><br></pre></td></tr></table></figure><p><a name="xCfbE"></a></p><h2 id="条件"><a href="#条件" class="headerlink" title="条件"></a>条件</h2><p>条件定义</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">DECLARE condition_name CONDITION FOR condition_value</span><br><span class="line"></span><br><span class="line">condition_value:</span><br><span class="line">  SQLSTATE [VALUE] sqlstate_value</span><br><span class="line">  | mysql_error_code</span><br></pre></td></tr></table></figure><p>条件处理</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">DECLARE handler_type HANDLER FOR condition_value[,...] sp_statement</span><br><span class="line"></span><br><span class="line">handler_type:</span><br><span class="line">  CONTINUE</span><br><span class="line">  | EXIT</span><br><span class="line">  | UNDO</span><br><span class="line">condition_value:</span><br><span class="line">  SQLSTATE [VALUE] sqlstate_value</span><br><span class="line">  | condition_name</span><br><span class="line">  | SQLWARNING</span><br><span class="line">  | NOT FOUND</span><br><span class="line">  | SQLEXCEPTION</span><br><span class="line">  | mysql_error_code</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; delimiter $$</span><br><span class="line">mysql&gt; </span><br><span class="line">mysql&gt; CREATE PROCEDURE actor_insert ()</span><br><span class="line"> -&gt; BEGIN</span><br><span class="line"> -&gt; DECLARE CONTINUE HANDLER FOR SQLSTATE &#x27;23000&#x27; SET @x2 = 1;</span><br><span class="line"> -&gt; SET @x = 1;</span><br><span class="line"> -&gt; INSERT INTO actor(actor_id,first_name,last_name) VALUES (201,&#x27;Test&#x27;,&#x27;201&#x27;);</span><br><span class="line"> -&gt; SET @x = 2;</span><br><span class="line"> -&gt; INSERT INTO actor(actor_id,first_name,last_name) VALUES (1,&#x27;Test&#x27;,&#x27;1&#x27;);</span><br><span class="line"> -&gt; SET @x = 3;</span><br><span class="line"> -&gt; END;</span><br><span class="line"> -&gt; $$</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; delimiter ;</span><br><span class="line">mysql&gt; call actor_insert();</span><br><span class="line">Query OK, 0 rows affected (0.06 sec)</span><br><span class="line">mysql&gt; select @x,@x2;</span><br><span class="line">+------+------+</span><br><span class="line">| @x | @x2 |</span><br><span class="line">+------+------+</span><br><span class="line">| 3 | 1 |</span><br><span class="line">+------+------+</span><br><span class="line">1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">--捕获 mysql-error-code：</span><br><span class="line">DECLARE CONTINUE HANDLER FOR 1062 SET @x2 = 1;</span><br><span class="line">--事先定义 condition_name：</span><br><span class="line">DECLARE DuplicateKey CONDITION FOR SQLSTATE &#x27;23000&#x27;;</span><br><span class="line">DECLARE CONTINUE HANDLER FOR DuplicateKey SET @x2 = 1;</span><br><span class="line">--捕获 SQLEXCEPTION</span><br><span class="line">DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET @x2 = 1;</span><br></pre></td></tr></table></figure><p><a name="tydua"></a></p><h2 id="光标"><a href="#光标" class="headerlink" title="光标"></a>光标</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">DECLARE cursor_name CURSOR FOR select_statement</span><br><span class="line">OPEN cursor_n</span><br><span class="line">FETCH cursor_name INTO var_name [, var_name] ...</span><br><span class="line">CLOSE cursor_name</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; delimiter $$</span><br><span class="line">mysql&gt; </span><br><span class="line">mysql&gt; CREATE PROCEDURE payment_stat ()</span><br><span class="line"> -&gt; BEGIN</span><br><span class="line"> -&gt; DECLARE i_staff_id int;</span><br><span class="line"> -&gt; DECLARE d_amount decimal(5,2);</span><br><span class="line"> -&gt; DECLARE cur_payment cursor for select staff_id,amount from payment;</span><br><span class="line"> -&gt; DECLARE EXIT HANDLER FOR NOT FOUND CLOSE cur_payment;</span><br><span class="line"> -&gt; </span><br><span class="line"> -&gt; set @x1 = 0;</span><br><span class="line"> -&gt; set @x2 = 0;</span><br><span class="line"> -&gt; </span><br><span class="line"> -&gt; OPEN cur_payment;</span><br><span class="line"> -&gt; </span><br><span class="line"> -&gt; REPEAT</span><br><span class="line"> -&gt; FETCH cur_payment INTO i_staff_id, d_amount;</span><br><span class="line"> -&gt; if i_staff_id = 2 then</span><br><span class="line"> -&gt; set @x1 = @x1 + d_amount;</span><br><span class="line"> -&gt; else </span><br><span class="line"> -&gt; set @x2 = @x2 + d_amount;</span><br><span class="line"> -&gt; end if;</span><br><span class="line"> -&gt; UNTIL 0 END REPEAT;</span><br><span class="line"> -&gt; </span><br><span class="line"> -&gt; CLOSE cur_payment;</span><br><span class="line"> -&gt; </span><br><span class="line"> -&gt; END;</span><br><span class="line"> -&gt; $$</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; delimiter ;</span><br></pre></td></tr></table></figure><p><a name="UhUFx"></a></p><h2 id="流程控制"><a href="#流程控制" class="headerlink" title="流程控制"></a>流程控制</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">IF search_condition THEN statement_list</span><br><span class="line">  [ELSEIF search_condition THEN statement_list] ...</span><br><span class="line">  [ELSE statement_list]</span><br><span class="line">END IF</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">CASE case_value</span><br><span class="line">  WHEN when_value THEN statement_list</span><br><span class="line">  [WHEN when_value THEN statement_list] ...</span><br><span class="line">  [ELSE statement_list]</span><br><span class="line">END CASE</span><br><span class="line">Or: </span><br><span class="line">CASE</span><br><span class="line">  WHEN search_condition THEN statement_list</span><br><span class="line">  [WHEN search_condition THEN statement_list] ...</span><br><span class="line">  [ELSE statement_list]</span><br><span class="line">END CASE</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">[begin_label:] LOOP</span><br><span class="line">statement_list</span><br><span class="line">END LOOP [end_label]</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; CREATE PROCEDURE actor_insert ()</span><br><span class="line"> -&gt; BEGIN</span><br><span class="line"> -&gt; set @x = 0;</span><br><span class="line"> -&gt; ins: LOOP</span><br><span class="line"> -&gt; set @x = @x + 1;</span><br><span class="line"> -&gt; IF @x = 100 then</span><br><span class="line"> -&gt; leave ins;</span><br><span class="line"> -&gt; END IF;</span><br><span class="line"> -&gt; INSERT INTO actor(first_name,last_name) VALUES (&#x27;Test&#x27;,&#x27;201&#x27;);</span><br><span class="line"> -&gt; END LOOP ins;</span><br><span class="line"> -&gt; END;</span><br></pre></td></tr></table></figure><p><br />iterate 语句  —&gt; 类似continue 语法</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; CREATE PROCEDURE actor_insert ()</span><br><span class="line"> -&gt; BEGIN</span><br><span class="line"> -&gt; set @x = 0;</span><br><span class="line"> -&gt; ins: LOOP</span><br><span class="line"> -&gt; set @x = @x + 1;</span><br><span class="line"> -&gt; IF @x = 10 then</span><br><span class="line"> -&gt; leave ins; </span><br><span class="line"> -&gt; ELSEIF mod(@x,2) = 0 then</span><br><span class="line"> -&gt; ITERATE ins;</span><br><span class="line"> -&gt; END IF;</span><br><span class="line"> -&gt; INSERT INTO actor(actor_id,first_name,last_name) VALUES (@x+200,&#x27;Test&#x27;,@x);</span><br><span class="line"> -&gt; END LOOP ins;</span><br><span class="line"> -&gt; END;</span><br><span class="line"> -&gt; $$</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; call actor_insert();</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; select actor_id,first_name,last_name from actor where first_name=&#x27;Test&#x27;;</span><br><span class="line">+----------+------------+-----------+</span><br><span class="line">| actor_id | first_name | last_name |</span><br><span class="line">+----------+------------+-----------+</span><br><span class="line">| 201 | Test | 1 |</span><br><span class="line">| 203 | Test | 3 |</span><br><span class="line">| 205 | Test | 5 |</span><br><span class="line">| 207 | Test | 7 |</span><br><span class="line">| 209 | Test | 9 |</span><br><span class="line">+----------+------------+-----------+5 rows in set (0.00 sec)</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">-&gt; REPEAT</span><br><span class="line"> -&gt; FETCH cur_payment INTO i_staff_id, d_amount;</span><br><span class="line"> -&gt; if i_staff_id = 2 then</span><br><span class="line"> -&gt; set @x1 = @x1 + d_amount;</span><br><span class="line"> -&gt; else </span><br><span class="line"> -&gt; set @x2 = @x2 + d_amount;</span><br><span class="line"> -&gt; end if;</span><br><span class="line"> -&gt; UNTIL 0 END REPEAT;</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; delimiter $$</span><br><span class="line">mysql&gt; CREATE PROCEDURE loop_demo ()</span><br><span class="line"> -&gt; BEGIN</span><br><span class="line"> -&gt; set @x = 1 , @x1 = 1;</span><br><span class="line"> -&gt; REPEAT</span><br><span class="line"> -&gt; set @x = @x + 1;</span><br><span class="line"> -&gt; until @x &gt; 0 end repeat;</span><br><span class="line">  -&gt; </span><br><span class="line">  -&gt; while @x1 &lt; 0 do</span><br><span class="line">  -&gt; set @x1 = @x1 + 1;</span><br><span class="line">  -&gt; end while;</span><br><span class="line">  -&gt; END;</span><br><span class="line">  -&gt; $$</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; delimiter ;</span><br><span class="line">mysql&gt; call loop_demo();</span><br><span class="line">Query OK, 0 rows affected (0.00 sec)</span><br><span class="line">mysql&gt; select @x,@x1;</span><br><span class="line">+------+------+| @x | @x1 |</span><br><span class="line">+------+------+| 2 | 1 |</span><br><span class="line">+------+------+1 row in set (0.00 sec)</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/procedure/</id>
    <link href="https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/procedure/"/>
    <published>2019-03-04T11:42:57.000Z</published>
    <summary>《深入浅出 MySQL》存储过程笔记：CREATE/ALTER/CALL 语法、权限要求、DEFINER/INVOKER 与 SQL SECURITY 特性</summary>
    <title>深入浅出MySQL -- 存储过程</title>
    <updated>2026-06-09T08:46:25.960Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/categories/Database/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/categories/Database/MySQL/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="MySQL" scheme="https://ilongda.com/tags/MySQL/"/>
    <category term="深入浅出MySQL" scheme="https://ilongda.com/tags/%E6%B7%B1%E5%85%A5%E6%B5%85%E5%87%BAMySQL/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"深入浅出MySQL -- 第一章 数据类型","description":"《深入浅出 MySQL》数据类型笔记：整数/bit/timestamp 行为差异、AUTO_INCREMENT 约束及不同引擎选型建议","image":"https://cdn.nlark.com/yuque/0/2020/png/106206/1595919595225-891a1e9f-9fca-480a-8119-dc5482132419.png#align=left&display=inline&height=879&margin=%5Bobject%20Object%5D&name=image.png&originHeight=879&originWidth=1033&size=215131&status=done&style=none&width=1033","wordCount":682,"datePublished":"2019-03-03T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.960Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/datatype/"},"url":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/datatype/","inLanguage":"zh-CN","keywords":["Database","MySQL","深入浅出MySQL"],"articleSection":["Database","MySQL","深入浅出MySQL"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"深入浅出MySQL -- 第一章 数据类型","item":"https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/datatype/"}]}</script><h1 id="第一章，-数据类型"><a href="#第一章，-数据类型" class="headerlink" title="第一章， 数据类型"></a>第一章， 数据类型</h1><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595919595225-891a1e9f-9fca-480a-8119-dc5482132419.png#align=left&display=inline&height=879&margin=%5Bobject%20Object%5D&name=image.png&originHeight=879&originWidth=1033&size=215131&status=done&style=none&width=1033" alt="image.png"><br /><br><br /><br><br /><br><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595919615611-ee819d7c-96c2-43d0-9e91-ee33bfd7eb28.png#align=left&display=inline&height=321&margin=%5Bobject%20Object%5D&name=image.png&originHeight=321&originWidth=714&size=27092&status=done&style=none&width=714" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595919626480-57547438-374e-42cc-9fd1-cb3c43802a23.png#align=left&display=inline&height=487&margin=%5Bobject%20Object%5D&name=image.png&originHeight=487&originWidth=896&size=39853&status=done&style=none&width=896" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595919702102-ea17ff97-f9e3-4fc8-a9a0-7fb13f7d1cd1.png#align=left&display=inline&height=747&margin=%5Bobject%20Object%5D&name=image.png&originHeight=747&originWidth=567&size=186009&status=done&style=none&width=567" alt="image.png"></p><ol><li>存储超过定义的宽度，其实没有影响， 对插入的数据有任何影响，还是按照类型的实际精度进行保存，这是，宽度格式实际已经没有意义，左边不会再填充任何的“0”字符。</li><li>如果一个列指定为zerofill，则MySQL 自动为该列添加UNSIGNED 属性。</li></ol><p><br />整数类型还有一个属性：AUTO_INCREMENT。在需要产生唯一标识符或顺序值时，<br /><br>可利用此属性，这个属性只用于整数类型。AUTO_INCREMENT 值一般从1 开始，每行增加1。<br /><br>在插入NULL 到一个AUTO_INCREMENT 列时，MySQL 插入一个比该列中当前最大值大1 的值。<br /><br>一个表中最多只能有一个AUTO_INCREMENT列。对于任何想要使用AUTO_INCREMENT 的列，<br /><br>应该定义为NOT NULL，并定义为PRIMARY KEY 或定义为UNIQUE 键。例如，可按下列<br />任何一种方式定义AUTO_INCREMENT 列：<br /></p><p>CREATE TABLE AI (ID INT AUTO_INCREMENT NOT NULL PRIMARY KEY);<br /><br>CREATE TABLE AI(ID INT AUTO_INCREMENT NOT NULL ,PRIMARY KEY(ID));<br /><br>CREATE TABLE AI (ID INT AUTO_INCREMENT NOT NULL ,UNIQUE(ID));<br /></p><br /><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595919957819-8047bbd5-3653-438c-95e6-5f175fc3080e.png#align=left&display=inline&height=461&margin=%5Bobject%20Object%5D&name=image.png&originHeight=461&originWidth=939&size=168782&status=done&style=none&width=939" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595920355801-8f9acc62-2606-4733-883b-94f72f1a4798.png#align=left&display=inline&height=998&margin=%5Bobject%20Object%5D&name=image.png&originHeight=998&originWidth=641&size=291157&status=done&style=none&width=641" alt="image.png"><br /></p><p><br /><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1596185560789-e66ae7e2-43d9-4515-b4f0-a61c1e6fab3a.png#align=left&display=inline&height=423&margin=%5Bobject%20Object%5D&name=image.png&originHeight=423&originWidth=803&size=77256&status=done&style=none&width=803" alt="image.png"><br /><br>关于bit， mysql 8.0 行为已经和mysql 5.6 行为不一致<br /><br><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595920675239-9f355b5e-4cd2-43dd-9c55-bd7d0865cf00.png#align=left&display=inline&height=484&margin=%5Bobject%20Object%5D&name=image.png&originHeight=484&originWidth=710&size=111232&status=done&style=none&width=710" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595921433879-b29a927e-1f63-404d-a007-fb4d6b66e9bf.png#align=left&display=inline&height=902&margin=%5Bobject%20Object%5D&name=image.png&originHeight=902&originWidth=1020&size=257656&status=done&style=none&width=1020" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595921681056-c95bdde2-5d38-4ddf-8754-8895cdf71a99.png#align=left&display=inline&height=207&margin=%5Bobject%20Object%5D&name=image.png&originHeight=207&originWidth=473&size=53745&status=done&style=none&width=473" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595921893924-40cccd2e-8291-4702-9a79-7485f7b90b61.png#align=left&display=inline&height=439&margin=%5Bobject%20Object%5D&name=image.png&originHeight=439&originWidth=762&size=143243&status=done&style=none&width=762" alt="image.png"><br /></p><p>第一个timestamp 默认值是current_timestamp, 但第二个timestamp 默认为0<br />timestamp 和时区相关， 插入日期时， 先转换为本地时区后存放， 取出时，也是先取出，后转换本地时区。 TIMESTAMP的取值范围为19700101080001到2038年的某一天<br />举例<br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595922021496-10f9610f-33e1-435b-b024-e350a467b83c.png#align=left&display=inline&height=157&margin=%5Bobject%20Object%5D&name=image.png&originHeight=157&originWidth=547&size=14891&status=done&style=none&width=547" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595922036111-7c0bfd13-f1d5-4140-848c-29da034c1379.png#align=left&display=inline&height=220&margin=%5Bobject%20Object%5D&name=image.png&originHeight=220&originWidth=517&size=12582&status=done&style=none&width=517" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595922050554-c5f2b5e1-aa8a-44cc-b879-12e1b9f35111.png#align=left&display=inline&height=324&margin=%5Bobject%20Object%5D&name=image.png&originHeight=324&originWidth=539&size=20975&status=done&style=none&width=539" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595922133155-80a91c7b-0f92-4854-bb06-c848b40aa7ed.png#align=left&display=inline&height=167&margin=%5Bobject%20Object%5D&name=image.png&originHeight=167&originWidth=936&size=44543&status=done&style=none&width=936" alt="image.png"><br /><br><br /><br><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595922232402-f91be9ee-0f1d-49cb-a90b-e5935f94f6a8.png#align=left&display=inline&height=700&margin=%5Bobject%20Object%5D&name=image.png&originHeight=700&originWidth=951&size=177527&status=done&style=none&width=951" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595923172517-cd42bb76-618f-4fbf-8010-bf454ba9b5f8.png#align=left&display=inline&height=538&margin=%5Bobject%20Object%5D&name=image.png&originHeight=538&originWidth=902&size=141609&status=done&style=none&width=902" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595923213332-5b0c9f78-42cc-4586-8ddf-e958ccd962b7.png#align=left&display=inline&height=447&margin=%5Bobject%20Object%5D&name=image.png&originHeight=447&originWidth=913&size=45723&status=done&style=none&width=913" alt="image.png"><br /></p><p>􀁺 MyISAM 存储引擎：建议使用固定长度的数据列代替可变长度的数据列。<br /><br>􀁺 MEMORY 存储引擎：目前都使用固定长度的数据行存储，因此无论使用CHAR 或<br />VARCHAR 列都没有关系。两者都是作为CHAR 类型处理。<br /><br>􀁺 InnoDB 存储引擎：建议使用VARCHAR 类型。对于InnoDB 数据表，内部的行存储<br />124<br />格式没有区分固定长度和可变长度列（所有数据行都使用指向数据列值的头指针），因此在<br />本质上，使用固定长度的CHAR 列不一定比使用可变长度VARCHAR 列性能要好。因而，主<br />要的性能因素是数据行使用的存储总量。由于CHAR 平均占用的空间多于VARCHAR，因此使<br />用VARCHAR 来最小化需要处理的数据行的存储总量和磁盘I&#x2F;O 是比较好的。<br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595923520355-8e7bb3f2-73f4-4c40-8b7c-7d5ebfdbbc66.png#align=left&display=inline&height=760&margin=%5Bobject%20Object%5D&name=image.png&originHeight=760&originWidth=944&size=148153&status=done&style=none&width=944" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595923972204-ad5b7de1-70e4-4d76-9d35-4e5529932533.png#align=left&display=inline&height=691&margin=%5Bobject%20Object%5D&name=image.png&originHeight=691&originWidth=909&size=125301&status=done&style=none&width=909" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595924032346-02ddb43e-2828-44b5-a79e-bde8791b2155.png#align=left&display=inline&height=231&margin=%5Bobject%20Object%5D&name=image.png&originHeight=231&originWidth=1105&size=88921&status=done&style=none&width=1105" alt="image.png"><br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595924563168-2177052f-fcaa-4669-acc8-52ff766755c2.png#align=left&display=inline&height=320&margin=%5Bobject%20Object%5D&name=image.png&originHeight=320&originWidth=542&size=73284&status=done&style=none&width=542" alt="image.png"><br /></p><p>从上面的例子中，可以看出ENUM 类型是忽略大小写的，对’M’、’f’在存储的时候将它们都转<br /><br>成了大写，还可以看出对于插入不在ENUM 指定范围内的值时，并没有返回警告，而是插<br />入了enum(‘M’,’F’)的第一值’M’，这点用户在使用时要特别注意。<br /></p><p><img data-src="https://cdn.nlark.com/yuque/0/2020/png/106206/1595925540639-a161ed69-0ba2-45b3-9e86-df21bd62d384.png#align=left&display=inline&height=920&margin=%5Bobject%20Object%5D&name=image.png&originHeight=920&originWidth=1094&size=149367&status=done&style=none&width=1094" alt="image.png"></p>]]>
    </content>
    <id>https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/datatype/</id>
    <link href="https://ilongda.com/2019/docs/mysql/%E6%B7%B1%E5%85%A5%E8%BF%81%E5%87%BAMySQL_reading_notes/datatype/"/>
    <published>2019-03-03T11:42:57.000Z</published>
    <summary>《深入浅出 MySQL》数据类型笔记：整数/bit/timestamp 行为差异、AUTO_INCREMENT 约束及不同引擎选型建议</summary>
    <title>深入浅出MySQL -- 第一章 数据类型</title>
    <updated>2026-06-09T08:46:25.960Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《F1 Query -- Declarative Querying at Scale》","description":"Google F1 Query 论文笔记：Spanner/BigQuery 胶水式 HTAP 架构分析，及 OLTP 点查与 OLAP 统一查询层设计反思","image":"https://ilongda.com/assets/f1_arc.png","wordCount":1018,"datePublished":"2018-12-26T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.962Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/paper/f1/"},"url":"https://ilongda.com/2018/docs/paper/f1/","inLanguage":"zh-CN","keywords":["论文","数据库"],"articleSection":["论文","数据库"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"《F1 Query -- Declarative Querying at Scale》","item":"https://ilongda.com/2018/docs/paper/f1/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>论文是很早以前看的， 后来把笔记梳理一下。<br>F1 就是一套胶水系统， 将oltp和olap 系统粘在一起提供一个统一接口层和服务层， 类似数据库里面的HTAP系统， 但数据库htap系统往往是自身提供oltp和olap 服务， 而f1 是通过不同的系统来达到htap的效果。<br>F1 有一个背景就是现有很多数据库系统，如spanner， bigquery， dremel， 如果让应用无缝的在这么多系统中切换，一套接口，一套服务完成所有的事情。<br>今天的 TiDB 有一点点f1 的味道， 上层tidb server + tikv， tispark +  tiflash，不过tidb 朝着tidb server + tikv&#x2F;tiflash的方向演进， 融合程度更高一些， 不过f1 在oltp 上主要是依赖spanner 来完成。<br>曾经的hybriddb 更类似f1 这种架构。 </p><p>坦白讲： 这种胶水系统没有什么前途， 对于oltp 业务， 对时延是非常敏感， 增加一层胶水系统， 不仅让时延增长很多，而且带来不确定性， 动不动抖动一下， 这些对oltp都是无法接受的， 因此粘合的场景是大数据的和olap的业务， 对于中小型企业， 大数据的系统太复杂， 一个olap 就够用，看看aws 上redshift 大行其道，就知道这个事实， 如果粘合多个olap，那就更不现实， 哪家公司没事干运行一大堆各式各样的olap系统， olap系统基本上就是赢者通吃， 没有那么多一个业务要跑好几个olap系统的， 所以这种胶水系统是没有什么前途的。 最后，google自己的员工透露到现在为止f1 已经事实上失败了。 </p><p>架构图：<br><img data-src="/assets/f1_arc.png" alt="f1_arc"></p><span id="more"></span><h2 id="需求"><a href="#需求" class="headerlink" title="需求"></a>需求</h2><ol><li>存储计算分离， 从架构上就可以看出计算层和存储层已经分离</li><li>可以支持跨机房部署， 不过跨机房部署，很多时候是依赖底层的存储系统来完成， 比如spanner 本身就可以支持跨机房</li><li>同时支持 oltp 尤其是点查， olap， 以及大型的etl请求</li><li>可以很容易提高并发度来支持大数据量的计算。 </li><li>支持UDF&#x2F;UDAF&#x2F;TVF</li></ol><h2 id="模块介绍"><a href="#模块介绍" class="headerlink" title="模块介绍"></a>模块介绍</h2><ul><li>F1 master,  对worker&#x2F;节点 进行监控， 对查询进行监控</li><li>F1 Server<ul><li>对于oltp 查询（点查）， 负责查询的执行， 个人理解， 做一个转义层，转成底层spanner的请求</li><li>对于olap的请求， 其实就是类似前端节点功能， 负责一些sql 解析，生成执行计划，查询catalog之类的工作</li></ul></li><li>F1 worker: 就是计算节点， 类似mpp数据库的worker节点</li><li>F1 server&#x2F;worker 是无状态的，不存数据， master节点会进行实时监控， 并且做failover 和扩容和缩容</li><li>catakig service ：  各种异构的数据的元数据存放在这里， 对用户展示一个统一视图</li><li>batch metadata： batch execution 模式下，任务的元信息，如执行计划</li><li>udf server， 一个比较有意思的模块， <ul><li>在执行引擎以外，专门存储udf， 执行引擎和udf server 进行rpc 进行交互</li><li>可以对执行引擎做一些保护， 资源隔离之类的事情</li></ul></li><li>存储系统：<ul><li>可以是spanner， 这些主要是针对oltp的</li><li>分布式 file system （colossus）， file system</li><li>其他的data source</li></ul></li></ul><h2 id="使用"><a href="#使用" class="headerlink" title="使用"></a>使用</h2><ul><li>SQL : 兼容sql 2011， 共用在dremel， bigquery， spanner， 可以多个应用迁移</li><li>数据写入<ul><li>默认是分布式文件系统</li><li>可以export 到指定存储</li><li>支持session 级别临时表</li></ul></li><li>执行模式<ul><li>central execution： 小查询， 单线程执行， 直接在server 就干了</li><li>batch execution： 超大查询， 生成mapreduce 任务&#x2F;flumejava sql 任务，放到后台大数据平台进行计算<ul><li>执行非常类似spark方式， 一个stage 一个stage 执行，前一个stage 落盘， 当stage 挂了可以只重启单stage</li></ul></li><li>distributed execution： 普通olap 执行， 分布式执行， server 起到前端功能， 执行在worker 节点进行执行</li></ul></li></ul>]]>
    </content>
    <id>https://ilongda.com/2018/docs/paper/f1/</id>
    <link href="https://ilongda.com/2018/docs/paper/f1/"/>
    <published>2018-12-26T11:42:57.000Z</published>
    <summary>Google F1 Query 论文笔记：Spanner/BigQuery 胶水式 HTAP 架构分析，及 OLTP 点查与 OLAP 统一查询层设计反思</summary>
    <title>《F1 Query -- Declarative Querying at Scale》</title>
    <updated>2026-06-09T08:46:25.962Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="读书笔记" scheme="https://ilongda.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <category term="读书笔记" scheme="https://ilongda.com/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《刻意练习》读后感","description":"《刻意练习》读书笔记：反驳天才与简单一万小时论，总结有目标、专注、反馈与走出舒适区四要素训练法，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://ilongda.com/img/keyilianxi.jpg","wordCount":707,"datePublished":"2018-12-13T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.937Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/keyilianxi/"},"url":"https://ilongda.com/2018/keyilianxi/","inLanguage":"zh-CN","keywords":["读书笔记"],"articleSection":["读书笔记"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"读书笔记","item":"https://ilongda.com/categories/读书笔记/"},{"@type":"ListItem","position":3,"name":"《刻意练习》读后感","item":"https://ilongda.com/2018/keyilianxi/"}]}</script><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/keyilianxi.jpg"  alt="《刻意练习》读后感"></div><p>《刻意练习》</p><p>鲁肃推荐的一本书， 非常有意思，值得推荐。 </p><p>成功都是靠大量不断提升的训练， 而刻意练习就是有针对性的训练。<br>在2000年， 英国科学家 对伦敦的出租车司机进行观察，出租车司机需要每天记住地图和所有标志性的建筑，用核磁共振观察16位出租车司机和普通50位男性相比，储存记忆的海马体要明显大很多。另外，对79名刚参加出租车考试的司机进行观察，在刚开始考试时， 大家的海马体在一个水平线上， 几年后， 79名中的41名仍然是出租车司机，另外38名中途放弃了， 结果这41名出租车司机的海马体要明显大于非出租车的38名非司机。</p><span id="more"></span><p>几种错误的观点：</p><ol><li>天才并不是天生的（或者基因决定的）。 拥有完美音高的人的概率是万分之1， 常识认为拥有完美音高的人都是天才是天生的， 莫扎特具有完美音高， 被认为是天生就是音乐天才。 但仔细分析， 莫扎特出生在音乐世界，3岁开始就接受高强度音乐训练， 让他拥有完美音高。 同样，2014年日本东京进行一次科学实验， 对24个普通的2～6岁小孩进行音乐训练（识别和弦），结果在一年半的时间内， 意外发现，这24个小朋友都被培养成拥有完美音高的人，也就是一个普通的人只要接受专业训练都可以发展某些看似天才的能力。</li><li>一万小时理论， 重复性的一万小时训练，当技能达到一定水准后（无意识状态后），再进行重复性的训练，并不能带来技能的提升，甚至技能后退。 比如开车，当学会开车后， 并且开了一年后，开车变成一种条件反射后， 再开多久的车，技术也没有多少进步，甚至后退。</li></ol><p>只有针对性的练习才能达到提高的目的</p><ol><li>要有目标，  只有有了目标，训练才会有了方向性， 一个大的目标可以拆解成为很多小的目标。</li><li>练习需要专注， 只有专注，才能突破自己</li><li>练习需要不断反馈，  哪些做的好，哪些做的不好，需要不断调整练习，克服不足。</li><li>练习需要不断挑战自己， 强迫自己走出舒适区。 如果持续不断待在舒适区， 就永远无法进步，大脑有偏爱稳定性的倾向。 离开舒适区，才能获得更大潜能。</li></ol>]]>
    </content>
    <id>https://ilongda.com/2018/keyilianxi/</id>
    <link href="https://ilongda.com/2018/keyilianxi/"/>
    <published>2018-12-13T11:42:57.000Z</published>
    <summary>《刻意练习》读书笔记：反驳天才与简单一万小时论，总结有目标、专注、反馈与走出舒适区四要素训练法，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>《刻意练习》读后感</title>
    <updated>2026-06-09T08:46:25.937Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《The Cascades Framework for Query Optimization》","description":"Cascades 查询优化框架论文笔记：规则对象化、memo 搜索 task 调度及逻辑表达式枚举与代价优化流程，更多细节与示例见正文。","image":"https://ilongda.com/assets/cascade_optimize_task.jpg","wordCount":5430,"datePublished":"2018-10-26T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.961Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/paper/cascade/"},"url":"https://ilongda.com/2018/docs/paper/cascade/","inLanguage":"zh-CN","keywords":["论文","数据库"],"articleSection":["论文","数据库"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"《The Cascades Framework for Query Optimization》","item":"https://ilongda.com/2018/docs/paper/cascade/"}]}</script><h1 id="The-Cascades-Framework-for-Query-Optimization"><a href="#The-Cascades-Framework-for-Query-Optimization" class="headerlink" title="The Cascades Framework for Query Optimization"></a>The Cascades Framework for Query Optimization</h1><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在不损失Exodus 和volcano的模块性， 扩展性和动态编程， prototype的memorization 的情况下，解决volocao的一些缺点：</p><ul><li>rules<ul><li>rule as objects</li><li>rule-specific 指导， 允许一组规则。 探索（生成各种等价逻辑expression）和优化（映射逻辑计划到物理计划）都可以有个数据库设计者进行控制和规范。</li><li>有一些规则用于， 映射输入pattern 到DBI-supplied 函数</li><li>有一些rule 来插入 enforcer 或 glu 算子</li><li>带替代品的规则 构成一个复杂表达式</li><li>规则用promise 进行排序（规则按权重排序）</li><li>有一些schema-specific 的规则用于物化视图</li><li>用rules或function来控制operator的参数。 </li><li>function rules和树算子可以通过DBI（database implementor）的supplied function 直接控制复杂树的item 操作。</li></ul></li><li>Framework<ul><li>搜索的方式， 或者采用rule来指导， 或者穷举方式</li><li>有一些用于schema-specific 和query-specific 规则的工具</li><li>有一些抽象接口类， 他们用于定义DBI-优化器 接口 和DBI-定义 的子类层次结构。 干净的接口和实现 充分利用C++ 的抽象机制</li><li>算子既可以是逻辑的， 又可以是物理的， 如谓词</li><li>pattern 是匹配一个子树</li><li>优化task 类似data structure</li><li>对逻辑表达式 进行等价增量枚举， 对评估逻辑属性进行增量改进</li><li>扩展性tracing 支持</li><li>一组工具来 并行search， 部分排序好的cost计算 和动态计划</li></ul></li></ul><h2 id="优化算法和task"><a href="#优化算法和task" class="headerlink" title="优化算法和task"></a>优化算法和task</h2><p><img data-src="/assets/cascade_optimize_task.jpg" alt="image"></p><ol><li>首先copy 原始query 到内部memo 数据结构中</li><li>启动一个task 优化 原始query 树root node </li><li>依次迭代一层一层优化子树。</li></ol><p>将优化算法切分为几个task， task 是一个对象， 有一个执行函数。 task 相对过程调用来说扩展性更好。 </p><ol><li>可以用一个大的task 存放很多子task</li><li>task实现为类似堆栈， 后入先出。</li><li>task 很容易根据启发式规则来进行一些排序。</li><li>可以用topology来展示task 的依赖关系和先后顺序， 也可以方便进行并行search。 </li><li>目前调度task 类似LIFO stack， 调度一个task 类似调用一个函数， task 完成是在子task 完成后。</li></ol><p>优化目标： 对一个group或表达式 用要求的和排除的物理属性下进行优化， 优化的代价不超过限定值。 要么成功输出一个plan， 要么失败。 找到一个最佳plan， 对这个group 内所有表达式都适用并且将规则应用到每个表达式上， 优化一个表达式通常从单表达式开始。 </p><p>在volcano search 逻辑中， 第一部分是应用转换规则做各种逻辑变化， 第二阶段执行实现规则， 并选取最佳plan<br>在cascade 中， 首先检查 这个优化目标之前有没有执行过， 如果执行，则返回之前search的结果， 这个动态编程和memorization 很重要的一个步骤。 另外抛弃了volcano 的2阶段优化。 他根据需要执行transformation rule， 根据一个给定的pattern 生成这个group的所有成员， 这个pattern 是task 定义的部分， 是规则的before-pattern或anticedent-pattern （前置pattern）的子树。同样可以避免一些重复工作， 在进行pattern 匹配时，可以检查这个group或表达式是不是之前就用过这个pattern， 如果用过，就结束这个task， 可以直接使用之前的search 结果。 有一个组件“pattern memory”来管理用过的pattern。 在volcano， 第一阶段使用了穷举策略， 这个产生的等价表达式太多了。 在cascade， 如果有一些guidance， 则可以做一些搜索路径的裁剪， 允许对同一个group使用不同的pattern进行多次操作。 在memo 中， 有一个bit map 来表示哪些transformation rule 已经被使用了（从而可以避免被再次使用）。 最差的情况下， casecade 如果没有任何guidance， 也会和volcano 的搜索效率一样。<br>如果guidance 不正确，就会导致不正确的搜索空间裁剪， 有2种技术用于guidance， 第一： 检查所有的规则（尤其是top 算子的规则的前置pattern和后置pattern）， 我们找出在一个单独规则使用中，那些算子可以影视为其他算子。 通过考虑闭包的传递性，排除一些规则。 第二 由DBI 实现一些机制来指导。<br>因为算子既可能是逻辑的又可能是物理的， 同样规则也是既可能是逻辑的又可能是物理的。<br>执行应用规则非常复杂， 分4个步骤， </p><ol><li>这个规则pattern的binding 是派生的，并且是一个接一个迭代的</li><li>对每一个binding， 用规则来创建一个新的表达式， 对function rule， 对每一个binding 可能有多个新的表达式</li><li>集成新表达式到memo 结构中， 这里可以去重表达式。</li><li>对每个表达式用同样的目标和当前规则应用的context 进行explore 或优化。</li></ol><p>因每个规则的前置pattern （antecedent）可能很复杂， cascade 使用一个复杂的过程来确定每个可能的binding。这个过程是递归的，对pattern的每个node 进行递归调用。 这个过程使用迭代器来实现的， 每一次调用都产生下一次可行的binding。 迭代的状态捕获在pattern内每个节点的BINDING 类的实例中。 一旦发现一个binding， 转化为有EXPR node 组成的树。 这一步copy的动作需要做一些努力，但他将优化器和这颗树要用的DBI method 分开。 对每个binding， 调用rule的condition function， binding就会转化为规则的结果（after-pattern， substitute）。 有一些规则，binding 非常简单并且完全由优化器来做。 对于一些规则来说， DBI 确定一个函数来创建substitue， 会重复调用这个函数尽可能创建多的substitute。 这样这个函数就是一个迭代器，被连续调用创建多个substitute。 因此， 从一个memo中提取binding 是需要评估的。<br>每个substitute（binding后续）表达式会集成到memo中， 这个过程包含search和去重。 从substitue的叶子节点（query&#x2F;plan tree&#x2F;表示rewriter operation 范围的operator 的叶子节点）开始递归， 从低向上至root。<br>最后， 如果substitute的root 是一个新的表达式， 随后的task就会启动， 如果这个substitute 是由exploration 的部分创建的， 就会创建一个task来expolre 这个相同pattern的substitute。 如果这个substitute 是优化的部分创建的， 随后启动的task就会依赖是一个转化rule还是一个实现rule，例如， 这个substitute 的root 算子是一个逻辑算子还是物理算子， 一个rule 可以同时是transformation rule和implementation rule， 这种情况下， 2种随后的task都会被创建。 对于一个逻辑root 算子， 一个optimization task 会被创建用来优化substitute。 对于一个物理root 算子， 会调度一个task来优化算子的input并计算处理代价。 优化输入（optimize inputs） task是不同于其他task。 其他task 调度他随后的task然后就小时， 这种task会激活多次。 也就是， 他调度随后的task， 然后等待随后task结束， 继续并调度下一个随后task等等。 随后的task是相同类型， 他们优化input group 。 cascade search engine 保证仅仅那些subtree和有兴趣的属性会被优化， 优化input task 获得派生的最好执行cost， 然后派生一个新的cost limit 给下一个优化input， 因此pruning 是尽可能的多。 </p><h2 id="数据抽象和用户接口"><a href="#数据抽象和用户接口" class="headerlink" title="数据抽象和用户接口"></a>数据抽象和用户接口</h2><p>目标：</p><ol><li>DBI 和优化器 之间的接口 会专注在minimal， 功能性和干净的抽象</li><li>实现一个优化器原型尽可能高效的使用接口</li><li>在exodus和volcano的教训的基础上 设计和实现一个高效search 策略， 结合学术和工业界查询优化的研究。<br>专注在：</li><li>support function 干净的抽象， 保证优化器生成器能根据specification 创建他们</li><li>rule机制允许DBI 选择rule或function 来控制算子参数</li><li>更准确和完整接口specification。</li></ol><p>在cascade 优化器和DBI 之间的接口的类 被设计为子类的root。 这些类的object 会关联上其他的类。 例如， 创建一个guidance 的结构会关联到一个rule 对象。 这个rule 对象是接口类RULE的子类。 这个创建的guidance 是接口类GUIDANCE的子类。  优化器仅仅依赖接口定义的函数。 DBI 在定义子类时，可以添加额外的函数。 </p><h3 id="算子和参数"><a href="#算子和参数" class="headerlink" title="算子和参数"></a>算子和参数</h3><p>cascade 优化器接口的OP-ARG类包括逻辑和物理算子。 对每个算子， “is-logical” 现实算子是否是逻辑算子， “is-physical” 显示是否是物理算子。 但有可能一个算子既不是逻辑算子也不是物理算子。 当优化器支持一些扩展语法时，这些算子就发挥作用， 想starburst 支持“non-terminal”。 DBI 可以通过子类来获得严格的逻辑算子和物理算子的隔离。<br>算子的定义包括他们的参数。 在exodus和volcano中，没有机制来做“argument transfer”。 有2个关键工具用于帮助建模谓词（在exodus和volcano框架中 建模为算子参数）为逻辑和物理关系代数的主要算子。 首先一个算子既可以是逻辑的，又可以是物理的， 它天然是一个single-record的谓词，并成为sargable（Search Argumentable）in system R。 第二， 指定的谓词转换例如可以在join中push 的复杂谓词组件抽取出来的谓词， 它可以在DBI function内更容易并高效实现而不是由search engine解释的一个rule， 也可以简单实现为调用DBI-supplied的rule 来map 一个表达式为一个或多个substitute 表达式（后pattern 表达式）。 exodus和volcano 经常被吐槽谓词控制非常麻烦， cascade 可以提供很多方便工具。<br>优化器的设计不包括逻辑和物理关系代数优化。 因此没有query或plan的算子包括到优化器中。 为了使用rule， 有2个特殊的算子， “LEAF-OP”和“TREE-OP”。 叶子算子在任何规则中都是作为叶子被使用， 在matching过程中，它可以匹配任何子树。 在应用一个规则， 会从匹配这个rule的pattern的search memory中提取一个表达式， 如果这个rule的pattern 有叶子， 这个提取的表达式也有叶子算子， 这个叶子算子参考search memory中等价类。 树算子类似叶子算子除了这个提取的表达式包括一个完整的表达式， 和它的大小和复杂度无关， 会down到逻辑关系代数的叶子算子。 这个算子在连接function rule时，特别有用。<br>除了提供“is-physical”&#x2F;“is-logical” 函数， 所有算子必须提供“OPT-CUTOFF” 函数。 在一个优化任务中，赋予一组move，这个函数决定有多少move 会被尝试， 尤其是哪些最可能被使用的。 默认， 所有可能的move 都会被尝试， 因为exhaustive的search 保证会找到优化plan。 有一小组函数用于那些被申明为逻辑的算子。 对于pattern 匹配和查找重复表达式， 会要求matching和hashing的函数。 用于查找并提高逻辑属性的函数用于决定一原始组的属性和当可选的表达式被找到后， 改进它。 最后， 对于exploration task， 有可能调用一个算子来初始化pattern memory 并决定在exploration过程中，尝试多少moves。</p><p>同样的， 有一些函数是给物理算子的。 明显的， 有个函数来决定算子的输出属性（展现的属性）。 更进一步， 有3个函数用于计算和检查cost。 第一个函数计算一个算法的local cost，不考虑输入的cost。 第二个函数 组合这个cost（应该是上一步的cost）， 算法输入的物理属性到完整subplan的cost。 第三个函数verify， cost 没有超标， 在优化一个算法的2个输入下， 并计算一个新的cost limit用于优化下一个输入。 最后， 就像最后一个函数那样， 它map 一个表达式的cost limit 到他其中一个输入的cost limit， 同样有一个函数， map 一个表达式的目标到 其中他一个输入的优化目标， 例如， 一个cost limit 和要求的和排除的 物理属性。 </p><h2 id="逻辑和物理属性，-cost"><a href="#逻辑和物理属性，-cost" class="headerlink" title="逻辑和物理属性， cost"></a>逻辑和物理属性， cost</h2><p>期待的执行cost的接口， COST 类， 非常简单， cost的实例 由 和其他类（如算子）关联的函数创建并返回。 除了destruction和printing， cost的唯一函数就是比较函数。 类似， 封装逻辑属性的唯一函数就是类 SYNTH-LOG-PROP， 它是一个hash 函数， 他可以更快检索重复的表达式即使这个函数还没有应用物理表达式。 封装物理属性的类是 SYNTH-PHYS-PROP， 却没有函数。 要求的物理属性类是REQD-PHYS-PROP， 只有一个函数，并且它决定一个合成的物理属性实例是否cover 这个要求的物理属性。如果一组属性比另外一组属性更明确例如 一个在A，B，C 上排序， 而另外一个只要求sort order在A，B。 这个比较函数就会返回MORE。 默认的返回是undefined。 </p><h2 id="表达式树"><a href="#表达式树" class="headerlink" title="表达式树"></a>表达式树</h2><p>另外一个抽象数据类型是部分接口 – EXPR 类。 这个类的实例是树上的一个节点， 由一个算子和指向input node的指针组成。任何一个表达式节点的子树数量等于这个node 算子的参数数量。一个表达式节点的函数，除了构造函数，析构函数，打印函数之外， 包括提取算子的函数或者有一个输入是匹配函数的函数， 递归遍历2个表达式树并调用每个节点算子的匹配函数。</p><h2 id="搜索指导规则"><a href="#搜索指导规则" class="headerlink" title="搜索指导规则"></a>搜索指导规则</h2><p>除了pattern， cost limit， 要求和排除的物理属性， 启发式规则可以控制规则应用， 启发式规则是GUIDANCE 类的实例来表示。 他用于转化优化启发式规则 从一个rule 应用到另外一个。 注意当执行一个plan时，会产生 正在操作的表达式的cost和属性和 中间结果的cost和属性； guidance 类会捕获搜索过程的只是和未来搜索用的启发式规则， 例如一些如交换率的规则就只被使用一次， 被叫做“once-guidance”和“once-rule”。<br>有一些研究者将优化规则分成模块， 一次只调用一个模块， 如MITCHELL。 GUIDANCE 可以很容易使用这个设计， 一个guidance结构可以显示哪个模块会被调用， 每个规则检查在它promise或condition 函数中的标识， 然后当为一个新建表达式和它input创建guidance结构时，会创建合适的标识。</p><h2 id="Pattern-memory"><a href="#Pattern-memory" class="headerlink" title="Pattern memory"></a>Pattern memory</h2><p>除了search guidance之外， pattern memory会限制探索。 pattern memory是防止一个group的无用探索，如2个相同patterm。 每个group 都会有一个pattern memory。 在针对一个pattern探索一个group前， 允许增加这个pattern到pattern memory并决定探索是否应该使用。 在大部分简单search中， 任何pattern的exploration都是穷举transformation 规则应用， pattern memory 需要包含一个boolean， 表示这个group是否之前被exlored， 更复杂的pattern memory 会store 每个pattern。</p><p>明显， pattern memory会和expoloration promise 函数交互。 对于大部分简单的promise 函数（使用穷举），上面简单的pattern memory是足够的。 由DBI 来设计pattern memory和promise 函数， 使用要被优化的关系代数。 </p><p>除了检查一个pattern是否已经存在与pattern memory中， pattern memory最复杂的函数是merge 2个pattern mmory。 当发现2组等价的表达式（例如一个转化后的表达式在不同group中出现）时， 会执行这个函数。 </p><h2 id="rules"><a href="#rules" class="headerlink" title="rules"></a>rules</h2><p>除了算子， 最重要的类就是rule。 注意rule是对象， 可以在run-time 创建并打印。 其他rule基础的优化器，如volcano 有逻辑算子和物理算子一样有transformation rule和implementation rule。 cascade 不区分这2中规则， 除了在新建的表达式中调用is-logical和is-physical 函数。 所有的rule 都是类RULE的实例， 他提供rule name， 一个祖先（before-pattern）和一个结果（substitute， after-pattern）。 pattern和substitute 都是用表达式树来表示。<br>任何时候 发现一个pattern或由exploration task 创建一个pattern， 在search memory中都会包括这个substitute 表达式。 rule的pattern和substitute 都可以是任意复杂。在volcano中， 一个implementation的rule的substitute 不能包含超过一个implementation 算子。 在cascade中，去掉了这个限制。 但还有一个限制就是substitute的top 算子必须是逻辑算子。 例如， 可以tranform 一个逻辑join 算子为物理的nested loop 算子（这个算子带一个在它自己input上的selection）， 因此， 可以将这个谓词从join上下推到input tree上。</p><p>对于一些复杂rule， 可以支持2中类型的condition function。 他们不仅考虑rule并且当前优化目标（cost limit， 要求和排除的物理属性）。在exploration 开始， promise 函数通知优化器 这个规则有多有用。 optimzation task 有一个promise 函数， expoloration task 也有一个。对于unguided 穷举search， 所有的promise 函数都会返回1.0. 0或更少表示优化器不会用他。 如果要求一个明确的物理属性， 默认promise 函数返回0, 如果substitute是一个实现算法，返回2. 其他就返回1. 如果关联这个算子的cutoff 函数选择穷举search， promise function的返回值不会改变最终plan的质量， 尽管他会影响发现plan的顺序， pruning的有效性，以及耗费的优化时间。<br>因为promise 函数是在exploration subgroup之前被调用的，例如从search memory中explored和提取出对应一个规则的完整表达式树， 在exploration 执行完和可以获得一组完整的算子（对应rule内pattern）之后 condition 函数坚持这个规则是否可用。 promise 函数返回一个表达promise 级别的值， condition 返回一个boolean表示这个规则是否可用。<br>除了promise和condition 函数， rule 有一些函数，除了构造函数，析构函数，打印函数外，还有提取pattern的函数， substitue，rule name， 和他的参数个数（pattern的叶子节点个数）。 “rule-type”函数展示一个规则是一个简单规则还是一个function fule。 top-match 函数决定search memory的一个算子是否match rule pattern的top 算子， 这个函数是built-in 检查， 在promise 函数调用前。  opt-cases 函数现实一个物理表达式被不同物理属性优化的次数。 有一些特殊的case， 它会返回1, 例如在merge-join 算法中， 有2个clause （R.a&#x3D;s.a and r.b&#x3D;s.b）会被优化为2个sort orders（“A，B” 和“B，A”）。 当优化一个新创建的表达式和他的输入时， 新建的guidance结构会用到剩下的函数。各有2个函数用于优化（optimization）和用于探索（exploration）， 各有2个函数用于新表达式和它的输入。 “opt-guidance”,<br>“expl-guidance”, “input-opt-guidance”, “input-expl-guidance”. 默认他们读返回null。<br>如果一个rule的substitute 仅仅包含一个叶子算子， 这个规则是一个reduction 规则。 如果使用reduction 规则， search memory的2个group会做merge。 如果一个规则的pattern 仅仅包含一个叶子算子， 这个规则是一个一直可以被使用的膨胀规则。 cascade 优化器以来DBI 来设计合适的promise和condition 函数来避免无用的transformation。 有一个重要的类， 插入可以enforce或保证期望的物理属性的物理算子， 这个规则叫enforcer 规则。 例如sort-merge的输入必须是sorted。 一个enforcer 规则插入sort 算子， 这个规则的promise和condition function 允许这个要求sort order的规则， sort 算子的“input-reqd-prop”函数必须设置排他属性以避免 产生sort oder和input sort一样的的plan的考虑。 An enforcer rule may insert a sort operation, the rule’s promise and condition functions must permit this rule only of sort order is required, and the sort operator’s ”input-reqd-prop” method must set excluded properties to avoid consideration of plans that produce their output in the desired sort order as input to the sort operator.</p><p>在一些情况下， 更容易实现一个函数直接转换一个表达式，比设计并控制一个rule 来做这个转换。 例如， 切分复杂join 谓词为clause， 这些clause 用于left， right，和2个input 是一个确定性的过程， 这个切分用单个函数最好实现。cascade 支持rule的第二个类 FUNCTION-RULE。 一旦从对应rule pattern提取出来的表达式， 会重复调用一个迭代器函数来创建这个表达式的substitute。 这个提取的表达式可以任意复杂度， 只要在这个rule pattern中用到这个树算子。 树算子和function fule 允许DBI 实现任何转换。 在极端场景下， 一组function rule执行所有的转换， 有可能违背cascade的设计初衷。</p>]]>
    </content>
    <id>https://ilongda.com/2018/docs/paper/cascade/</id>
    <link href="https://ilongda.com/2018/docs/paper/cascade/"/>
    <published>2018-10-26T11:42:57.000Z</published>
    <summary>Cascades 查询优化框架论文笔记：规则对象化、memo 搜索 task 调度及逻辑表达式枚举与代价优化流程，更多细节与示例见正文。</summary>
    <title>《The Cascades Framework for Query Optimization》</title>
    <updated>2026-06-09T08:46:25.961Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《The Volcano Optimizer Generator Extensibility and Efficient Search》","description":"Volcano 优化器生成器论文笔记：data model 与逻辑/物理代数、规则扩展及动态规划高效搜索的设计理念，更多细节与示例见正文。","image":"https://ilongda.com/assets/optimizer_generator.png","wordCount":4001,"datePublished":"2018-10-20T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.961Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/paper/The_Volcano_Optimizer_Generator/"},"url":"https://ilongda.com/2018/docs/paper/The_Volcano_Optimizer_Generator/","inLanguage":"zh-CN","keywords":["论文","数据库"],"articleSection":["论文","数据库"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"《The Volcano Optimizer Generator Extensibility and Efficient Search》","item":"https://ilongda.com/2018/docs/paper/The_Volcano_Optimizer_Generator/"}]}</script><h1 id="《The-Volcano-Optimizer-Generator-Extensibility-and-Efficient-Search》"><a href="#《The-Volcano-Optimizer-Generator-Extensibility-and-Efficient-Search》" class="headerlink" title="《The Volcano Optimizer Generator: Extensibility and Efficient Search》"></a>《The Volcano Optimizer Generator: Extensibility and Efficient Search》</h1><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>这篇论文还是一篇基本性的描述， 主要介绍优化器的很多理念，如果已经看过一些基本的优化器介绍， 这篇论文可以skip。</p><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>数据库除了需要满足新功能还要高性能， volcano 提供高效和易扩展的工具来满足查询请求。 优化器生成器将data model， logic algebra， physical algebra 和优化规则 转化为优化器源码。 和前一代优化器生成器exodus 相比， 查询引擎更易扩展和更powerful。 他提供对non-trivial cost model 和物理属性例如sort order的高效支持。 与此同时， 高效结合了动态编程。 和其他rule based 优化系统相比， 它提供独立完整的data model和更自然的扩展性</p><h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p>不能因为扩展性而失去性能， 因为， 现在数据量的增速远超过数据库能力的增速， 另外一个原因，现在在跑的科学计算基于文件系统已经在运行了， 否则没有动力进行迁移。 优化和并行 是主要提供性能优势的方式。 <br />有几个要求:</p><ol><li>optimizer generator 必须是一个独立工具, 既可以在volcano 中使用, 又可以在其他项目中使用</li><li>新系统必须更高效, 优化时间必须段, 并且内存消耗上也必须足够小</li><li>易扩展, 可以高效添加类似sort order 和压缩status 的物理属性</li><li>允许使用heuristics 和data model semantics 来指导搜索和对一些查询空间进行裁剪</li><li>支持灵活的cost model， 允许为非完整的查询生成动态plan</li></ol><h1 id="整体视图"><a href="#整体视图" class="headerlink" title="整体视图"></a>整体视图</h1><p><img data-src="/assets/optimizer_generator.png" alt="image.png"><br /><br>将data model 描述 生成为编译器代码, 和其他数据库模块 如查询引擎, 搜索引擎编译和链接在一起,生成数据库. </p><h2 id="设计原则"><a href="#设计原则" class="headerlink" title="设计原则"></a>设计原则</h2><p>5个基本原则</p><ol><li>关系系统的查询是基于关系代数算子， 通過代數算子， 代數等價規則和合适的实现算法。可以使用关系代数规则,比如各种等价变化, 如交换律,结合律.  算子消费一个或多个types 并生成出下一个operator的输入。 执行引擎基于算子， 算法消费和生成bulk types。 算子集和算法集是不同的。 选择最优的算法是优化器最重要的工作之一。 优化器生成器使用2种关系代数， 一种是逻辑，一种是物理。 通过逻辑关系代数的transformation和 基于代价的关系代数到算法的关系映射, 生成的优化器可以映射逻辑代数到物理逻辑代数算子(包含算法)的expression。 </li><li>通过规则来提高优化器的扩展性和模块化.  rule   用一种 精简和模块化的方式来指定pattern 的knowledge 和代数原则的knowledge（比如等价转化用patterns和rules 来表达）。 专注在独立的rule上可以提高模块化， rule 可以独立从一个rule 转化为另外一个rule， 并且和search engine combine 在一起。 </li><li>volcano generator的输入的代数等价是优化器做的各种选择（映射一个query到一个优化的等价plan），search engine 用一个合适的方式来使用这些关系代数等价转化公式， 对于那些优化器实现者来说， 这些优化器想做一些特殊控制，比如确定某些搜索或做剪枝搜索等， 有一些优化工具来做这些事情。 </li><li>为了获得更好的性能， 选择了对rule 进行编译来执行，没有选择解释执行，类似EXODUS 优化器生成器， 但为了提高扩展性， 使用最强参数，强参数使用解释性 pointless， 提高rule sets的扩展性， 参数化rule和他们的condition, 例如， 控制search的全面性和观察和exploit rule 应用的重复顺序。</li><li>基于动态编程</li></ol><h2 id="优化器生成器的输入和优化操作"><a href="#优化器生成器的输入和优化操作" class="headerlink" title="优化器生成器的输入和优化操作"></a>优化器生成器的输入和优化操作</h2><p>优化器生成器的目标就是最小化 要实现的data model的假设，操作和功能的 data modle。 这章介绍优化器的组件。优化器的输入是query， 输出是优化的执行plan。</p><ol><li>parser 解析query 后，生成logic 算子组成的关系表达式（逻辑算子是在生成优化器时 model specification 定义和编译进去的， 算子可以0个或多个input）， 这个作为优化器的输入，优化器输出是一个执行plan， 这个plan 是带有关系代数算法。 算法集， capability和代价 代表着data format 和物理存储结构。 </li><li>优化过程是 将一个逻辑关系代数表达式 映射为一个优化的，等价的，物理关系代数表达式。  对算子进行reorder， 对实现算法进行挑选。 transformation rule 包含等价转化， 交换律， 结合律。 用implementation rule 来确定映射算子到算法。 rule language 必须支持复杂的mapping， 可以join后带projection 用一个rule来表示， 可以一个rule 来表达映射多个逻辑operator到一个物理operator。 当pattern 匹配到算子和算法后，还需要确定所有rule的condition。  在一个rule pattern成功匹配后， 可以attache condition code 到rule上。 </li><li>用属性来描述expression的result。 逻辑属性来自逻辑关系代数表达式， 包括schema， expected size等。 物理属性来自算法， 如sort order， parition等。 当优化一个many-sorted 代数， 逻辑属性包含中间结果的type， 可以用一个rule的condition来检查type 来保证正确的type的rule可以用到这个expression上。逻辑属性可以attach到等价类， 一组等价的逻辑express 和plan， 而物理属性只能attach 一个plan和算法。</li><li>物理属性集合对每个中间结果summarized到 物理属性vector， 由优化器的实现者来定义并被volcano 优化器生成器和search engine当作一个抽象data type。 也就是物理属性的types和semantics 可以由优化器实现者来设计。</li><li>有一些物理代数不存在任何逻辑代数中， 比如sorting和decompression。 这种算子不执行逻辑数据处理但对他们的输出附加物理属性， 这些属性用于随后的query 处理算法。 我们称这种算子为enforcer， 类似starburst的glue。 enforcer 可以ensure 2个属性，甚至enforce 一个属性但destory 另外一个。 有2中enforcer sort&#x2F;hash, 在并行和分布式系统中，location&#x2F;paritioning 会被enforce 网络或并行算子如exchange 算子中。 在面向对象系统中， 对复杂对象进行assembledness， 使用assembly 算子作为enforcer。</li><li>每一个优化目标是一个pair（逻辑express 和物理属性vector）。 为了判断是否可以在这个logic expression的root node上执行一个算法或一个enforcer， 优化器寻找合适的implementaion rule， 执行rule上关联的condition， 最后调用 applicability function， applicability function 来决定这个算法或enforce是否可以deliver 这个logic expression带物理属性， 这个物理属性满足物理属性vector， applicability function同样决定了算法输入必须满足的物理属性vector。 举例来说， 当优化一个join expression时， 它的结果应该sorted， hybrid hash join 不满足这个要求， 但merge-join 就可以满足，  因此当输入不排序时，一个enforce就会传过去， 输入排好序后， hybrid hash join 也可以。算法不能过度限制。</li><li>在优化器决定用一个算法或enforcer后， 他调用算法的cost function来估算他的代价。 cost 是优化器生成器的抽象代价类型。 优化器实现者可以选择cost to be a number（以耗时为标准）， 以record（以cpu time&#x2F;io count）或其他类型。 代价的算术计算和比较通过关联抽象类型cost的函数来执行。</li><li>对于每个逻辑和物理的代数表达式， 逻辑和物理属性来自使用property functions。 每个逻辑算子， 算法和enforcer 都有一个property function。在优化前， 由关联逻辑算子的property function 来决定逻辑属性基于逻辑表达式。 例如， 决定中间结果的schema 可以独立于创建任意一个等价的代数表达式。 逻辑property function 同样封装selectivity 评估。相反， 像sort order 这样的物理属性仅仅在执行计划选择后才能决定。 因为一致性检查之一， 优化器确定 一个选择的plan的物理属性满足优化目标的物理属性vector。</li></ol><h1 id="search-engine"><a href="#search-engine" class="headerlink" title="search engine"></a>search engine</h1><p>优化器就是从相同的的执行计划中选自一个代价最小的执行plan， search engine和他的算法就是最核心的组件。 volocan 优化器生成器提供一个现成的sarch engine， search engine 可以自动link  匹配的pattern 和由data model 描述生成的rule application code。<br>在exodus 优化器中浪费了很多search effor， volcano 优化器生成器使用动态编程， 并且目标驱动和需求驱动。<br>动态编程在system r 和starburst 代价优化器等数据库中已经被使用过了。volcation 优化器生成器的search strategy 扩展了动态编程， 从关系join 优化 到通用代数查询和优化器请求， 自上而下， 目标驱动 的代数控制策略来处理 可能的plan 超过预计算的实际限制。那些部分query被认为是更大的subquery的部分， 这些部分query的等价expression和plan的动态编程。不是所有的等价expression和plan， 这些expression和plan 看起来可行在他们的sort order上。 因此subqueries的exploration和优化， 和他们的可选plan是非常直接和目标驱动的。exods&#x2F;system r&#x2F;starburst  关系系统都使用forward chaining， volcano 使用backward chaining， 因为，他仅仅探索那些真正在一个更大expression使用的subqueries和plan。 我们称这种算法为 directed dynamic programming. volocana 优化器的动态编程通过获得部分优化结果的大集合， 并且在后续优化决定中使用之前的优化结果。当前，当每个查询开始优化时，部分优化结果的集合会被重新初始化。 也就是 早期的部分优化结果只有在优化单查询时被使用。 </p><p>代数转换系统总是包含相同表达式的各种可能。 为了减少无效的优化， 通过探测来自相同逻辑表达式和plan的多余优化， 捕获expression 和plan 在表达式和他的等价类目中的hash table。 一个等价类含2个集合， 一个等价逻辑表达式， 一个等价物理表达式（plan）。 逻辑代数表达式  搜索空间的高效和完整探索使用逻辑代数表达式， 满足物理属性要求的一个合适input plan的一个快速选择<br>使用plan。 一个等价类的已经优化的物理属性可以被combination ， 比如没有sorted， 在a上sorted， 在b上sort， 会保留最好的plan。<br><img data-src="/assets/volcano_search_engine.png" alt="SEARCH_ENGINE"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line">FindBestPlan(LogExpr, PhysProp, Limit) &#123;</span><br><span class="line">    if  find LogExpr/PhyProp in HashTable &#123;</span><br><span class="line">        if  cost &lt; limit &#123;</span><br><span class="line">            return plan/cost</span><br><span class="line">        &#125;else &#123;</span><br><span class="line">            return failure</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;else  &#123;</span><br><span class="line">        //优化过程</span><br><span class="line">        创建3种 可能的move &#123;</span><br><span class="line">            applicable transformations</span><br><span class="line">            physprop 要求的算法</span><br><span class="line">            physprop 要求的enforcer， 有时可以做exclude 物理属性vector</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        根据promise 对move 进行排序</span><br><span class="line">        for 对于最可能promising的moves &#123; //使用所有的move或根据规则选择部分</span><br><span class="line">            if 如果这个move 使用transformation &#123;</span><br><span class="line">                使用这个transformation， 生成新的NewLogExpr；// 这个地方会标记已经使用的transform，防止重复transform</span><br><span class="line">                调用FindBestPlan(NewLogExpr, PhysProp, Limit)</span><br><span class="line">            &#125;else if (move 使用算法) &#123;</span><br><span class="line">                //某个算法可以deliver 这个逻辑表达带对应的物理属性</span><br><span class="line">                Totalcost ：= 算法的cost</span><br><span class="line">                for each input I while Totalcost &lt; Limit &#123;</span><br><span class="line">                    生成physical properties PP for I</span><br><span class="line">                    cost = FindBestPlan(I, PP, Limit - TotalCost)</span><br><span class="line">                    TotalCost += cost</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;else &#123; // enforcer</span><br><span class="line">                TotalCost := cost of enforcer</span><br><span class="line">                根据enforcer property 调整PhysPop</span><br><span class="line">                调用 FindBestPlan(LogExpr, NewPhysProp, Limit)</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        if LogExpr 不在look up table</span><br><span class="line">            insert LogExpr  Look-up-table</span><br><span class="line">        </span><br><span class="line">        insert PhyProp/BestPlan into Look-up-table</span><br><span class="line">        return BestPlan and cost</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>starburst 使用2阶段，增加了query rewrite 阶段。 而volcano 留给了优化器实现者。<br>cost 限制用于减少search 范围。 一旦一个逻辑expression的完整的plan和物理属性vector， 没有更高代价的其他plan或者部分plan ， 这个完整plan可以作为优化plan。 快速找到一个相对好的plan是非常重要，即使exhaustive 扫描。 更进一步， cost limit 可以透传到subexpression， 控制优化的上限空间。<br>对于一些binary 算子， 输入的实际的物理属性不如input之间的一致性物理属性重要。 对于intersection的sort-based 实现， 类似merge-join的算法， 只要2个input用相同方式进行sort即可。 对于一些case， search engine 允许尝试一些物理属性vector</p><h1 id="other-related-work"><a href="#other-related-work" class="headerlink" title="other related work"></a>other related work</h1><ol><li>这里提到在starburst 如何做优化， 将优化分为2个阶段， 第一个阶段，叫rewrite phase， 基本上是基于规则的，非cost base，执行nested sql queries， union， outer join， grouping， aggregation另外一个阶段cost base 优化， 比如merge nested subqueries， bundles selection， join predicate。 cost base 优化器会执行exhaustive的search在某些条件下， 比如限制搜索在左深树。 它同样使用了动态编程， 对于类似enforcer的操作叫glue， 执行sort order或者创建高效access plan 。 <ol><li>有2个基础性问题， 1.基于类似语法的规则，step-wise expansion（逐步膨胀） 的join expression， 举例来说， 语法依赖一个中间层， 交换join&#x2F;不交换join&#x2F;规则集 都是特殊定制的。 问题是不是很清楚 现有的rule 如何与算子&#x2F;expansion rule 交互。 定义一个怎样的中间层来用于expansion grammar？当集成一个新的算子到cost based optimizer， 必须定义几个新的中间层和他们的grammar rule三。 这些新rule 可能和现有的rule 进行交互， 创建一个复杂和繁琐的task。 volocano 代数优化方式更自然和易懂。</li><li>当添加新算子到cost-based optimzer时， 新算子需要集成到query rewrite level。 但在query rewrite level 是有层级， 也就是他不包含cost estimation。 比如， 优化有一些欠缺， 考虑一个union 或intersection of N set， 实际上就是join n relation， 然而join优化需要遍历tree 和join ordering， 基于selectivity 和cost 进行评估。但 union&#x2F;intersection 仅仅用rewrite 和交换率来进行启发式优化。 当启发式规则适用于一些transformation时（如rewriting subquery到semi join）， 但这个启发式规则不适用在query rewrite level的已经存在的关系算子，不适用一个扩展查询优化系统未来的代数算子和属性是未知的。</li><li>我们相信一个单一层， 在这层中， 所有的代数等价式和transformation 可以用一个语言来表达并且由一个优化器组件来进行执行， 在未来探索数据库代数和优化时， 他可以有延续性。 </li><li>注意， volcano 优化器生成器 通过合适的ranking和moves的选择来允许 启发式transformation， 他留给优化器实现者来进行选择何时以及如何使用启发式规则和cost-sensitive 优化， 而不是像starburst 要求先验</li></ol></li><li>sciore&#x2F;sieg 批评早期的基于规则优化器， 并得出结论 模块化是优化器系统扩展性的基本要求，如 rule set 和应用的控制结构。优化的不同task，如rule 应用和selectivity 评估 应该封装在独立和合作的 experts。</li></ol>]]>
    </content>
    <id>https://ilongda.com/2018/docs/paper/The_Volcano_Optimizer_Generator/</id>
    <link href="https://ilongda.com/2018/docs/paper/The_Volcano_Optimizer_Generator/"/>
    <published>2018-10-20T11:42:57.000Z</published>
    <summary>Volcano 优化器生成器论文笔记：data model 与逻辑/物理代数、规则扩展及动态规划高效搜索的设计理念，更多细节与示例见正文。</summary>
    <title>《The Volcano Optimizer Generator Extensibility and Efficient Search》</title>
    <updated>2026-06-09T08:46:25.961Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Microsoft SQL Server PDW","description":"Microsoft SQL Server PDW 论文笔记：share-nothing MPP 架构、DMS 数据移动与 DSQL 分布式执行计划生成","image":"https://ilongda.com/assets/ms-pdw-arch.jpg","wordCount":2879,"datePublished":"2018-10-20T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.963Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/paper/ms-sql-server-pdw/"},"url":"https://ilongda.com/2018/docs/paper/ms-sql-server-pdw/","inLanguage":"zh-CN","keywords":["论文","数据库"],"articleSection":["论文","数据库"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"Microsoft SQL Server PDW","item":"https://ilongda.com/2018/docs/paper/ms-sql-server-pdw/"}]}</script><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><p>Microsoft SQL Server PDW, 是一个基于SQL-Server基础上构建的MPP 分布式数据库， 旨在提供数据仓库解决方案。 复用了很多SQL-Server的技术， 如query 简化， 空间探索， cardinality 评估。<br>整个架构share nothing，有一点类似DRDS 的架构，优化器叫PDW QO </p><h1 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h1><p><img data-src="/assets/ms-pdw-arch.jpg" alt="image"><br>pwd 是一套软硬一体的硬件， 可以进行水平扩张（增加节点），或垂直扩展（升级cpu&#x2F;memory&#x2F;存储）<br>控制节点和计算节点都包含一个sql-server 节点， DMS – data movement service。<br>优化搜索空间（optimization search space） 称为memo。 搜索空间会配置数据分布的参数信息从而找一个并行执行计划。</p><h2 id="控制节点"><a href="#控制节点" class="headerlink" title="控制节点"></a>控制节点</h2><ol><li>一个engine service， 这个engine serice 是中央控制核心节点。</li><li>用户接口 </li><li>使用sql-server 栈，负责parsing， 权限验证，生成分布式执行计划（DSQL Plan）， 分发plan到计算节点， 跟踪执行进度， merge 计算节点的结果，返回结果给用户</li><li>元数据&#x2F;configuration data存在控制节点的sql-server, 也存储global statics（local statics 存在计算节点的sql-server）， aggregated statics of user, 用户数据的分区信息，用户的权限信息，但不存任何用户的数据</li><li>遍历搜索空间， 触发data movement， 并基于最小代价选择最终执行计划。</li><li>负责管理dms</li></ol><h2 id="计算节点"><a href="#计算节点" class="headerlink" title="计算节点"></a>计算节点</h2><ol><li>提供数据存储， 数据以分片形式或者复制形式分布在sql-server的table 上。</li><li>计算的主力节点</li></ol><h2 id="DMS"><a href="#DMS" class="headerlink" title="DMS"></a>DMS</h2><ol><li>经常需要将中间结果从一个节点传到另外一个节点。有的时候，会把计算节点的数据传到控制节点做aggregation或者传给用户前的sorting。</li><li>用临时表来移动数据或存中间结果， 不过也可以， 用户sql 写成无临时表方式，这样计算节点的结果数据会以流式方式发给客户端，中间不涉及dms</li></ol><h2 id="DSQL"><a href="#DSQL" class="headerlink" title="DSQL"></a>DSQL</h2><ol><li>sql 操作， 在计算节点上执行的sql 语句</li><li>DMS 操作， 在计算节点之间进行搬迁数据</li><li>临时表操作， setup staging table 用于后续操作</li><li>return 操作， push result to client。</li></ol><p>一个步骤一个时间（应该是一种操作一个stage， 按照stage 进行流水线）。 单独一个步骤可以并行。 </p><h3 id="example"><a href="#example" class="headerlink" title="example"></a>example</h3><p>以tpch 为例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select c_custkey, o_orderdate from orders, customer where o_custkey = c_custkey and o_totalprice &gt; 100;</span><br></pre></td></tr></table></figure><p>customer表按照c_custkey 分区， 但oder 表不是按照o_custkey分区， 因此，可能2个步骤</p><ol><li><p>dms 操作： 对order 表按照o_custkey 进行repartition。</p></li><li><p>sql 操作， 计算节点选择tuple， 并最终返回结果给客户端</p></li><li><p>dms 操作，</p><ol><li>用sql 语句提取原始数据</li><li>tuple 路由策略</li><li>目标临时表</li></ol></li></ol><p>控制节点广播dms 操作到每个计算节点， 计算节点执行sql 语句</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select o_custkey, o_orderdate from orders where o_totalprice &gt; 100</span><br></pre></td></tr></table></figure><p>每个dms 读取本地的数据， 路由对应的数据到对应的dms上， 对o_custkey 进行hash， 并且接受从其他dms接受或扫描出来的数据插入到目标临时表。 </p><ol start="2"><li>sql 操作<br>engine service 执行第二步，它会建立一个链接到每个计算节点的sql Server， 然后发送一个sql 语句，最后从每个计算节点中拉出计算结果数据</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select c_custkey, tmp.o_orderdate from orders, tmp where tmp.o_custkey = c_custkey ;</span><br></pre></td></tr></table></figure><h2 id="cost-based-查询优化"><a href="#cost-based-查询优化" class="headerlink" title="cost-based 查询优化"></a>cost-based 查询优化</h2><p>优化过程， 参考下图<br><img data-src="/assets/ms-pdw-query.png" alt="image"></p><ol><li><p>生成算子树</p></li><li><p>执行完算子树上logic exploration， 并且这个过程和单sql到sql-server过程一样。</p></li><li><p>pdw parser: 生成ast 语法树， 会做一些语法验证。 有一些query 会做一些基本的transformation。</p></li><li><p>sql server 优化： 转换后的query（这个地方有疑问是query还是ast？） 转发到shell database，sql-Server 输出的是一个相对优化的plan， 最优的串性计划不一定是最优的分布式计划，pwd 并不是用sql-server 来获得最优计划， 是自己来计算出来的。</p><ol><li>简化输入算子树为一个格式化的form， 这个作为初始计划插入到memo data structure（memo hold 所有可选计划的搜索空间）</li><li>logical transformation based on relational algebra rules.</li><li>评估每个执行计划的中间结果的大小， 根据base table 大小和列上statics</li><li>implementation 阶段， 增加物理算子&#x2F;物理算法 到search space 中。 计算cost 并做一些搜索空间的裁减</li><li>提取优化plan</li></ol></li><li><p>xml generator，  将sql server 用memo 表达的搜索空间 用xml 来表示</p></li><li><p>pdw 优化： 消费xml generator的输出。 </p><ol><li>有个parser 解析xml ，生成memo数据结构</li><li>bottom-up 优化， 基于cost base， 会在搜索空间增加data movement 策略和cost</li></ol></li></ol><p>还是以tpch 为例：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">select * from orders, customer where o_custkey = c_custkey and o_totalprice &gt; 100;</span><br></pre></td></tr></table></figure><p><img data-src="/assets/ms-pdw-op-flow.png" alt="!image"></p><p>mem 由2个组成， 一个叫groups， 另外一个叫groupExpressions,  group 表示一组等价的算子树，他们产生相同的输出。 为了减少内存使用量， 并不会对group 内所有的算子树进行评估。 groupExpress 是一个算子， 这个算子包含其他groups 作为孩子。如上图c 部分，逻辑算子是灰色， 物理算子是白色。 group 1， 有物理的table scan （可以通过pk或heap），sorted index scan （通过2级索引）。 group 4 包含所有的c 自然连接o 的等价表达式。 groupExpression 4.1 (join(1.3)) 表示所有的算子树， 树根是join， 第一个child 是group1， 第二个child是group3. 物理groupExpressions的孩子指向最高效的groupExpression. groupExpression 4.6 表示一个hash 算子， 他的左子树 是第3个groupExpression 在group 1 中， 右子树是第三个groupExpression 在group 2.  memo 提供算子树的去重， cost 管理和其他的底层支持。<br>sql server 生成串性的memo后， 它会并行化参数， pdw 优化器会加入data movment group和操作到memo 基于数据的分布。 例如group 5 表示group 1 的输出 data movement， 假设c 表和o 表是不兼容的， 优化器会考虑这个算子来使c表和o 表 分区兼容 来执行 C 自然连接 o. group 6 表示group 2 这边的输出。 类似关系算子， logical data movment 操作有许多物理实现， 如shuffle， replication 等。 由物理算子组成的最终执行plan 从memo中提取出来。 这个eplan 转化为可执行的dsql plan。<br>为什么最佳的串性计划不是最好的？ sql-server 优化器不清楚数据的分布， pdw 有额外的插入task 和data movement costing 来得到一个正确和高效的并行计划。 logical search space 基本上大家共同。 因此， 从sql server 导出logical search space 到pdw 基于pdw 的 目标。例如，在单机tpch中， join order 可能就是customer, order, lineitem 根据table size 来判断， 但pdw 中却是 order， lineitem， customer。 主要原因是order 和lineitem的co-location， 然后再对结果进行shuffle on custkey。</p><h1 id="pdw-qo-优化器实现"><a href="#pdw-qo-优化器实现" class="headerlink" title="pdw qo 优化器实现"></a>pdw qo 优化器实现</h1><h2 id="基于sql-server的修改"><a href="#基于sql-server的修改" class="headerlink" title="基于sql-server的修改"></a>基于sql-server的修改</h2><ol><li>导出优化器search space。 加了个功能 类似“showplan xml”（已经在sql-server）。 这个入口点会trigger 任何pdw特定逻辑的使用， 它会输出xml，表示memo 结构。</li><li>扩展查询surface 以支持pdw。 pdw 是完全和sql-server 兼容。 这个扩展限制为 方便的query hint 以支持特定的分布式执行策略。</li><li>扩展优化器搜索空间， 包括一些选择，例如尤其是join&#x2F;union的搭配的分布式执行。 transformation based 的架构让sql-server 不需要大的改动就可以支持这些扩展。</li><li>对于超大规模的搜索空间， 有超时限制，不会生成所有的计划。 会用一些执行计划seed memo，  这些执行计划含有分布式信息的table和操作配置</li></ol><h2 id="plan-enumeration"><a href="#plan-enumeration" class="headerlink" title="plan enumeration"></a>plan enumeration</h2><p>最原始的enumeration 可能不成功但对query 很简单。  一个bottom-up 搜索策略， 一个top-down enumeration 技术<br><img data-src="/assets/ms-pdw-op.png" alt="image"></p><ol><li>从最小的expression 开始优化， 不断迭代到整个query</li><li>特别留意 影响生成优化plan的物理属性。</li><li>interesting 属性 表示 interdsting oders 的概念扩展， 来自system r。 特别的， pdw 考虑如下的colum 为interesting 在data movement： a。 join 列 ； b， group-by 列。 join 列是因为他们让local 或directed join 变的可能， group by 是因为可以在每个node 上进行本地aggregation并对结果进行union。</li></ol><h2 id="cost-model"><a href="#cost-model" class="headerlink" title="cost model"></a>cost model</h2><ol><li>costing data movement 是一个cost 子集。 相对关系运算， data movement的研发，测试和调试要更简单一些。</li><li>data movement 有可能因为 物化数据到临时表中，从而在查询时间上占大头</li><li>不能依赖sql server 优化器来做这些事情， 它没有对应的操作。</li></ol><h3 id="cost-model-假设"><a href="#cost-model-假设" class="headerlink" title="cost model 假设"></a>cost model 假设</h3><p>从头构建cost model  太挑战了， 当前cost dms 操作根据response 时间。</p><ol><li>假设 dsql 步骤 的连续执行 （应该是pipeline 执行方式）</li><li>不是采用生产者-消费者 模式， 而是采用物化表的方式</li><li>query 和query 之间独立</li><li>机器是同构的</li><li>跨节点统一的数据分布。</li></ol><h3 id="dms-操作类型"><a href="#dms-操作类型" class="headerlink" title="dms 操作类型"></a>dms 操作类型</h3><ol><li>shufle move （many-to-many）. </li><li>partition move (many to one). </li><li>control node move (from control to compute node, tale in control node broadcast to all compute node)</li><li>broadcast move.</li><li>trim move.  （初始在每个节点的复制table上， 目标是一个分布式table 在他自己的node， 对数据进行hash到目标table）</li><li>replicated broadcast. tale in only one compute node  broadcast to serveral nodes.</li><li>remote copy to single node。 a remote copy of a replicated table&#x2F;distributed table.</li></ol><h3 id="cost-of-dms-operator"><a href="#cost-of-dms-operator" class="headerlink" title="cost of dms operator"></a>cost of dms operator</h3><p><img data-src="/assets/ms-pdw-dms-cost.png" alt="image"><br>source component 是发送端， 分成2个cost 子组件。<br>creater， 从sql server 中读取tuple， 打包到buffer<br>cnetwork， 将数据从buffer 发送， 这个操作是异步<br>Csource &#x3D; max(Creater, Cnetwork).<br>target component负责接受数据， 也分2个部分<br>Cwriter: 从buffer 中解压包，并准备插入到临时表<br>CsqlBlkCpy: bulk 插入， 这个操作是异步<br>Ctarget &#x3D; max(Cwriter, CsqlBlkCpy)</p><p>Cdms &#x3D; max(Csource, Ctarget)</p><h3 id="costing-of-一个单独模块"><a href="#costing-of-一个单独模块" class="headerlink" title="costing of 一个单独模块"></a>costing of 一个单独模块</h3><p>理论上， 模型越复杂， 评估越准确。 但实际上， 评估模型越复杂， 在data或statics 上轻微改变会越敏感。也导致了，cost model 越来越难debug和维护。 当前版本对每个组件的cost modle 基于处理的原始数据量： Cx &#x3D; B * x, B 是原始字节数， x是每个字节的cost。<br>x 是个常量， 是通过目标性能测试计算出来。 称计算过程为cost calibration。 cost calibration 的结果显示 x 依赖row的数量， column 数量和column 类型。 观察的区别不足够明显来提高cost model 的复杂性。 因此x 考虑为常量而不管这些参数。 每个cost component 都有他自己的常量值x。 Creater 有2个常量， 一个是Xhash, 一个是Xdirect, 复杂hash 的overhead，<br>B 依赖input和output stream的分布式属性。 let Y 指示全局cardinality， w  row的宽度 （memo 中的statictis 提供这2个值）。 let n 表示计算的node 数。 （Y * W&#x2F;N） 对于分布式data stream， （Y * w） 对于复制stream</p><h2 id="dsql-生成"><a href="#dsql-生成" class="headerlink" title="dsql 生成"></a>dsql 生成</h2><p>一旦pdw 优化器选择了一个查询执行计划， 它会被翻译称dsql format， 这样可以在实际节点中运行。 不像其他mpp, 如greenplum 一样发送算子树到每个计算节点。 pdw 发送一个sql 语句到每个计算节点。 这个语句在每个计算节点的dbms 上运行。 执行dsql 生成 表示将算子树翻译成sql。 我们使用QRel 编程框架， 它封装了mapping 关系树到查询语句。 如下图所示。<br><img data-src="/assets/ms-pdw-dsql-gen.png" alt="image"><br>先将物理算子树翻译为RelOp 树， 乐死sql server 输出代数算子树。 然后翻译为PIMOD ast 树通过QRel library. 最后为t-sql. </p>]]>
    </content>
    <id>https://ilongda.com/2018/docs/paper/ms-sql-server-pdw/</id>
    <link href="https://ilongda.com/2018/docs/paper/ms-sql-server-pdw/"/>
    <published>2018-10-20T11:42:57.000Z</published>
    <summary>Microsoft SQL Server PDW 论文笔记：share-nothing MPP 架构、DMS 数据移动与 DSQL 分布式执行计划生成</summary>
    <title>Microsoft SQL Server PDW</title>
    <updated>2026-06-09T08:46:25.963Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="数据库" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《Volcano-An Extensible and Parallel Query Evaluation System》","description":"Volcano 可扩展并行查询系统论文笔记：迭代器接口、exchange/choose-plan 元算子与 support function 扩展机制","image":"https://ilongda.com/assets/volcano_module.png","wordCount":10438,"datePublished":"2018-10-10T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.961Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/paper/Volcano/"},"url":"https://ilongda.com/2018/docs/paper/Volcano/","inLanguage":"zh-CN","keywords":["论文","数据库"],"articleSection":["论文","数据库"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"《Volcano-An Extensible and Parallel Query Evaluation System》","item":"https://ilongda.com/2018/docs/paper/Volcano/"}]}</script><h1 id="《Volcano-An-Extensible-and-Parallel-Query-Evaluation-System》"><a href="#《Volcano-An-Extensible-and-Parallel-Query-Evaluation-System》" class="headerlink" title="《Volcano-An Extensible and Parallel Query Evaluation System》"></a>《Volcano-An Extensible and Parallel Query Evaluation System》</h1><h1 id="Abstract"><a href="#Abstract" class="headerlink" title="Abstract"></a>Abstract</h1><ol><li>volcano 提供一种增强扩展性并且支持并行的方式。 在算子，数据类型，算法，type-specific methods， 资源申请， 并行执行， load balancing， 查询优化启发式规则 上极具扩展性。 volcano 不假设任何特定的数据model， 仅仅假设查询基于用参数化算子来进行item tranformation。 为了达到data model 独立， 将processing control 和data item的interpretation 和manipulation 独立开。 可以同时在单进程环境和多进程环境下同时工作， 单进程query evalution plan 可以在shared-memory的机器上并行化， 也可以在distributed 环境下并行化。  并行查询过程的机制是独立于算子和语义。<br>有一些原则：</li><li>volcano 提供mechanism 来支持policy。 policy 是由人经验或优化器来进行设置。 mechanism和policy的独立性可以提升数据库的扩展性。</li><li>用迭代器来实现算子， 可以做到在单进程内高效传输数据和高效控制。</li><li>迭代器的统一接口容易集成新的算子和算法。</li><li>持续打开stream 元素的解释和控制以支持任何数据模型和任何类型，形状和展现的处理item。</li><li>exchange的封装允许在单进程环境下开发查询算法依旧可以在多进程环境下工作。<br>有一些特性：</li><li>用一种接口(support function)来将关系代数算子(很容易增加代数算子)和算子实现隔离开，support function 可以轻松增加算子和算子实现，并且可以实现任意数据类型和任意实现的算子，非预定义的。 support function 以独立item（如谓词）上的操作方式被引入进来。传递一个function给被调用的算子， 这个function会设置适当的参数来确定一些item 如selection 谓词，hash function等。</li><li>volcano 提供2种创新的meta-operators,  choose-plan 算子和exchange 算子。这2个算子不是用来进行数据处理如selection， derivation， file scan， sort， join等，主要用于查询的额外控制。<ol><li>choose-plan 算子可以用来干 dynamic query evalution, 并且可以支持延迟plan 优化直到runtime。 动态plan 允许准备多个等价plan， 每个plan 在一定实际参数下的范围下最优。choose-plan 在运行时（其他算子已经明确下来）选择一个plan，</li><li>exchange meta-operator 算子支持并行操作， 可以在分区dataset 上进行算子间的并行， 也可以在算子内进行vertical 或horizontal 并行。其他算子可以不用exchange 算子就可以相互之间交换数据。其他算子都是设计为单进程处理环境， 并行由exchange 算子来实现, 其他算子都不考虑并行度， 所有并行问题如分区或data flow都封装在exchange 算子中。 因此data 操作和并行是正交的。exchange 算子可以帮助在数据处理时，数据驱动和demand 驱动相互切换。并行查询处理独立于算子和语义。这个算子在processor boundaries 之间交换数据。这种方式获得近线性性能提升。目前的exchange 算子只能工作在shared memory 环境下， 但我们正在在不破坏封装性的条件下移植到分布式环境中。</li></ol></li><li>通过将处理控制（由算子提供或继承的， 在item上的操作）和解释性或控制性的data item 隔离开， 来达到data model的独立性。</li></ol><h1 id="相关工作："><a href="#相关工作：" class="headerlink" title="相关工作："></a>相关工作：</h1><ul><li>Wisonsin Storage System 是一个record oriented file system 提供heap files， BTree&#x2F;hash index, buffer,  带谓词的scan.</li><li>GAMA 是一个分布式集群，通过token ring来连接17台VAX 11&#x2F;750， 8个cpu 各带一个local disk， 访问使用WSS， 本地磁盘只能被本地cpu 访问， 不带磁盘的cpu 可以用来干join</li><li>EXODUS 是一个扩展的database， 带一些想tool-kit 方式的组件， 优化器生成器， storage manager。最开始，EXODUS 设计为data model 独立，但后来一个powerful，结构化的并且支持很多data model的object-oriented 的data model Extra 开发出来</li></ul><h1 id="整体框架"><a href="#整体框架" class="headerlink" title="整体框架"></a>整体框架</h1><ol><li>volcano 是2层模块组成的library， 含1.5w行代码</li></ol><p><img data-src="/assets/volcano_module.png" alt="image.png"></p><ol><li>2层模块实现动态query evaluation plan和允许并行处理各种算法。 filesystem layer 和query process layer. File system 提供record, file, index 等带谓词的scan 和buffer 系统。 查询层是一套查询的module， 并且支持可以相互嵌套来组成一个复杂的查询tree。</li><li>单个record上的所有操作都是精心设计为left open。</li><li>support function 带合适的参数 传给查询operators</li></ol><h2 id="File-system"><a href="#File-system" class="headerlink" title="File system"></a>File system</h2><p>buffer manager 是最有意思的部分， 对性能至关重要， 它被设计支持海量的context和各种policy， 它包含多个buffer pools， 称为clusters的可变长度的buffering unit， 从high level传过来的replacement hint。 buffer manager 提供机制 （hint 是提供机制来支持灵活的policy的典型范例） pinning， page replacement， reading&#x2F;writing disk pages， 这些policy来自上层软件来决定， 这些policy 由 data semantics, importance, access pattern 来决定。 buffer manager 主要通过obsered reference 来决定replacement ，而不管这个行为是上层软件来决定还是本filesystem 的提前决定。</p><p>records&#x2F;clusters&#x2F;扩展 组成file。 因为file 操作是一个高频操作， 因此file module 只提供最基本的功能，但这些功能性能最佳。 一个cluster 由一个或多个page 来组成， 它（cluster）是I&#x2F;O 或buffer 的最小操作单元。 cluster size 由每个文件独立设定。 不同的file 可以有不同的cluster size。文件上的磁盘空间是连续空间，避免磁盘寻道并且更好的预读和write-behind。</p><p>RID 来标识一个record， 可以用RID 直接来access 一个record， 为了快速access 大量的record， volcano 不仅支持file&#x2F;record的独立操作， 也支持带read-next 的scan和append 操作。 file scan 有2个接口，  其中一个是file scan的标准流程， open， next， close， rewind。 next 返回下个record的内存地址， 这个地址保证pin到内存中，直到下个next 执行时。 获得同一个cluster的下个record不需要调用buffer manager（无内存申请操作），可以高效执行。 另外一套接口是在query process过程中描述。</p><p>为了快速创建文件， scan 支持append 操作。 它申请一个record slot并返回这个新slot的内存地址， 由调用者用有效信息来填充这个record space。 append 函数不知道data和它的含义。</p><p>scan支持谓词。 next 以带参数和一个record 地址的方式来调用predicate function。 selective scan就是一个support function， scan 机制依赖从上层level传过来的predicate function。</p><p>support function作为函数entry point 传给 一个operation， 一个无类型指针作为predicate 参数。 support function的参数有2种， compiled和解释型的查询执行。 在编译性的scan中， predicate evaluation 函数是机器代码， 它的参数可以是一个常量或指向几个谓词函数的指针。 例如当predicate 是将一个string和record的field进行比较，comparison function是predicate function， string 是predicate 参数。 在解释型的scan中， 用一个通用的解释器来evaluate 查询的所有predicate， 可以传合适的code 给解释器。 解释器的entry point是predicate function。</p><p>indices 目前支持B+ tree， 接口类似files 。 一个叶子节点包含一个key和对应的信息（这个信息包含RID， 这个信息可以允许任意内容）， key 和信息可以是任何类型， 必须提供一个比较函数用于比较key。 比较函数用的参数类似predicate scan 的参数。 b+ 树支持类似file 一样的scan， 如predicate和append 操作。除此之外，它还可以支持查询特定的key和 一个确定上下范围的区间。</p><p>处理的中间结果（后续称为streams）， volcano 称他为virtual device， 它是只存在于buffer中， 一旦unpin，他们就消失了。 对于持久化结果或中间结果，都是使用相同的机制来简化实现。</p><p>file system 功能基本上都是很常规的， 但他们用一种灵活，高效和compact 方式来实现。 file system 支持最基本的抽象和操作， 并称之为 device， file， record， b+tree， scan。提供策略来访问这些对象，由高层软件来决定策略。设计和实现机制的一个重要目标是性能， 因为只有潜在的机制是高效的情况下， 才可以做性能研究和并行。 性能和扩展性的tradoff的前提条件是 有一个高效evaluation 平台。 </p><h2 id="query-processing"><a href="#query-processing" class="headerlink" title="query processing"></a>query processing</h2><p>查询用查询plan或关系代数表达式来表示。 关系代数的算子带查询处理算法，可以称这种关系代数为executable 关系代数（物理关系代数）， 另外一种是逻辑关系代数（relational 关系代数）， 算子可被viewed并被实现为一组object上的操作（volcano 不依赖这组对象的内部结构）。 实际上，更倾向在一个oriented对象数据库中使用volcano 作为query processing， 使用volcano 可以将 processing 和data item的解释进行分离。</p><p>所有的关系代数算子都被实现为迭代器， 他们支持open-next-close 协议， 基本上， 迭代器提供一个loop中的迭代组件，如initialization， increment和loop termination condition和最后的housekeeping。 允许对任意operation的结果进行迭代，类似对一个传统文件scan结果上的迭代操作。  每个迭代器带一个state record， 它包含一些参数， 如在open过程中申请hash table的大小和 state（hash table的location）。 迭代器的状态信息都存在state record并且没有static 变量， 因此可以在一个query中多次使用算法，只不过使用多个state record。</p><p>data object的操作和解释（如比较或hashing）以support function方式（entry point的指针）传给迭代器。 每个support function都使用一个参数（可以是解释型也可以是编译型）。 如果没有support function，迭代器就是空算法壳子。</p><p>迭代器可以嵌套并且可以像coroutine一样操作。 state records之间用input 指针link 在一起， 并且input指针也保存在state中。<br><img data-src="/assets/volcano_iterator.png" alt="image.png"><br>上图显示一个查询plan的2个算子。<br>filter operator的一个参数是print stream的function。 最顶层的结构可以访问state record和（print ）function。 通过指向这个结构的指针， 可以调用这个filter 函数并将他们的local state 作为处理参数传给它（结构）。<br>这些函数（如open-filter）可以使用被包含在state record内的input 指针来调用input 算子函数。 因此当需要时， filter的函数可以调用file scan的函数， 根据filter的需要来推进 file scan。 整体上就是打印一个文件中选中的行。</p><p>总的来说： input 是一个复杂结构的指针， 这个结构是operator之间的桥梁， input 内部包含下一个算子的function （这个function是一个参数）， 下一算子的input 和state，当前input 存在当前state中。 </p><p>使用迭代器的标准形式， 一个算子不需要知道是什么算子产生它的输入，不论是复杂查询或一个简单的文件扫描。 我们称这种方式为匿名inputs或streams。 streams是一个简单但powerful的抽象， 允许combine 任意数量和任意类型的算子为一个复杂查询， 是volcano 扩展性的第二个基石， streams 代表了最高效的执行时间（无同步operator）和空间。</p><p>对最顶上的算子调用open 会实例化对应的state record 的状态（如申请一个hash table）并对所有的input进行open， 通过这种方式， 一个query的所有迭代器都会被递归初始化。 为了处理query， 持续不断call 顶层的next知道碰到end-of-stream 标记。 顶层算子调用next时， 如果他需要更多的input data 来产生output， 他会调用它input的next。 类似close 操作。</p><p>许多query或环境变量（query predicate 参数和system load 信息） 会影响policy 的决定，当opening 一个查询plan。 传递这些参数的过程称为bindings。 它是一个无类型指针。 同样用support function来实现policy 的决定。 例如 hash join 模块允许动态调整hash table的大小 ， bindings 参数是在动态query evaluation plan中特别有用。</p><p>树形结构的query evaluation plan 通过命令驱动的dataflow来执行query。 next 操作的返回值除了状态标识器外，还有一个Next-Record 结构， 它包含一个RID 和在buffer pool中的record 地址。 pinned在buffer中的record 一次只能被一个operator拥有。 一个算子接受一个record 后， 可以hold 一会儿（如在hash table中）， unfix 他当predicate失败或传给下一个operator。 复杂的操作会产生新的record 比如join， 必须在传出output之前组装output 并unfix他们的输入。这导致了大量的buffer call。</p><p>一个Next-Record 结构智能指向一个record。当前实现是传完整的record在operators之间。 在join中，会产生新的record，中间涉及不少copy操作， 这容易引起争论， 有一种选择是保留原始的record， 产生一个pairs、triples等。 但这个没有明确实现， 因为volcano已经实现了必要的机制，如filter 迭代器， 可以在一个stream中用一个RID 指针pair 来替换record。</p><p>总的来说， 命令驱动的数据流通过封装算子为open&#x2F;next&#x2F;close迭代器来实现。将processing control（迭代机制） 和item的解释和操作 独立分开的方式提供对任何数据类型的处理能力。</p><ol><li>scans， functional join， filter。 第一套scan的接口已经在file system里面介绍了， 第二套scan 接口，可以scan file和b+ 树， 提供迭代器接口。 open 打开file或b+树，用文件系统level的scan 过程初始化scan。 把文件名或关闭的file descriptor 存到state record里面， 是一个可选的predicate和绑定到b+ 树scan。 2种scan接口功能上等价， 区别在于，filestem的scan大量被内部使用，迭代器接口提供在查询plan中的叶子操作。</li></ol><p>B+树索引在他们的叶子包含keys和RID ， 想要用B+树的索引， 需要获得record 所在的数据文件。 在volcano中， 这种look-up 操作从B+树scan迭代器中剥离出来，用functional join 算子来执行。  这个算子用包含RID 的stream records 作为输入，或者输出用RID对应的records， 或者用input record和retrieved record 组成新的record （作为输出）， 这样join b+树的entry和对应的data record。</p><p>将index 的search和record的获取分开， 有几个原因， 第一， 在叶子节点存储数据不一定是个好主意， 有时我们需要实验用lookup key来获取其他的相关信息， 其次， 这样可以将RID list的控制和复杂查询切分开， 第三，目前这种方式比较自然，可以更智能的组装复杂的对象。<br>上例的filter 算子执行3种功能， 依赖stat record 是否含对应的support function。  predicate 函数使用一个selection predicate， 如实现bit vector filter。 transform 函数 创建一个新的record， 一般一个全新record（如使用projection 创建一个新的record）。 apply 函数在每个record上只调用一次因为side effect受益， 典型的example就是update并打印。 filter 算子也是一个side-effect 算子（如创建一个用bit vector 过滤的 filter）。 filter 算子是一个单入单出的算子，可以干非常多的事情。</p><ol><li>one-to-one match， 同filter 算子一起， one-to-one match 算子可能是最高频算子， 它实现了许多set-matching 函数。 在一个算子中， 他实现了join， semi-join， outer-join， anti-join， intersection， union， difference， anti-difference， aggregation， 和duiplicate elimination。 one-to-one match 算子是一个像sort 一样的物理算子。 operator来实现所有的操作， 一个item的输出依赖对一对item比较的结果.</li></ol><p><img data-src="/assets/volcano_sets.png" alt="image.png"></p><p>上图显示在one-to-one match 的二元算子的基本规则， 切分matching和非matching 2个set的组件，称为R, S， 产生相应的子集， 可能在一些类似join后的transformation和combination。 因为所有操作基本要求相同的步骤， 因此逻辑上实现在一个通用和高效的模块内部。 一元操作（aggregation）和二元（equi-join）操作的区别在于前者是对同一个input进行比较，而后者是对不同input比较。</p><p>因为volcano的one-to-one match 是data model 独立， 所有data item上的操作都是通过support function引入的， module不限制为关系模型但可以在任意数据类型上执行set matching 功能。</p><p>volcano 实现了2种join， merge-join&#x2F;hash join， 允许对sort-&#x2F;hash-base的查询算法的tradoff和对duality上进行探索。 在传统的hash join（纯内存操作，非hybrid hash join）上， build阶段（用一个input来build hashtable）后，probe 阶段（用另外一个input 来probe 这个hash table 来决定匹配并构建新的tuple）后， 原来的hash table 会扔弃掉， 增加了第三个阶段flush phase， 这个阶段用于做aggregation或其他操作。</p><p>one-to-one match 算子也是象其他算子一样的迭代器， open 包含build 阶段， 其他2个阶段都包含在next function， 当第二个input exhausted后，自动从probe阶段切换至flush 阶段。<br>build阶段可以用来去重或在build input上执行aggregation。 one-to-one match module 并不强制要求一个probe input， 如果只有aggregation而没有后续的join， probe 阶段就可以跳过。对于aggregation来说， 不是像传统hash join 一样来插入一个tuple到hash table中， 一个input 如果直接匹配 hash bucket的tuple， 新的tuple直接扔弃， 把值累加到老的tuple中。 </p><p>hash table 会消耗大量内存，当内存oom时， 称为hash table overflow， 有2种办法解决， 如果用了优化器时， 可以做partition input， 另外一种是，当出现overflow时 创建overflow 文件。 目前volcano 使用hybrid hash join算法， 比在GAMMA里面的hybrid hash join 有几个优点：1. 插入到hash table的tuple 不需要被copy， hash table 直接指向在buffer中的record。 如果不能densely pack， 很容易内存用完。  有一个参数称为packing threshold 表示填充memory 非常快，当hash table中的item数到达这个数，就必须将item 发到overflow 文件（overflow 文件的cluster 并没有unfix到buffer， 也就是还在内存中）中，当hash table中item数达到spilling threshold， 这时将第一个分区文件spill到磁盘， hashtable 内的tuple数量减少， 当再次达到spilling threshold， 第二个partition 被再次spill到磁盘， 依次进行recursive。</p><p>第一个parition的fan-out 是由总内存大小减去packing threshold 的内存大小。 通过设置packing和spilling threshold 基于expected build input size， 优化器可以避免对小的build input 进行record copy， 避免大的build input的overflow。 当build input size 不能被准确estimated时， 优化器可以基于评估的input size进行动态调整， 如可以很确定不会出现overflow， 可以把threshold 设置很高， 如果overflow极有可能， 就会把threshold设定为相对低的一个值。 </p><p>可以把初始的packing和spilling threshold 设置为0, 这样overflow的avoidance行为就和grace database machine是一样的。 除了调整overflow的参数， 还可以优化cluster size， recusion depth参数。<br>one-to-one match的第二个版本是基于sorting， 有2个模块， 一个是disk-based merge-sort 和实际的merge-sort。 merge-join类似hash-join 一样支持semi-join， outer join， anti join和set 操作。 sort 算子用迭代器来实现。 open sort 迭代器 准备sorted runs for merging， 如果输入数超过最大支持数， 会进行多级merge。最后一个merge 有next 函数来进行驱动。 如果完整的input 可以放到sort buffer中， 他会保留在buffer中，直到下一个next 函数。 sort 算子同样支持aggregation和去重。 可以在操作的早期执行， 比如写临时文件。 </p><p>通过将item组上的操作控制 和独立item上的解释&#x2F;控制进行分离， 他可以高频执行大量的set matching task并可以在任意data types和models 上执行这种task。  这种在overflow 管理上的机制和策略的切分可以支持overflow avoidance 和hybrid hash overflow resolution 一样。 同时支持sort&#x2F;hash-based 算法提供一些实验性质的研究， 在duality和trade-off 在sort和hash基础的查询处理算法之间。迭代器保障了one-to-one match 算子 可以很容易和其他算子合并甚至一些还没有设计的新算子。<br>3. one-to-many match， 这个算子是 compare 每个item 和许多其他的item 进行比较以决定是否生成一个新的item。 典型例子是relational division， 这个关系代数算子对应在关系微积分中的全称量化。volcano中有2个关系除， 第一个版本是基于sorting的 native division， 第二个版本被称为hash division， 用了2个hash table， 一个用于除数， 一个用于商。 可以发现有2种算法和基于aggregation 函数的可选算法， 在分析的和实验性的性能比较结果和对hash table overflow和多处理器实现的2个分区策略的讨论细节中。 我们正在研究怎样生成这些算法， 用一种和aggregation&#x2F;join的类 可比较的方式来生成这些算法。</p><h1 id="扩展性"><a href="#扩展性" class="headerlink" title="扩展性"></a>扩展性</h1><p>讨论volcano 用了哪些方式来提升扩展性。 </p><ol><li>当extend 对象类型系统，比如 一个新的抽象数据类型类似date&#x2F;box， volcano 可以不受影响的， 因为他自己不提供type system。 所有对独立object的操作和计算都是由support function来执行的。 volcano 不完整，但将处理独立 instance的解释， 提供了一个好的接口。 volcano 在instance type和语义上继承了扩展性。</li></ol><p>在算子之间用next 迭代器处理的data item是记录。 这会是一个不可接受的问题和限制。 volcano 只传root component 节点 records， 在load 和fixing 必要的record 到buffer中和合适的swizzling record内部指针。 一些非常简单的对象会被一些函数性的join算子给封装起来。 在对象或者non-first-normal-form 数据库中， 对这种算子的通用化是很有必要的， 可以在volcano中很容易做到。实际上， 在REVELATION 对象数据库中， 用了一个assembly 的算子。 </p><ol start="2"><li><p>为了在单独object或aggregate 函数中增加新的函数， 如几何平均， 要求合适的support function， 并把他传给一个query procession routine。 也就是， 只要接口和返回值是正确的， query 处理路径不会受support function的语法影响。 volcano 在functionality on 单独object的扩展性是因为 volocano 只提供抽象和实现 在处理和sequencing 对象集合用stream，  解释和控制 独立object 的能力是通过supoort function来引入的。 </p></li><li><p>为了合并一个新的access 函数， 如R-tree的多维索引， 必须定义合适的迭代器。 不仅检索并且维护storage 结构用迭代器的形式。 比如， 由predicate 定义的一组item需要被update， 迭代器可以 feed 它的data 到一个维护的迭代器。 fed into到维护迭代器的item 会包含要被update的storage 结构， 如RID 或一个key， 一个新的值会被计算出来。  update 多个结构可以用nested 迭代器来高效组织和执行。 更进一步， 通过btree排序让维护更高效， 一个sort&#x2F;ordering的迭代器很容易可以加到计划中。 很容易考虑计划不仅通过检索并且update plan。 stream的概念很开放，匿名的input 可以保护现存的查询处理模块和来自其他迭代的新的迭代器。 </p></li><li><p>为了引入一个新的算法， 这个算法需要支持code迭代器的范式。 算法必须实现open&#x2F;next&#x2F;close ， 必须用这些函数来处理输入stream和输出。 当以这种形式添加算法后， 集成这个算法到volcano是非常简单。 例如， one-to-one match或者division 算子 不用考虑其他算子就被加入进来， 在早期 支持overflow的hash base的one-to-one match算子替换in-memory-only的hash base 算子给取代时， 没有其他的算子或者meta 算子被修改。 最近添加了一个复杂object assembly 算子。</p></li></ol><p>在不同的context下考虑扩展性。 为了长期运行， 提供一个交互式 前端可以让使用volcano 更简单。 我们正在做一个 双前端， 一个是非优化的命令解释器 基于volcano的执行代数， 另外一个是基于逻辑关系代数或微积分语言的带优化的前端。优化器生成plan的翻译 由一个模块 walk 优化器生成的evaluation plan， state records， support function。 用有优化能力的前端来做 动态 查询evaluation plan。 </p><h1 id="dynamic-query-evaluation-plan"><a href="#dynamic-query-evaluation-plan" class="headerlink" title="dynamic query evaluation plan"></a>dynamic query evaluation plan</h1><p>在大部分数据库系统中， 一个查询嵌入到用传统编程语言实现的程序后， 一旦程序被编译，这个查询就会被优化。 查询优化器必须假设这个查询内出现的程序变量和这个数据库的数据是不变的。这些假设包括， 通过猜测程序变量的典型值和database 在查询期间是不变， 优化器同样也会预测资源的使用量。 优化的结果依赖这些假设。 如果一个查询计划在一个时间内重复使用， 则重新优化就很有必要。 我们正在避免重新优化， 通过一个dynamic query evalution plan的技术。 </p><p>volcano 使用choose-plan 算子来实现 多plan的access module和动态计划。 它并不是像data 控制算子那样运行。 他提供查询执行上的控制， 类似meta-operator。 他也提供open&#x2F;next&#x2F;close 函数， 因此可以插到plan的任何地方。open 操作决定了用那个等价plan， 并用他作为输入。 在open 上调用support function 来支持策略决定。 next&#x2F;close 会调用对应的操作来出来open 选择的那个input。 </p><p><img data-src="/assets/volcano_dynamic_plan.png" alt="image"><br>selection 谓词是由一个程序变量来控制的。 index scan 和function join 一般比file scan 更快，但如果index 没有load到内存并且大量的item 需要检索时， file scan 更快。 优化器会为这2种case 高效准备， 应用程序可以在任何谓词值下都可以工作很好。 </p><p>choose-plan 灵活性很高，如果在一个query evaluation plan的顶层， 它实现多plan access module。 如果在一个计划中，插入了多个choose-plan 算子， 他实现一个动态query evaluation plan。 所有动态plan的形式可以用一个简单和高效的机制来实现。 choose-plan 没有policy来决定选择那条plan， 它仅仅提供机制， 通过support function引入policy， 决策由query 变量&#x2F;resource&#x2F;竞争 状况，其他考虑（如用户priority）。</p><p>choose plan 算子提供在查询优化和极少的代码上很大的自由。因为他符合查询处理的范式， 他对其他operator 毫无影响，并且可以用很灵活的方式来使用。 </p><h1 id="多进程查询evaluation"><a href="#多进程查询evaluation" class="headerlink" title="多进程查询evaluation"></a>多进程查询evaluation</h1><p>大量研究表明查询处理可以用并行算法进行加速。 很容易在数据里面探索多进程处理</p><ol><li>查询用一个计划树来表示， 算子很容易用几个进程来处理， 进程之间形成pipeline。 – 算子之间的并行</li><li>每个算子生产和消费数据， 这些数据可以被分区， 从而可以被并行 – 算子之内的并行</li></ol><p>当volcano 移植到多进程环境中， 代码基本不用修改。 所有的并行在一个算子内部并在算子间提供标准的迭代接口。<br>目前并行和同步 都是在一个叫做exchange的算子完成。 它同样有open&#x2F;next&#x2F;close， 因此它可以查到计划树的任何地方。 </p><p><img data-src="/assets/volcano_exchange.png" alt="image"><br>上图显示一个复杂的查询，包括file scan， join和exchange。 </p><h2 id="vertical-并行（垂直并行）"><a href="#vertical-并行（垂直并行）" class="headerlink" title="vertical 并行（垂直并行）"></a>vertical 并行（垂直并行）</h2><p>exchange的第一个功能是做vertical parallelism 或者处理之间的pipeline。 在共享内存中创建用于同步和data exchange的port 数据结构后，open函数创建一个新的进程。 子进程完全复制父进程。 在父进程和子进程中， 各自的exchange 算子走不同的路径。 </p><p>父进程作为消费者， 子进程作为生产者。 消费者进程内部的exchange 算子像普通迭代器一样运行， 仅仅是它接受到的数据是跨进程的而不是迭代器之间的call（同进程的）。 在创建子进程后， 消费者的open_exchange 就完成了。 next_exchange 就等待port 来的数据 并一次返回一个record。 close_exchange 通知生产者 可以close了， 等待生产者的acknowledgement并返回。 </p><p><img data-src="/assets/volcano_exchange_vertical.png" alt="image"><br>上图显示了vertical 并行或exchange 算子形成的pipeline。 exchange 算子创建多进程， 并执行各自进程的边界， 将现存的进程边界对work 的算子透明起来。 join 算子执行在同一进程， exchange 算子可以在执行树中任意放置。 exchange 仅仅提供并行的机制， 而各种并行策略都是有可能的。<br>在生产者进程中， exchange 算子通过在它自己输入上的open&#x2F;next&#x2F;close 成为查询树上在exchange 算子之下的其他算子的驱动。 对next 输出进行打包， 是一组next-record的数据结构。 打包大小是exchange 迭代器的state record 上的一个参数， 可以设置1 到32000. 但打包好， 它会发送到port 的一个linked list，并用信号量通知consumer 有个新的packet。 packet 放到共享内存， 只能被consumer 进行拆包。<br>当生产者的输入处理完， 生产者会标记最后一个packet 为end-of-stream tag, 等待consumer 同意关闭所有的open files。 等待一下很有必要， 因为虚拟设备上的文件不会被close 直到所有的record 都在buffer中被unpin。<br>exchange 模块使用一个和其他算子不同的dataflow 样式， 其他模块都是基于命令驱动方式的data flow， exchange 算子内的生产消费者使用一种数据驱动方式的dataflow。 我们同样倾向于对于horizontal parallelism 中使用exchange 算子， 它很容易基于data驱动来实现。 第二， 这个计划不再需要request message， 即使一个计划含有request message，比如使用信号量， 在一个共享内存的机器上执行会产生不可避免的延迟和开销。 高并行度和高性能的查询需要一个紧密网络， hypercube，共享内存机器， 最后证明data exchange的工作方式在share nothing的数据库中工作良好。<br>exchange 有2个开关 flow control 和反压， 他们使用额外的信号量。 假设生产者的速度比消费者快很多， 生产者就会pin 很多buffer， 这样自然就把整体性能拖慢。 如果使用flow control， 当生产者发送一个packet 到port， 它必须请求一个flow control 的信号量， 直到消费者接受到packet后释放对应的flow control 信号量。 信号量的初始值可以决定生产者可以领先多少个packet。<br>注意， 流控和命令驱动的数据流不是一回事。 一个区别是 命令驱动是一个刚性请求和分发， consumer 会等待producer 直到下个输出。 第二个区别是， 流控很容易结合水平分区和并行。</p><h2 id="水平并行"><a href="#水平并行" class="headerlink" title="水平并行"></a>水平并行</h2><p>有2种形式的水平并行， 一种称为bushy 并行和算子内并行。 在bushy 并行中， 不同的cpu 执行复杂查询树的不同子树。 算子内并行，表示不同的cpu 执行相同的算子，对数据集或中间结果做分区。<br>bushy 并行很容易通过在执行树中插入exchange 算子来实现， 比如树中含有merge join， 在2个input 各插入sort 算子后，在sort 算子和join算子之间插入exchange 算子。 在fork 子进程后， 子进程produce 第一个输入按照sort order， 父进程执行第二个sort input。 这样2个sort 就并行操作了。<br>算子内并行要求数据分区， 分区需要使用多文件， 尤其是在不同的设备上。 可以通过增加多队列在一个端口上实现中间结果的分区， 如果有多个消费者，则每个消费者消费自己的队列， 生产者用一个support function 来决定数据将去哪个queue， support function 可以是round robin， key range或者hash 分区。<br><img data-src="/assets/volcano_exchange_horizontal.png" alt="image"><br>上图显示水平分区。 join 算子被3各进程来处理， 每个file scan 算子被一个或2各进程来处理， 尤其是不同设备上文件分区。 和之前图的区别， 在exchange state reocrd内的并行度参数 会被设置为2或3， exchange 算子用分区support function来传输文件扫描输出到join 进程。 所有的文件扫描进程都可以传输数据到所有的join进程， 但数据只会出现在它自己的join 进程。 如果在不同属性上有2个join和使用分区性质的并行join， 这种限制就会让并行join 不可行。volcano 提供一个新的exchange 算子，叫inter-change。 </p><p>如果一个算子或者一个子树 被一组进程并行执行， 有一个进程会被称为master。 当打开一个查询树，并只有一进程运行，很自然就是master。 当一个master fork 了一个子进程 在生产者-消费者关系中， 子进程就是这个group的master。 master 生产者的动作就是通过support function 决定需要多少个slave。 如果生产者需要并行， 他就会fork 一些生产者进程。 当fork 生产者进程后， 生产者之间是没有同步， 除了2个列外， 第一， 当访问一个共享数据时， 指向消费者或buffer table的port， 在插入linked-list时，需要获得一个短时间的lock。 其次，如果一个生产者group 又同时时消费者group， 又至少2个exchange 算子和3个进程group 在vertical pipeline中， 同时兼生产者和消费者的group 需要同步2次。 在同步的空隙中， group的master 会创建port， 这个port 服务这个group的所有进程。<br>当close 自顶向下传递时，到达第一个exchange operator时， 消费者的master 执行close_exchange 通知所有的生产者进程， 他们可以用之前vertical 并行提到的信号量来close。 如果生产者又是消费者， 它会通知其他生产者。 这样，所有的算子会按序shut down并且整个查询会是自调度的。 </p><h2 id="exchange-算子的变体"><a href="#exchange-算子的变体" class="headerlink" title="exchange 算子的变体"></a>exchange 算子的变体</h2><p>这些变体都是实现在exchange 算子中， 由state record中的变量进行控制。<br>对于一些操作， 需要复制或broadcast 一个stream到所有的消费者。 一个例子就是 fragment-and-replicate 并行join算法， 其中一个输入不动，而另外一个输入需要发送到所有的进程。为了支持这个， exchange 算子可以直接发送所有的record 给所有的消费者， pin 他们多次在buffer pool中， 注意不是让他们被copy 多次，而是在共享内存中，每个消费者不再需要他们的时候可以unpin。<br>变体给exchange 加了2个feature，<br>第一个， 实现一个merge network， 通过这个网络， 多个进程生产的sort stream 可以被其他进程并行merge。 sort 迭代器可以生成sorted stream， 一个merge 迭代器很自然来自sort module。 它用一个单路merge，而不是cascade merge。 merge算子的输入是exchange， merge 迭代器要求分辨input record 通过他们各自的生成者。 另外一个例子， join 算子不关心输入的record 从哪里产生， 所有的输入在一个stream中就可以被计算。 对于merge 算子，识别input record由哪个producer 产生很重要， 这是为了正确的merge 多个sorted stream的function。</p><p>我们修改了exchange模块，这样可以将输入record 独立他们的生产者。 next_exchange 的第三个参数用于和对应生产者通讯， 从merge 到exchange 算子。 一些更进一步的改进，比如增加exchange 用的input buffer数量， 在生产者和消费者之间信号量的数量。 这些改动都是用这种方式，支持多路merge tree。 自动选择merge 路径， 这样在每个level load 都可以均匀分布。</p><p>第二个feature， 我们实现一个sort 算法， sort 随机分区的磁盘data到一个range partition的文件， 比如，一个sorted 文件分布在多个磁盘上， 要求每个cpu 2个进程 ， 一个执行file scan 和分区record， 另外一个进程执行sort them。  创造比cpu 数还多的进程会带来显著的额外成本。<br>为了更好的利用可使用的算力， 减少每个cpu的进程数， 一个cpu 一个进程， 这样就需要对exchange 算子进行改动。 在这之前，exchange 算子只能位于一个进程的算子数的top或bottom。 这样，exchange 算子可以在进程算子树的中间。 当open 一个exchange 算子， 它并不fork 任何进程，而是建立一个通讯端口用于数据exchange。 next 会要求它input tree的record， 可能发送他们到group的其他进程， 直到找到这个record所属的partition。 这种算子被叫做inter-change。 这种方式不再需要flow control。 一个进程只有当没有input数据时跑生产者。 因此，它可能作为消费者overrunning ， 生产者不会被调度。 </p><h2 id="File-System-Modification"><a href="#File-System-Modification" class="headerlink" title="File System Modification"></a>File System Modification</h2><p>需要修改文件系统以支持多进场并发请求。 volcano 不包含保护文件和record，而是磁盘的volumn table的内容，另外那种非重复性的操作如mount 都是在root process 开始前或查询结束后执行。<br>改动最大的还是在buffer module。 实际上，让buffer manager 在sharing memory的机器上不成为bottelneck， 本身就比较有挑战。 buffer manager上的并发控制被设计成为未来有效和高效率机制研究的测试平台，从而没有破坏policy和mechanism的分离。<br>用一个排他锁是最简单保护一个buffer pool和它内部数据的方式。 减少并发会把并行查询的优点给抹杀掉。 因此buffer manager 用了2层设计。每个buffer pool 都有一个锁， 并且每个描述符（page或cluster）也有一个锁。当search或update hash table和bucket chain时，必须持有buffer pool的锁。 在做i&#x2F;o 操作时，绝不能持有它， 对于长时间的操作也不能持有buffer pool的锁。而当执行i&#x2F;o操作或跟新buffer中的description（修改count值）时，必须持有page或cluster 锁。<br>当一个进程请求buffer中的一个cluster时， 可以使用原子try 机制（test-and-lock）. 如果try 失败，则pool lock失败（读者感觉应该是descriptor lock失败吧） ， 这个操作就会被delay并被重启。 因为持有锁的进程有可能替换请求的cluster， 因此有必要重启buffer的操作，如hash table的查询。 请求的进程必须等待上一个操作的结果。 这种对descriptor lock的重试机制可以避免死锁。 死锁有4种要求， mutual exclusion， hold-and-wait， no preemption和circular wait。volcano 破坏了第二个条件， 但另外一方面， 可能会出现饥饿， 如果减少buffer 竞争的buffer 修改可以极大的避免饥饿。</p><p>总的来说， exchange 模块封装的并行处理， 它提供一个丰富的并行机制。 buffer manager 和file system 只需做很小的改动。 exchange 最重要的属性就是它实现了3种并行方式，让并行执行完全自调度， 支持各种policy，如分区， packet size。 完全将data selection， 控制， derivation 相互之间独立开。 </p><h1 id="未来"><a href="#未来" class="headerlink" title="未来"></a>未来</h1><p>研究：</p><ol><li>研究lru&#x2F;mru buffer 替换策略， 通过一个keep-or-toss hint。</li><li>研究在一个device 上不同的cluster size， 通过动态申请内存，而不是静态（要求仔细的evaluate），从而避免buffer shuffling。</li><li>研究duality 和trade offer between sort- andhash-based 的查询处理算法和实现</li><li>需要并行算法的性能测试和在share memory 架构下的瓶颈识别， 类似在分布式环境下的研究</li><li>在分布式环境下，一个独立的调度进程的优缺点</li><li>在shared nothing 环境下， data-driven 工作的挺好的， 尝试在shared memory的网络环境下， demand- 和data- 驱动 的结合。</li></ol><p>改进：</p><ol><li>volcano 对error 探测扩展性很好，但没有用fail-fast 函数来封装error。 因此，当有一个all-or-nothing的语义时，需要修改所有的模块，在exchange 模块中，这个非常复杂， 尤其是分布式内存环境。</li><li>为了更好性能评估， volcano 需要加强多用户系统以允许查询之间的并行。</li><li>需要增加事务语义包括recovering</li></ol>]]>
    </content>
    <id>https://ilongda.com/2018/docs/paper/Volcano/</id>
    <link href="https://ilongda.com/2018/docs/paper/Volcano/"/>
    <published>2018-10-10T11:42:57.000Z</published>
    <summary>Volcano 可扩展并行查询系统论文笔记：迭代器接口、exchange/choose-plan 元算子与 support function 扩展机制</summary>
    <title>《Volcano-An Extensible and Parallel Query Evaluation System》</title>
    <updated>2026-06-09T08:46:25.961Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="读书笔记" scheme="https://ilongda.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <category term="读书笔记" scheme="https://ilongda.com/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《乔布斯传》读后感","description":"《乔布斯传》读后感：探讨偏执与群体智慧、世界级营销大师手法，以及不同读者阅读后的个性化感悟差异，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://ilongda.com/img/qiaobusi.png","wordCount":638,"datePublished":"2018-10-08T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.943Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/qiaobusi/"},"url":"https://ilongda.com/2018/qiaobusi/","inLanguage":"zh-CN","keywords":["读书笔记"],"articleSection":["读书笔记"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"读书笔记","item":"https://ilongda.com/categories/读书笔记/"},{"@type":"ListItem","position":3,"name":"《乔布斯传》读后感","item":"https://ilongda.com/2018/qiaobusi/"}]}</script><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/qiaobusi.png"  alt="《乔布斯传》读后感"></div><p>乔布斯传，很早以前就看到人手一册， 但看到外界对乔布斯的点评就如同媒体点评马老师一样， 众彩纷纭， 最近因为机缘巧合， 被人要求阅读一下乔布斯传， 就把乔布斯传，拿过来读了一番， 读完之后，有很多不同的感悟，挑2个和大家探讨一下。<br> 第一个印象就是 唯有偏执才能成功，做什么事情都有自己的想法和见地， 甚至对很多事情到了偏执的程度， 年少的乔布斯就放荡不羁，想到了什么就会坚信不疑，   对精神世界的探索， 对素食主义的偏好， 对极简主义的信奉，所幸这些偏执大部分都是善意的， 但在中国， 有句古语叫， 兼听则明， 偏听则暗， 个人观点， 天才往往是超凡脱俗， 天才往往总是坚持自己内心深处的追求， 总是引领世界， 将世界引向自己的方向， 如果非天才级的人物， 最终还是会走向“三个臭皮匠抵得上一个诸葛亮”，依赖群体智慧， 群体的智慧往往是最稳定，大部分时候都是正确的。<br>营销大师，这个世界上， 有2种事情是最难的， 第一就是从别人的口袋里把钱掏过来， 第二个，就是把自己的思想灌输到别人的大脑中。 乔布斯是一位世界级的营销大师。 乔布斯，最开始也不是总是灌输自己的idea到别人的脑中， 但从印度之行回来后， 就开始开挂的人生， 总是在不停的说服其他人按照自己想法，当还是一家小公司时，在做每次展览的时候，就深谙如何抓住眼球 而且他总是和很多营销大师进行交流， 因此他的营销技巧在不断的完善， 另外，他对营销的追求， 远远是高于其他人一个量级， 因此，当苹果的广告出来时，总是给人耳目一新。 营销已经不仅仅对苹果的产品产生深远影响， 同样带来了一个 “乔布斯磁场”， 当乔布斯出现时，大家最后总是在不知不觉中和乔布斯的观点一致。</p><p>最后，一千个读者就会有一千个哈姆雷特， 相信大家每次阅读乔布斯，都会有不同的感悟 。</p>]]>
    </content>
    <id>https://ilongda.com/2018/qiaobusi/</id>
    <link href="https://ilongda.com/2018/qiaobusi/"/>
    <published>2018-10-08T11:42:57.000Z</published>
    <summary>《乔布斯传》读后感：探讨偏执与群体智慧、世界级营销大师手法，以及不同读者阅读后的个性化感悟差异，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>《乔布斯传》读后感</title>
    <updated>2026-06-09T08:46:25.943Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/categories/Database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《数据库实现》-- 第八章 事务","description":"《数据库实现》第八章事务：严格调度、级联回滚、ACR、逻辑日志、死锁检测与 saga 补偿事务等机制，更多细节与示例见正文。","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1575356944447-ae039735-fa22-4df5-831b-655e563dd6cb.png#align=left&display=inline&height=259&name=image.png&originHeight=259&originWidth=559&size=21290&status=done&style=none&width=559","wordCount":855,"datePublished":"2018-08-12T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.950Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter8_transaction/"},"url":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter8_transaction/","inLanguage":"zh-CN","keywords":["Database","数据库实现"],"articleSection":["Database","数据库实现"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"《数据库实现》-- 第八章 事务","item":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter8_transaction/"}]}</script><h1 id="第八章-事务"><a href="#第八章-事务" class="headerlink" title="第八章 事务"></a>第八章 事务</h1><p><a name="jF5yl"></a></p><h1 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h1><p><a name="TgR0y"></a></p><h3 id="严格调度"><a href="#严格调度" class="headerlink" title="严格调度"></a>严格调度</h3><p>级联回滚， 当中止一个事务时， 需要把读入这个事务写入的元素的事务进行回滚， 并依次进行递归回滚依赖。 </p><p>可恢复调度， 调度中每一个事务都在它所读取的所有事务提交之后才提交， 则这个调度成为可恢复的<br />日志的提交记录到达磁盘的顺序必须和他们被写入的顺序时一致的。 </p><p>避免级联回滚调度(AVOID CASCADING ROLLBACK)， 调度中的事务只读取已经提交事务的数据。 不进行脏读。 每一个acr 都是可恢复的。 </p><p>保证ACR 方式：</p><ol><li>严格封锁， 直到事务提交或中止提交， 事务才允许释放排他锁</li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1575356944447-ae039735-fa22-4df5-831b-655e563dd6cb.png#align=left&display=inline&height=259&name=image.png&originHeight=259&originWidth=559&size=21290&status=done&style=none&width=559" alt="image.png"><br />严格调度中块的回滚， <br />事务T 更新A 到缓冲区， 回滚时， 不需要使用日志的回滚， 把缓冲区直接释放到缓冲区队列，禁止刷到磁盘。 </p><p>严格调度中元组或对象（比块小）<br />一个块包含两个或多个事务修改的数据。<br />方法1， 从磁盘中读取老的值，更新到缓冲区<br />方法2， 用undo 日志恢复<br />方法3， 额外维护一个单独的主存日志， </p><p>3种方法都不好， 第一种需要额外的磁盘， 第二种方法， 需要查看磁盘上大量日志；方法3， 消耗额外大量内存。 </p><p>成组提交， 不立即将日志提交记录刷新到磁盘， 避免脏读， 按日志顺序刷新， 提交记录到缓冲区日志中，以后释放锁。 </p><p><a name="83vnP"></a></p><h3 id="逻辑日志"><a href="#逻辑日志" class="headerlink" title="逻辑日志"></a>逻辑日志</h3><p>逻辑日志， 只描述块中的变化。 </p><ol><li>操作时幂等的， 可以操作多次</li><li>当改动的数据量比较少时， 记录改变的字节及其位置。</li><li>改变描述简单，易于恢复，但改动量比较大。 </li><li>改变数据库许多字节，但改变不可撤销。</li></ol><p> </p><p>比如位B-树记录逻辑日志， 写入描述变化的简单记录。 </p><p>从逻辑日志恢复。 </p><p><a name="QbvB2"></a></p><h2 id="死锁"><a href="#死锁" class="headerlink" title="死锁"></a>死锁</h2><ol><li>即使是两阶段也会导致死锁</li><li>共享锁升级排他锁， 容易死锁</li></ol><p><a name="MZGav"></a></p><h3 id="死锁检测"><a href="#死锁检测" class="headerlink" title="死锁检测"></a>死锁检测</h3><ol><li>超时，最简单方式</li><li>等待图，  有向无环图</li><li>通过时间戳检测死锁， 都是较老的事务占有优势， 并且在实际过程中， 比等待图更容易<ol><li>事务T 等待U 持有锁时<ol><li>等待-死亡方案， 只会等待比较新的事务<ol><li>如果T 比U 老， 允许T 等待U 持有的锁</li><li>如果U 比T 老， 判断T 死亡， T 将回滚</li></ol></li><li>伤害–等待方案， 只会等待比较老的事务<ol><li>如果T 比U 老， 他将伤害U， U 必须回滚并放弃T 需要的锁</li><li>如果U 比T 老， T 等待U 持有的锁</li></ol></li></ol></li></ol></li></ol><p><a name="LDK69"></a></p><h2 id="长事务"><a href="#长事务" class="headerlink" title="长事务"></a>长事务</h2><p>有时会出现长事务。需要严格长期锁和无锁控制之间做一个平衡。</p><p><a name="isieX"></a></p><h3 id="saga"><a href="#saga" class="headerlink" title="saga"></a>saga</h3><ol><li>一系列动作</li><li>一个图， 其节点是动作节点或终点。</li><li>有一起点</li></ol><p>要求是五环图。</p><ol><li>可以认为每个动作本身是一个短事务</li><li>整个事务即任何通向终止结点的路径通过 “补偿事务”机制来管理</li></ol><p> </p><p><a name="HqA9O"></a></p><h3 id="补偿事务"><a href="#补偿事务" class="headerlink" title="补偿事务"></a>补偿事务</h3><p>saga 中，每一个动作都有一个补偿事务，成为A-1, 将状态回滚到初始状态。</p><p>原理：<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1575374049900-e6ecb1df-7a62-4154-9af2-e8e12ec2d73c.png#align=left&display=inline&height=35&name=image.png&originHeight=35&originWidth=654&size=6302&status=done&style=none&width=654" alt="image.png"></p>]]>
    </content>
    <id>https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter8_transaction/</id>
    <link href="https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter8_transaction/"/>
    <published>2018-08-12T11:42:57.000Z</published>
    <summary>《数据库实现》第八章事务：严格调度、级联回滚、ACR、逻辑日志、死锁检测与 saga 补偿事务等机制，更多细节与示例见正文。</summary>
    <title>《数据库实现》-- 第八章 事务</title>
    <updated>2026-06-09T08:46:25.950Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/categories/Database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《数据库实现》-- 第七章 并发控制","description":"《数据库实现》第七章并发控制：可串行化调度、冲突图、两阶段锁、优先图判断及基于锁的并发实现，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1574143862145-9fa229cc-3e93-4a54-aeb2-996a0b444f0f.png#align=left&display=inline&height=232&margin=%5Bobject%20Object%5D&name=image.png&originHeight=232&originWidth=258&size=11020&status=done&style=none&width=258","wordCount":2099,"datePublished":"2018-08-11T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.950Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter7_concurrency/"},"url":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter7_concurrency/","inLanguage":"zh-CN","keywords":["Database","数据库实现"],"articleSection":["Database","数据库实现"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"《数据库实现》-- 第七章 并发控制","item":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter7_concurrency/"}]}</script><h1 id="第七章，-并发控制"><a href="#第七章，-并发控制" class="headerlink" title="第七章， 并发控制"></a>第七章， 并发控制</h1><p><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574143862145-9fa229cc-3e93-4a54-aeb2-996a0b444f0f.png#align=left&display=inline&height=232&margin=%5Bobject%20Object%5D&name=image.png&originHeight=232&originWidth=258&size=11020&status=done&style=none&width=258" alt="image.png"><br />调度器保证并发执行事务能保持一致性， 当调度器接受到事务请求时，并不是立即执行，而是稍等一下， 有的时候是因为缓冲区的缘故， 有的时候是因为lock的缘故， 甚至有的时候会中止提交的请求。 <br />调度器关注3样元素， 锁， 时间戳和有效性确认。 <br /></p><p><a name="2wWad"></a></p><h2 id="串行调度和可串行化调度"><a href="#串行调度和可串行化调度" class="headerlink" title="串行调度和可串行化调度"></a>串行调度和可串行化调度</h2><p><a name="kasHt"></a></p><h3 id="串行调度"><a href="#串行调度" class="headerlink" title="串行调度"></a>串行调度</h3><p>调度的粒度是完整执行一个事务， 这个事务内包含大量动作， 事务和事务之间是串行化。 <br /><br><br />串行化调度可保持数据库的一致性， 如果一个调度，他执行并不是串行化，但得到的结果和串行化是一致的，则可以称这个调度为可串行化<br /></p><p><a name="xkxvf"></a></p><h3 id="冲突可串行化"><a href="#冲突可串行化" class="headerlink" title="冲突可串行化"></a>冲突可串行化</h3><p>冲突： 如果将顺序改变， 涉及的事务至少一个行为有改变<br /></p><ol><li>Ri(X), Rj(Y) 不会冲突</li><li>Ri(X), Wj(Y) 不会冲突， 只要X!&#x3D;Y, </li><li>Wi(X), Rj(Y) 不会冲突</li><li>Wi(X), Wj(Y) 不会冲突</li></ol><br /><br />冲突<ol><li>同一个事务的2个动作永远都是冲突的</li><li>不同事务对同一元素的写冲突</li><li>不同事务对同一元素的读写也冲突。</li></ol><p><br />无冲突的动作是可以交换， 进行任意非冲突的交换， 将该调度转换为一个串行调度。 <br /></p><p><a name="F7gfH"></a></p><h3 id="优先图及冲突可串行化判断"><a href="#优先图及冲突可串行化判断" class="headerlink" title="优先图及冲突可串行化判断"></a>优先图及冲突可串行化判断</h3><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574233140606-3c6ae6cc-5ce3-4e56-b8fd-597aabb83d35.png#align=left&display=inline&height=32&margin=%5Bobject%20Object%5D&name=image.png&originHeight=32&originWidth=514&size=4287&status=done&style=none&width=514" alt="image.png"><br />对于A, r2(A) 在w3(A)前，w2（A）在w3（A）&#x2F;r3(A) 前   –》 因此就A 而言， T2 优先T3<br />对于B, r1(B) 在w2（b）前， w1（b）在r2（b）&#x2F;W2(B)之前， 因此 就B 而言， T1 优先T2<br /> <br />我们可以构造S的优先图， 如果没有构成环， 则表示冲突可串行化。 就可以通过相邻动作的合法交换，改变调度中动作的顺序， 直到调度称为一个串行调度<br />基于原来的T1 &lt; T2 &lt; T3<br />可以修改调度为<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574233651640-cb52f0e2-7907-432c-9dc9-5a6451d75704.png#align=left&display=inline&height=26&margin=%5Bobject%20Object%5D&name=image.png&originHeight=26&originWidth=526&size=4393&status=done&style=none&width=526" alt="image.png"><br /><br><br />可串行化的调度， 是一个迭代的过程， <br />找到一个事物Ti， 这个事物没有依赖， 将这个事物移到调度的最前， 然后迭代事物序列，找到剩余的事物中，依赖除了Ti 之外的事物。 <br /><br><br /><br><br />不过有的时候， 冲突可串行化不重要。</p><ol><li>比如， 当T1 修改a， T2 也修改a， 但最后的T3 修改a， 因此T1 和T2 的顺序不重要， 因为最终都是T3 的修改是最终值。</li></ol><p> </p><p><a name="KgiX0"></a></p><h2 id="基于锁的可串行化实现"><a href="#基于锁的可串行化实现" class="headerlink" title="基于锁的可串行化实现"></a>基于锁的可串行化实现</h2><p>事物获得他所访问元素上的锁， 防止其他事物在同一时间访问这些元素并因而引入非可串行化的可能。 <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574234253449-bbb38dd8-9e86-484b-b416-4d8af3cc4244.png#align=left&display=inline&height=201&margin=%5Bobject%20Object%5D&name=image.png&originHeight=201&originWidth=323&size=11600&status=done&style=none&width=323" alt="image.png"><br />用一张锁表来帮助调度器做一些决策， 事物在读写数据库元素以外必须申请和释放锁。 <br />因此对，之前的case<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574235038800-5a06cd28-87ce-4b22-a268-f4a37b99c837.png#align=left&display=inline&height=56&margin=%5Bobject%20Object%5D&name=image.png&originHeight=56&originWidth=797&size=13959&status=done&style=none&width=797" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574235048838-3c80c9f5-d071-4e5e-bbf1-cb77728924bb.png#align=left&display=inline&height=312&margin=%5Bobject%20Object%5D&name=image.png&originHeight=312&originWidth=713&size=31158&status=done&style=none&width=713" alt="image.png"><br /><br><br />现在带锁的操作如下<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574236415998-361a369d-15a9-4b33-a264-5a8a33ce2cb5.png#align=left&display=inline&height=102&margin=%5Bobject%20Object%5D&name=image.png&originHeight=102&originWidth=652&size=12747&status=done&style=none&width=652" alt="image.png"><br />仅仅带锁还不能解决问题，还是不能保证可串行化。 <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574237129125-a4860d4b-5fc7-4555-82ee-4209a648afc7.png#align=left&display=inline&height=310&margin=%5Bobject%20Object%5D&name=image.png&originHeight=310&originWidth=377&size=21365&status=done&style=none&width=377" alt="image.png"><br /></p><p><a name="GaGKW"></a></p><h3 id="2阶段锁"><a href="#2阶段锁" class="headerlink" title="2阶段锁"></a>2阶段锁</h3><p>2阶段锁可以保证一致事务的合法调度， 就是冲突的可串行化。 <br />原则：</p><ol><li>在一个事务中， 所有的lock 操作先于所有的解锁操作</li><li>2阶段（2 Phase lock）， 获得锁的第一阶段， 释放锁的第二阶段</li></ol><p><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574237350478-2177c828-58d5-48d8-a3ac-73ecf367d33f.png#align=left&display=inline&height=275&margin=%5Bobject%20Object%5D&name=image.png&originHeight=275&originWidth=440&size=22552&status=done&style=none&width=440" alt="image.png"><br /><br><br />但这个存在死锁的可能性， 假设T2 先修改B， 然后再修改A, 则会出现死锁<br /><br><br />如何对2阶段锁，进行串行调度<br /><br><br />假设有n 个事务T1, T2, T3, …., Tn,  其中Ti 是第一个有解锁的动作， ui(x).  将Ti的动作不经过任何有冲突的读或写向前移动到调度最开始。 <br /><br><br /></p><p><a name="eSQJ4"></a></p><h3 id="读写锁"><a href="#读写锁" class="headerlink" title="读写锁"></a>读写锁</h3><p>用sli(x) 读锁<br />用xli(x)写锁<br /><br><br />事务的2阶段锁， 所有的lock必须在解锁前。 <br />一个调度过程<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574327500959-c45a1f89-75e1-478c-8093-162182827e21.png#align=left&display=inline&height=41&margin=%5Bobject%20Object%5D&name=image.png&originHeight=82&originWidth=836&size=11540&status=done&style=none&width=418" alt="image.png"><br /><br><br />因此产生的效果，可能如图<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574327580830-52f3f4af-f30e-4694-96b7-33b658f125ff.png#align=left&display=inline&height=185&margin=%5Bobject%20Object%5D&name=image.png&originHeight=370&originWidth=536&size=29018&status=done&style=none&width=268" alt="image.png"><br />冲突等价的串行顺序是T2 T1. <br><a name="MEvKY"></a></p><h3 id="更新锁"><a href="#更新锁" class="headerlink" title="更新锁"></a>更新锁</h3><p>传统的读写锁，其中当持有读锁时， 当更新时，不能升级为写锁。 但更新锁可以。 <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574328010687-94bbf408-377c-4a77-92ab-de29b9831e38.png#align=left&display=inline&height=145&margin=%5Bobject%20Object%5D&name=image.png&originHeight=290&originWidth=428&size=18621&status=done&style=none&width=214" alt="image.png"><br />当有读锁时， 允许再申请更新锁，<br />但当有更新锁时， 他就和写锁没有区别<br /></p><p><a name="0YXmW"></a></p><h3 id="增量锁"><a href="#增量锁" class="headerlink" title="增量锁"></a>增量锁</h3><p>对一个元素进行增加或减少的锁， 这种锁背后的动作其实时可以交换的， 比如对一个元素进行增加<br />增量锁会与读锁或写锁冲突，但增量锁和增量锁不冲突<br /><br><br /></p><p><a name="X1mfy"></a></p><h2 id="基于锁的简单调度"><a href="#基于锁的简单调度" class="headerlink" title="基于锁的简单调度"></a>基于锁的简单调度</h2><p>有个前提：</p><ol><li>事务自身不申请锁或释放锁， 都是由调度器来插入或释放锁。</li></ol><p><br />逻辑比较简单</p><ol><li>接收事务产生的请求流， 在所有的操作前后，加入合适的锁动作</li><li>执行请求流<ol><li>事务T 申请的锁出现冲突， 推迟这个动作， 并加入事务T 执行的动作列表</li><li>如果没有冲突， 修改锁表， 将刚授予的锁写进去， 并执行动作</li></ol></li><li>当 T 提交或中止时， 释放等待或持有的锁。 </li><li>如果其他事务释放了锁， 则执行步骤2</li></ol><p><a name="yuSVv"></a></p><h3 id="锁表"><a href="#锁表" class="headerlink" title="锁表"></a>锁表</h3><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574330468516-d3a28a0e-df18-4514-8c0b-f403153bf34a.png#align=left&display=inline&height=314&margin=%5Bobject%20Object%5D&name=image.png&originHeight=628&originWidth=746&size=72038&status=done&style=none&width=373" alt="image.png"><br />在释放锁的时候， 重新调度等待锁的时候， 有一些调度算法</p><ol><li>先来先服务</li><li>共享锁优先</li><li>升级优先</li></ol><p><a name="Jyi3K"></a></p><h3 id="警示锁"><a href="#警示锁" class="headerlink" title="警示锁"></a>警示锁</h3><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574388994823-a794359b-740c-4d6d-a0b6-a3e125bb850f.png#align=left&display=inline&height=189&margin=%5Bobject%20Object%5D&name=image.png&originHeight=378&originWidth=578&size=31296&status=done&style=none&width=289" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574389183359-a5974879-ca15-40a2-b37d-a5213678c1b5.png#align=left&display=inline&height=150&margin=%5Bobject%20Object%5D&name=image.png&originHeight=300&originWidth=532&size=17467&status=done&style=none&width=266" alt="image.png"><br /><br><br />警示锁在普通锁（读写锁&#x2F;更新锁）前加前缀， </p><ol><li>锁从根开始向下扫描</li><li>如果就是要锁当前节点， 就对当前节点直接上锁并结束遍历。</li><li>如果锁的对象是当前节点的子元素， 则当前节点进行警示锁， 对子元素进行锁。</li></ol><p> </p><p><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574389787062-6e4d7be7-9384-46b9-9a25-f38132932ebc.png#align=left&display=inline&height=493&margin=%5Bobject%20Object%5D&name=image.png&originHeight=986&originWidth=1428&size=130834&status=done&style=none&width=714" alt="image.png"><br /><br><br />幻象记录<br />在执行一个事务的过程中， 并行插入了一条新的记录， 这条新的记录就像幻象一样存在。 <br />解决的办法，<br />首先这2个事务不是串行化事务， <br />当第一个事务， 对全表进行扫描的时候， 他会获得全表的警示读锁， <br />当第二个事务执行时， 对表进行插入， 他需要获取全表的警示写锁， 这个时候， 就会发生冲突， 需要事务t2 进行等待<br /></p><p><a name="0PYen"></a></p><h3 id="树协议"><a href="#树协议" class="headerlink" title="树协议"></a>树协议</h3><p>在b+ 树中， 对任何一个节点进行访问，都需要对根节点进行警示锁， 这个非常容易发生冲突。<br /></p><ol><li>事务的第一个锁可以在树的任何节点</li><li>事务只有获得父节点的锁后， 才能获得后续的锁</li><li>事务不能对一个已经释放的锁再进行上锁， 即使拥有父节点的锁</li><li>任何时刻可以unlock</li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574391962115-8a9fb9af-6f01-45e4-ad06-04265771ecd0.png#align=left&display=inline&height=378&margin=%5Bobject%20Object%5D&name=image.png&originHeight=756&originWidth=1208&size=64554&status=done&style=none&width=604" alt="image.png"><br />树协议生成的调度关系，基本上都是串行化。 <br /></p><ol><li>如果2个事务都对几个元素进行锁， 则这些元素的锁顺序必须相同</li></ol><br /><p><a name="86xbH"></a></p><h2 id="时间戳"><a href="#时间戳" class="headerlink" title="时间戳"></a>时间戳</h2><ol><li>为每个事务分配一个时间戳， 事务的时间戳确保调度等价于实际事务的调度。</li><li>有效确认。 当提交一个事务的时候， 检查事务和数据库元素的时间戳， 这一过程称为有效性确认。</li></ol><p> </p><p><br />时间戳需要解决2个问题</p><ol><li>过晚的读， 当读一个数据元素时， 发现读取元素的更新时间戳比自己时间戳还要新， 意味着读一个未来的值。 </li><li>过晚的写， 当理论上应该读取事务t 写入的值，但却读取了别的事务的值。</li></ol><p> </p><p><br />基于时间戳的调度</p><ol><li>当接受到一个读事务时<ol><li>如果ts(T) &lt; wt(X), 则读非法， 中止事务T, 用一个更大的ts 来执行读</li><li>如果ts(T) &gt;&#x3D; wt(X), 可执行<ol><li>如果是已经提交的值， 同意请求， 当ts(T) &gt; rt(X), 设置rt(X) &#x3D; ts(T)</li><li>如果还没有提交的值（脏读）， 推迟T 或中止</li></ol></li></ol></li><li>当接受一个写事务时<ol><li>TS(T) &lt; RT(X), 当前写的时间比读上来的时间早， 回滚</li><li>TS(T) &gt;&#x3D; PT(X) &amp;&amp; TS(t) &gt;&#x3D; wt(x), 执行写， 置wt（x） &#x3D; ts（t）， 置脏位</li><li>如果TS(T) &gt;&#x3D; RT(X) &amp;&amp; TS(T) &lt; WT(X), 可以写入，但后面有个更新的写， 如果是已经提交的写， 当前写必须丢弃， 如果是脏写还没有提交， 可以推迟当前写。</li></ol></li><li>如果调度器执行提交的t的请求，则需要设置所有修改元素提交位， 所有等待事务可以继续</li></ol><p> </p>]]>
    </content>
    <id>https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter7_concurrency/</id>
    <link href="https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter7_concurrency/"/>
    <published>2018-08-11T11:42:57.000Z</published>
    <summary>《数据库实现》第七章并发控制：可串行化调度、冲突图、两阶段锁、优先图判断及基于锁的并发实现，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>《数据库实现》-- 第七章 并发控制</title>
    <updated>2026-06-09T08:46:25.950Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/categories/Database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《数据库实现》-- 第六章 故障恢复","description":"《数据库实现》第六章故障恢复：undo/redo 日志、检查点、事务原语 input/read/write/output 及恢复管理流程","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1573547126916-e03d892a-4638-4959-a778-21e0e653002b.png#align=left&display=inline&height=291&name=image.png&originHeight=291&originWidth=344&size=16798&status=done&width=344","wordCount":1827,"datePublished":"2018-08-10T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.950Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter6_recovering_and_log/"},"url":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter6_recovering_and_log/","inLanguage":"zh-CN","keywords":["Database","数据库实现"],"articleSection":["Database","数据库实现"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"《数据库实现》-- 第六章 故障恢复","item":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter6_recovering_and_log/"}]}</script><h1 id="第六章，-故障恢复"><a href="#第六章，-故障恢复" class="headerlink" title="第六章， 故障恢复"></a>第六章， 故障恢复</h1><p><a name="WccxB"></a></p><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>故障恢复， 依赖很多组件一起协同完成， </p><ol><li>日志管理器， 存储日志</li><li>事物管理器， 保障事物的一致性</li><li>恢复管理器， 备份和恢复的协调者</li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573547126916-e03d892a-4638-4959-a778-21e0e653002b.png#align=left&display=inline&height=291&name=image.png&originHeight=291&originWidth=344&size=16798&status=done&width=344" alt="image.png"></p><p><a name="tzh8X"></a></p><h2 id="事物原语操作"><a href="#事物原语操作" class="headerlink" title="事物原语操作"></a>事物原语操作</h2><p>地址空间</p><ol><li>物理磁盘块空间</li><li>缓冲区的内存地址</li><li>事物的局部地址空间</li></ol><p>一种描述数据在地址空间移动的操作：</p><ol><li>input（x），  将磁盘块含有数据库元素 x  load 到内存，  由缓冲区管理器触发</li><li>read（x）， 将数据库元素x 拷贝到事物的局部变量t， （如果不在内存，则先执行input(x)， 然后赋值给事物局部变量t）。 事物管理器触发</li><li>write（x）， 将事物局部变量写到内存x。 事物管理器触发。</li><li>output（x）， 刷到磁盘。 缓冲区或日志管理器触发。</li></ol><p> </p><p>因为事物的原子性， 有一些设计上的要求</p><ol><li>数据库元素大小不超过一个块 （无论是在内存中操作或磁盘io操作）</li></ol><p>例子：<br />需求<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573562345712-9d430b0f-d224-43b3-8c0b-4b6c6d95363d.png#align=left&display=inline&height=46&name=image.png&originHeight=92&originWidth=260&size=2141&status=done&width=130" alt="image.png"><br />则事务的原语是<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573562385522-dcc1806e-e00a-4b9d-876b-1e1a328d26c5.png#align=left&display=inline&height=200&name=image.png&originHeight=400&originWidth=764&size=34548&status=done&width=382" alt="image.png"><br />如果不再缓冲区， 还需要增加input 指令。 </p><ol><li>在output 之前，发生故障， 都能保持数据库一致性</li><li>在output 之后， 发生故障，也能保持数据库一致性</li><li>在output过程中，发生故障， 会破坏一致性。</li></ol><p> </p><p><a name="vrujx"></a></p><h2 id="undo-日志"><a href="#undo-日志" class="headerlink" title="undo 日志"></a>undo 日志</h2><p>undo 日志， 撤销事务在系统崩溃前还没有完成的动作来修复数据库状态。 </p><p>日志通常是一个三元组&lt;T, X, V&gt;,  事务改变了数据库元素x， x原来的值是v。 </p><ol><li>日志是反映write 动作，不反映output动作 </li><li>日志记录是原始值， 不记录新值。 </li><li>如果事务t 改变了数据库元素x， 日志必须在数据刷到磁盘前，日志写入磁盘</li><li>如果事务提交， commit日志必须在所有元素写到磁盘之后写到磁盘，而且要快。</li></ol><p> </p><p>新的逻辑<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573563603961-70c0fdcf-4e92-4f71-af94-b42d1992a0e6.png#align=left&display=inline&height=266&name=image.png&originHeight=532&originWidth=922&size=43602&status=done&width=461" alt="image.png"></p><p>undo 日志恢复<br />恢复管理器， </p><ol><li>事务分已提交和未提交日志。 <ol><li>恢复时， 按顺序从尾部向头部扫描</li><li>如果发现commit 日志， 则事务已经刷到磁盘，不需要额外恢复操作</li><li>当发现abort 日志记录或发现start，但没有commit日志时， 这个事务必须被回滚。 <ol><li>用日志内部的原始值更新到磁盘， 强制刷到原始值</li><li>如果没有abort 日志， 写入abort 日志</li></ol></li></ol></li><li>恢复过程中的二次崩溃， undo的设计时幂等的，可重入的。</li></ol><p> </p><p>checkpoint<br />定期做checkpoint， 清理老的日志， 避免恢复到一个比较过时的状态。<br />静态checkpoint</p><ol><li>停止新事务</li><li>等待当前活跃的事务， 提交commit或abort</li><li>刷日志到磁盘</li><li>写入日志记录ckpt</li><li>重新接收事务</li></ol><p>动态checkpoint<br />静态checkpoint 需要停止新事务， 这个过程对用户非常不友好。</p><ol><li>写入 日志记录&lt; start ckpt(t1, t2, t3, …. tk)&gt; 其中t是所有活跃的事务</li><li>等待t1， t2， t3， 。。。 tk 提交或中止， 但允许其他事务进行</li><li>当t1， t2， t3， 。。。。 tk 完成后， 写入<end ckpt></li></ol><p>当写入end ckpt 后，可以将上一个start ckpt 之前的记录全部清楚掉</p><p>lsn<br />日志经常是使用旧的日志文件， 会循环使用旧的数据库， 这种情况下， 日志得有一个只增不减的序列号。 <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573565905455-587cdc60-e70c-4e13-9b74-853bf7abec0a.png#align=left&display=inline&height=49&name=image.png&originHeight=98&originWidth=574&size=4639&status=done&width=287" alt="image.png"><br />如果发现下一条记录的lsn 比当前记录的lsn还要小， 说明已经到结尾了。 </p><p>日志通常是一个多层次结构， 顶层文件记录日志文件的构成， 顶层文件的最后一条， 表明可以在什么地方找到最后一条记录。 </p><p>先不考虑循环文件， 如何恢复， 有了checkpoint， 恢复的速度可以大大加快， 恢复可以提交停止。 </p><ol><li>还是按照之前的逻辑进行恢复 </li><li>如果先遇到<end ckpt>， 向前扫描， 当遇到start ckpt 就停止， start ckpt 之前的日志没有必要再恢复了</li><li>如果先遇到start ckpt， 记录这个ckpoint 所涉及的事务列表， 向前扫描， 找到最早出现这些事务的日志的地方， 之前的事务日志就不用再扫描了。</li></ol><p> </p><p><a name="ynbEQ"></a></p><h2 id="redo-日志"><a href="#redo-日志" class="headerlink" title="redo 日志"></a>redo 日志</h2><p>redo 日志忽略未完成的事务， 重复已经提交事务所做的改变。 </p><p>redo 日志是记录commit后的值<br />&lt;t, x, v&gt;, 事务t 为数据库元素x 写入新值v</p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573567479645-29768c9d-8f8a-4c01-a4eb-c6dfa704fc97.png#align=left&display=inline&height=215&name=image.png&originHeight=430&originWidth=928&size=41149&status=done&width=464" alt="image.png"></p><p>整个顺序是不同</p><ol><li>写到内存中redo日志</li><li>写commit日志</li><li>在output磁盘前，刷redo log</li></ol><p>redo log 的恢复<br />从日志首部向下扫描</p><ol><li>如果t是未提交的事务， do nothing</li><li>如果t是已经提交的事务， 写入v</li><li>对没有完成的事务， 写入一个<abort t>到日志</li></ol><p>redo 的checkpoint</p><p>在真实环境中， 数据output 到磁盘会比较慢， 但redo 是按时刷到磁盘。 <br />写checkpoint过程<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573568291162-ac0be8bb-641d-4cdb-9ecf-8332f037aeec.png#align=left&display=inline&height=110&name=image.png&originHeight=220&originWidth=1392&size=34988&status=done&width=696" alt="image.png"></p><p>带检查点redo恢复</p><ol><li>只找最后一个start ckpt提到的事务即可</li><li>如果是end ckpt，则记录这个ckpt 对应的事务列表，  对应start ckpt 前提交的事务已经刷到磁盘， 不需要恢复</li><li>对ckpt 列表的事务 进行恢复</li></ol><p><a name="9Alb3"></a></p><h2 id="undo-redo-一体日志"><a href="#undo-redo-一体日志" class="headerlink" title="undo&#x2F;redo 一体日志"></a>undo&#x2F;redo 一体日志</h2><p>日志格式&lt;T, X, v, w&gt;, v 为修改前的值， 新值为w<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1574078993551-3c59f7d0-7a12-4b97-b133-e15c91a7811f.png#align=left&display=inline&height=218&name=image.png&originHeight=436&originWidth=852&size=42791&status=done&width=426" alt="image.png"></p><p><a name="PjHUN"></a></p><h2 id="MySQL-innodb-undo-和redo-协同工作"><a href="#MySQL-innodb-undo-和redo-协同工作" class="headerlink" title="MySQL innodb undo 和redo 协同工作"></a>MySQL innodb undo 和redo 协同工作</h2><p>假设有A、B两个数据，值分别为1，2.<br />A.事务开始.<br />B.记录A&#x3D;1到undo log.<br />C.修改A&#x3D;3.<br />D.记录A&#x3D;3到redo log.<br />E.记录B&#x3D;2到undo log.<br />F.修改B&#x3D;4.<br />G.记录B&#x3D;4到redo log.<br />H.将redo log写入磁盘。</p><p>I.事务提交</p><p>innodb 有2个优化</p><ol><li>redo log 不是直接写入磁盘， 是先写入redo log buffer， 然后定期flush到磁盘， 因此，当flush到磁盘时， 会把一批commit和abort&#x2F;uncommitted的redo log 都刷到了磁盘， 后续如何处理依赖undo log<ol><li>在恢复的时候， 是结合undo log 一起进行恢复， 因为在恢复的过程中，还有一些log是没有commit的，但已经刷到了磁盘， 当前还是会把他apply，但通过undo log来进行恢复</li></ol><p> </p></li><li>redo log的文件空间是预分配的，为了最优性能，操作是append only</li><li>把undo log当作数据来处理， 因此undo log 也能用buffer来缓存， undo log 也是定期刷磁盘， 而不是每次commit必须刷磁盘</li></ol><p>记录1:&lt;trx1，Undo Loginsert&lt;undo_insert…&gt;&gt;<br />记录2:&lt;trx1，insertA…&gt;<br />记录3:&lt;trx1，Undo Loginsert&lt;undo_update…&gt;&gt;<br />记录4:&lt;trx1，updateB…&gt;<br />记录5:&lt;trx1，Undo Loginsert&lt;undo_delete…&gt;&gt;<br />记录6:&lt;trx1，deleteC…&gt;<br />记录7:&lt;trx1，insertC&gt;<br />记录8:&lt;trx1，updateBtooldvalue&gt;<br />记录9:&lt;trx1，deleteA&gt;</p><p><a name="VYA7s"></a></p><h2 id="针对介质的防护"><a href="#针对介质的防护" class="headerlink" title="针对介质的防护"></a>针对介质的防护</h2><p><a name="xyOa2"></a></p><h3 id="备份"><a href="#备份" class="headerlink" title="备份"></a>备份</h3><ol><li>备份可以使用日志</li><li>备份分2个等级<ol><li>全量转储</li><li>增量转储， 第一次为0级， 而后每一级即为上一次的delta备份</li></ol></li></ol><p>日志方式倾向于redo或者undo&#x2F;redo 一体 日志， 使用undo 日志干不了</p><ol><li>写入<start dump></li><li>根据采用的日志方式（如redo log）执行一个适当的检查点</li><li>执行转储， 拷贝数据到远程节点</li><li>至少dump 第二步中的checkpoint 和checkpoint之前的日志完成dump</li><li>写入日志记录<end dump></li></ol><p>恢复</p><ol><li>先找全量转储</li><li>再找增量转储，</li></ol><p> </p>]]>
    </content>
    <id>https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter6_recovering_and_log/</id>
    <link href="https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter6_recovering_and_log/"/>
    <published>2018-08-10T11:42:57.000Z</published>
    <summary>《数据库实现》第六章故障恢复：undo/redo 日志、检查点、事务原语 input/read/write/output 及恢复管理流程</summary>
    <title>《数据库实现》-- 第六章 故障恢复</title>
    <updated>2026-06-09T08:46:25.950Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/categories/Database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《数据库实现》-- 第五章 查询编译器","description":"《数据库实现》第五章查询优化：语法分析树、语义检查、选择投影下推、distinct 与 group by 等代数优化规则","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1572490236735-d7c44cb1-7e66-403f-99db-d4b6eb5e19d0.png#align=left&display=inline&height=189&name=image.png&originHeight=189&originWidth=661&size=20577&status=done&width=661","wordCount":4817,"datePublished":"2018-08-09T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.949Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter5_query_optimize/"},"url":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter5_query_optimize/","inLanguage":"zh-CN","keywords":["Database","数据库实现"],"articleSection":["Database","数据库实现"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"《数据库实现》-- 第五章 查询编译器","item":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter5_query_optimize/"}]}</script><h1 id="第五章-查询编译器"><a href="#第五章-查询编译器" class="headerlink" title="第五章 查询编译器"></a>第五章 查询编译器</h1><p>每个系统在具体执行上有一些细微的出入， 不过大致的操作差不多<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572490236735-d7c44cb1-7e66-403f-99db-d4b6eb5e19d0.png#align=left&display=inline&height=189&name=image.png&originHeight=189&originWidth=661&size=20577&status=done&width=661" alt="image.png"></p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572490057713-bd65faa1-92a5-4ee8-b9f7-56d120181a8d.png#align=left&display=inline&height=506&name=image.png&originHeight=506&originWidth=747&size=267181&status=done&width=747" alt="image.png"></p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572867866718-abd8130e-d166-4439-b160-7f76a684203c.png#align=left&display=inline&height=351&name=image.png&originHeight=351&originWidth=242&size=16273&status=done&width=242" alt="image.png"></p><p><a name="SVSDu"></a></p><h2 id="语法分析"><a href="#语法分析" class="headerlink" title="语法分析"></a>语法分析</h2><p>将sql 的string 转换为语法分析树， 树的节点必须</p><ol><li>原子， 它是词法成分， 如果关键字select， 关系或属性的名字</li><li>语法类。 用一个&lt;&gt;表示语法类， 比如 <QUERY>表示select-from-where， <CONDDITION>表示条件，常常where之后的表达式。</li></ol><p>查询<br /><QUERY> ::&#x3D; SELECT <SelList> FROM <FromList> WHERE <Condition><br />省略group by， having， order by， distinct， union ， join等操作</p><p><SelList> ::&#x3D; <Attribute>, <SelList><br /><SelistL> ::&#x3D; <Attribute></p><p><FromList> ::&#x3D; <Relation>, <FromList><br /><FromList> ::&#x3D; <Relation></p><p><Condition> ::&#x3D; <Condition> and <Condition> <br /><Condition> ::&#x3D; <Attribute> IN <Query>  <br /><Condition> ::&#x3D; <Attribute>&#x3D; <Attribute>  <br /><Condition> ::&#x3D; <Attribute>LIKE <Pattern>  <br />省略 or、not、exist</p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572869151644-2cd4a648-4810-436f-89a9-bf0c5db7b5fe.png#align=left&display=inline&height=142&name=image.png&originHeight=142&originWidth=269&size=10515&status=done&width=269" alt="image.png"></p><p>语法树为<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572869172997-da869772-b267-4e19-b46f-d28be1837e74.png#align=left&display=inline&height=385&name=image.png&originHeight=385&originWidth=506&size=28167&status=done&width=506" alt="image.png"></p><p><a name="DJGAb"></a></p><h3 id="预处理"><a href="#预处理" class="headerlink" title="预处理"></a>预处理</h3><p>预处理要很多事情</p><ol><li>语义检查<ol><li>检查关系的使用。 from 子句出现的关系是当前关系中的关系或视图</li><li>视图替换为原查询树</li><li>检查与解析属性的使用。 select 子句或where 子句中每个属性必须是当前范围中某个关系的属性。resolve属性到关系中。 也检查二义性，如果某个属性属于2个或多个关系， 则报错</li><li>检查类型。 </li><li><br /></li><li><br /></li></ol></li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572869709913-61ac7b7d-614b-42c9-bb0b-e6990e213c78.png#align=left&display=inline&height=92&name=image.png&originHeight=92&originWidth=311&size=6083&status=done&width=311" alt="image.png"> <img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572869719506-5f5617d6-f50d-4a8c-b1a6-245e974e2b52.png#align=left&display=inline&height=69&name=image.png&originHeight=69&originWidth=211&size=3578&status=done&width=211" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572869735112-3aaf6251-241e-42f1-ac3a-eef68255bd27.png#align=left&display=inline&height=194&name=image.png&originHeight=194&originWidth=580&size=12282&status=done&width=580" alt="image.png"></p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572869759192-daf17fb5-af1e-4e72-847e-68d7eaee5d90.png#align=left&display=inline&height=349&name=image.png&originHeight=349&originWidth=704&size=18294&status=done&width=704" alt="image.png"></p><p><a name="hwU0P"></a></p><h2 id="查询优化"><a href="#查询优化" class="headerlink" title="查询优化"></a>查询优化</h2><p><a name="Nq5Lp"></a></p><h3 id="基于代数定律的查询优化"><a href="#基于代数定律的查询优化" class="headerlink" title="基于代数定律的查询优化"></a>基于代数定律的查询优化</h3><p><a name="L1lzD"></a></p><h4 id="交换律和结合律"><a href="#交换律和结合律" class="headerlink" title="交换律和结合律"></a>交换律和结合律</h4><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572869923451-59e310db-d8a7-4979-9ba0-ac26dd2e8b0e.png#align=left&display=inline&height=112&name=image.png&originHeight=112&originWidth=438&size=10634&status=done&width=438" alt="image.png"><br><a name="owNLR"></a></p><h4 id="选择下推"><a href="#选择下推" class="headerlink" title="选择下推"></a>选择下推</h4><p>逻辑优化里面常常进行“下推选择” 这种优化<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572870183922-147b91f3-ef59-4a90-af8d-4115120d0276.png#align=left&display=inline&height=76&name=image.png&originHeight=76&originWidth=353&size=5563&status=done&width=353" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572870191848-4d5be75e-417f-407e-bdce-9acbb52f22c2.png#align=left&display=inline&height=40&name=image.png&originHeight=40&originWidth=286&size=2559&status=done&width=286" alt="image.png"></p><ol><li>对于并， 下推选择到2个参数， <img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572870307429-abbc80f0-6658-4183-a9de-c7b44fb8fe49.png#align=left&display=inline&height=19&name=image.png&originHeight=19&originWidth=309&size=2243&status=done&width=309" alt="image.png"></li><li>对于差， 下推到第一个， 第二个可选 <img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572870316561-1f74fb64-2779-4980-8c14-b900df5a3fec.png#align=left&display=inline&height=24&name=image.png&originHeight=24&originWidth=251&size=1884&status=done&width=251" alt="image.png">或者<img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572870332616-1bb28e5c-3e3d-4b61-9e86-d4d4afd11dbf.png#align=left&display=inline&height=23&name=image.png&originHeight=23&originWidth=284&size=2155&status=done&width=284" alt="image.png"></li><li>对于其他， 下推到其中一个</li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572870363114-d9de44d1-d83e-4af6-9c05-969d4c0f7f12.png#align=left&display=inline&height=136&name=image.png&originHeight=136&originWidth=273&size=8248&status=done&width=273" alt="image.png"><br />对于积或连接来说， 下推时，主要条件下推到含有属性的关系上，不要下推错了。 </p><p><a name="oE2Ml"></a></p><h4 id="条件下推"><a href="#条件下推" class="headerlink" title="条件下推"></a>条件下推</h4><p>有时需要将选择先上推，然后再下推到所有可能的分支。<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572881772333-1524e417-7bf3-486b-abe0-dacee7b4ef8c.png#align=left&display=inline&height=31&name=image.png&originHeight=62&originWidth=838&size=7748&status=done&width=419" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572881782926-f5f64306-2a5d-4186-80bb-8ee235aa01a4.png#align=left&display=inline&height=62&name=image.png&originHeight=124&originWidth=378&size=6527&status=done&width=189" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572881793028-6c824e6a-44b4-42ee-81d8-210c5f4be49a.png#align=left&display=inline&height=30&name=image.png&originHeight=60&originWidth=536&size=5941&status=done&width=268" alt="image.png"></p><p>执行计划如下<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572881834816-f295c1a6-5464-4e58-a390-8673a67ca167.png#align=left&display=inline&height=198&name=image.png&originHeight=396&originWidth=568&size=17085&status=done&width=284" alt="image.png"></p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572881883299-83155b17-3d0c-4dcd-a9ef-83429e741a23.png#align=left&display=inline&height=208&name=image.png&originHeight=416&originWidth=496&size=17669&status=done&width=248" alt="image.png"><br><a name="YGDn2"></a></p><h4 id="project-下推"><a href="#project-下推" class="headerlink" title="project 下推"></a>project 下推</h4><p>下推project 和下推选择不一样， 保留project 在原处经常发生。 <br />因为， 下推project 一般不改变元组数量，只是减少元组的长度。 </p><p>如果project的属性，即使输入又是输出， 则成为简单的， 比如πa,b,c 这种就是简单的， 如果是π a+b-&gt;c 就是复杂的</p><p>投影优化：<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572917739185-237f3caf-179a-4764-bb48-02475bcf75d7.png#align=left&display=inline&height=34&name=image.png&originHeight=34&originWidth=306&size=2559&status=done&width=306" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572917750339-20f86d7e-25a1-41dd-9b0f-3ec2cb0696bb.png#align=left&display=inline&height=29&name=image.png&originHeight=29&originWidth=336&size=2828&status=done&width=336" alt="image.png"><br />可以优化为：<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572917791454-97e33efb-f98f-4f90-be25-b7012b0d4370.png#align=left&display=inline&height=22&name=image.png&originHeight=22&originWidth=189&size=1604&status=done&width=189" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572917818000-e44f2648-c524-4632-9069-48f0a94056bf.png#align=left&display=inline&height=100&name=image.png&originHeight=100&originWidth=519&size=11716&status=done&width=519" alt="image.png"><br />比如， <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572918182188-5ab57ff8-a4f8-4d99-b376-47d9221de40e.png#align=left&display=inline&height=52&name=image.png&originHeight=52&originWidth=778&size=10698&status=done&width=778" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572918486405-69311027-78ea-415b-97b4-3255ccaacb40.png#align=left&display=inline&height=117&name=image.png&originHeight=117&originWidth=783&size=18500&status=done&width=783" alt="image.png"><br><a name="YV3nR"></a></p><h4 id="连接和积"><a href="#连接和积" class="headerlink" title="连接和积"></a>连接和积</h4><ol><li><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572932915558-e7f71a15-d07d-45ee-a39e-2367bef10e76.png#align=left&display=inline&height=55&name=image.png&originHeight=55&originWidth=221&size=3417&status=done&width=221" alt="image.png">第一个表示基于C的自然连接</li></ol><p><a name="1uMyR"></a></p><h4 id="distinct-优化"><a href="#distinct-优化" class="headerlink" title="distinct 优化"></a>distinct 优化</h4><ol><li>通常下推distinct 会有一些优化效果， 会下推到一些算子中，而不是全部算子</li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572933157582-efffb35f-6449-4ebe-87a3-15ba7058a7e0.png#align=left&display=inline&height=140&name=image.png&originHeight=140&originWidth=263&size=8622&status=done&width=263" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572933186592-b7997e1c-e054-4505-a80f-92d16e725de5.png#align=left&display=inline&height=35&name=image.png&originHeight=35&originWidth=474&size=3986&status=done&width=474" alt="image.png"></p><ol start="2"><li>但一般而言， distinct 不能移入Ub， -b 或project 等运算符</li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572933329825-096803cb-f10f-4175-b787-6bc247edf24e.png#align=left&display=inline&height=70&name=image.png&originHeight=70&originWidth=770&size=13794&status=done&width=770" alt="image.png"></p><ol start="5"><li>有时把distinct 移到一个可以完全消除的位置，因为它作用到一个不含重复的关系上。<ol><li>比如主键</li><li>比如对group by的结果</li><li>集合的一个并&#x2F;交&#x2F;差的结果</li></ol></li></ol><p><a name="yOkoh"></a></p><h4 id="group-by-和aggregate"><a href="#group-by-和aggregate" class="headerlink" title="group by 和aggregate"></a>group by 和aggregate</h4><ol><li>大部分时候取决于aggregate 运算符的细节</li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572934419976-e281c7db-4445-450c-9999-c3d84a28e8c2.png#align=left&display=inline&height=29&name=image.png&originHeight=29&originWidth=201&size=1758&status=done&width=201" alt="image.png"><br />比如像distinct， min&#x2F;max 不受影响， 但sum&#x2F;count&#x2F;avg 就会受影响</p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572937141192-fd0caa21-34e0-4729-87f1-d458b4fc60c5.png#align=left&display=inline&height=125&name=image.png&originHeight=125&originWidth=576&size=14187&status=done&width=576" alt="image.png"><br />原始的plan tree 类似<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572937163406-a8187143-0a2d-41d7-b0ef-bf3015ca6f23.png#align=left&display=inline&height=143&name=image.png&originHeight=143&originWidth=221&size=5687&status=done&width=221" alt="image.png"></p><p>这个里面可以做几个优化</p><ol><li>因为是等值join， 可以转为自然连接</li><li>因为最上层是做group by， 因此，可以引入distinct， distinct 对group by 不影响</li><li>因为最终结果只用了2个属性， 因此，可以下推projection</li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572937301171-3ca64445-b8c5-4510-b7ad-d89ed9474dea.png#align=left&display=inline&height=226&name=image.png&originHeight=226&originWidth=514&size=14503&status=done&width=514" alt="image.png"></p><p><a name="XFIfl"></a></p><h2 id="语法分析树到逻辑查询计划"><a href="#语法分析树到逻辑查询计划" class="headerlink" title="语法分析树到逻辑查询计划"></a>语法分析树到逻辑查询计划</h2><ol><li>用关系代数替换语法树上节点</li><li>逻辑优化</li></ol><p><a name="p5uLU"></a></p><h3 id="subquery"><a href="#subquery" class="headerlink" title="subquery"></a>subquery</h3><p>当condition中包含子查询时，引入运算符中间形式， 它介于语法树和关系代数之间，称为2参数选择 。</p><p>当对子查询进行翻译时， 这些属性后面与外部属性相比较，</p><ol><li>可以将外部该属性上的条件过滤应用到子查询上</li><li>不需要的属性，可以project 消除</li><li>有一些属性可以做distinct</li></ol><p>比如<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572940119010-9d01a510-6e98-4b54-8b08-475c375121c0.png#align=left&display=inline&height=251&name=image.png&originHeight=251&originWidth=250&size=10413&status=done&width=250" alt="image.png"> 可以优化为<img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572940144071-b879d79b-8734-48e7-bfb5-32acb8d4ecfd.png#align=left&display=inline&height=180&name=image.png&originHeight=180&originWidth=182&size=5583&status=done&width=182" alt="image.png"></p><p>复杂subquery 优化<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572940192824-0c430d0b-60e7-4d12-bdba-9df076dba525.png#align=left&display=inline&height=125&name=image.png&originHeight=125&originWidth=268&size=10314&status=done&width=268" alt="image.png"><br />找出 影星平均年龄小于等于40岁的电影的名字和制作年份<br />原始语法树<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572940340105-65ad3bfe-7e9a-4b53-9d66-a1597bdbf823.png#align=left&display=inline&height=276&name=image.png&originHeight=276&originWidth=402&size=12130&status=done&width=402" alt="image.png"></p><p>第一步优化 延迟选择<br />延迟选择， m1.movieTitle &#x3D; m2.movieTile and m1.movieYear&#x3D;m2.movieYear</p><ol><li>为了 支持延迟选择， 需要对子查询的movieTitle和movieYear 进行分组， 引入group by</li><li>引入别名  avg（s.birthday）-&gt;abd</li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572940882703-b2e11ee4-311b-42f2-997b-62cf568d9c43.png#align=left&display=inline&height=280&name=image.png&originHeight=280&originWidth=418&size=14748&status=done&width=418" alt="image.png"><br />还可以做一些优化</p><ol><li>distinct 目前是最后执行</li><li>starts m1 中影星名字被project 消除掉</li><li>starts m1 的 title， year  与starts m2 的title， year 又是等职join</li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572941117385-43fda858-b0d4-4c43-85b1-c2531fb96b6d.png#align=left&display=inline&height=271&name=image.png&originHeight=271&originWidth=273&size=10007&status=done&width=273" alt="image.png"></p><p><a name="T5cXs"></a></p><h3 id="逻辑优化"><a href="#逻辑优化" class="headerlink" title="逻辑优化"></a>逻辑优化</h3><ol><li>条件过滤， 尽可能下推到子树， 当多个条件and时，可以把该条件分解并下推</li><li>投影也可以下推， 或者添加投影。 当有选择时， 下推投影需要小心</li><li>distinct 有时可以消除， 或者移动到合适的地方</li><li>基于积的某些选择可以转换为等值join<ol><li>将θ连接替换为自然连接， 相同名字取等值</li><li>增加project 消除转θ连接为自然连接时的重复属性</li><li>连接的条件必须是可结合的， 存在θ连接不能满足结合律</li><li><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572943601575-146564a1-5c5e-4ee7-9564-c2af8ed8ea61.png#align=left&display=inline&height=220&name=image.png&originHeight=220&originWidth=443&size=11942&status=done&width=443" alt="image.png"></li></ol></li></ol><p><a name="aDJEq"></a></p><h2 id="cost-计算"><a href="#cost-计算" class="headerlink" title="cost 计算"></a>cost 计算</h2><p>当把逻辑计划转为物理执行计划时，可能存在多个不同的物理计划，这个时候需要对物理计划进行cost 评估。然后选择最小代价。 这种评价，称为代价枚举。 </p><ol><li>满足结合律和分配律的运算， 如连接、并、交、分组</li><li>每个运算符可能有多个实现， 比如join 有hash join和nestloop join</li><li>一些算子，比如scan， sort 在逻辑计划中不是显示存在，但在物理计划里面必须实例化</li><li>参数从一个算子传送到下一个算子的方式。 比如可以通过temp disk， 或iterator， 或内存</li></ol><p>第一步，需要准确估计代价。 一般是经过statics 来估计。 </p><p><a name="G4Qrw"></a></p><h3 id="中间表大小的估计"><a href="#中间表大小的估计" class="headerlink" title="中间表大小的估计"></a>中间表大小的估计</h3><p>中间表的大小对cost 有很大的影响。 </p><ol><li>准确估计中间表的cost</li><li>易于计算</li><li>逻辑一致， 中间表大小估计不依赖中间表的计算方式， 比如多个连接的大小不依赖连接顺序。</li></ol><p> </p><p>很难做到3个条件同时满足， </p><ol><li>不能准确估计大小时， 只要误差是稳定的， 大小估计方法能达到最佳物理计划最小计划。</li></ol><p><a name="HE2nr"></a></p><h3 id="project-cost"><a href="#project-cost" class="headerlink" title="project cost"></a>project cost</h3><ol><li>通常project 减少record 大小，但扩展project能产生新成分，可能增加record， 这部分很难计算， 参考后续的例子。</li></ol><p> </p><p><a name="x1zk1"></a></p><h3 id="选择-cost"><a href="#选择-cost" class="headerlink" title="选择 cost"></a>选择 cost</h3><p>selectitity，</p><ol><li>通常选择一个属性等于某个值。T（s）&#x3D;T（r）&#x2F;v(r, a), a 是r的一个属性， v（r，a）类似cardinality, 假设V(r, a) &#x3D;50, 则表示大概1&#x2F;50 概率a 为某个值</li><li>非等值比较， 比如s&#x3D;σa&lt;10(r), 常常约定T(s)&#x3D;T(r)&#x2F;3 , 3分之一的满足条件</li><li>不等比较， 这种比较小 T（s）&#x3D;T（r）(V(r, a)-1)&#x2F;v(r, a)</li><li>多条件and 比较， 则selectivity 进行相乘</li></ol><p>例如， 一张表r（a，b， c）， s &#x3D; σ a&#x3D;10 and b &lt; 20, 另T(r) &#x3D; 10000, v(r, a) &#x3D; 50, 则r 大概1&#x2F;50 满足a &#x3D;10， 1&#x2F;3 满足b &lt; 20, s &#x3D;  T(r)&#x2F;(50 * 3), 因此结果大概是67<br />注意， 如果选择条件是s &#x3D; σ a&#x3D;10 and a &gt; 20, 如果按照cost 计算方式应该也是67， 但实际上 应该为0， 如何防止这种错误呢， 在逻辑计划优化时候， 就应该检查条件选择， 会发现选择实际上就是false。 </p><ol start="5"><li>当多条件or时， 比较难计算， 一般是求和，但经常selectivity 选择出来的结果集比实际结果集还要大。 真正计算公式是， 假设r 有n个record， m1 满足条件c1， m2 个满足条件c2， s &#x3D; n（1-（1- m1&#x2F;n）（1-m2&#x2F;n））， 1-m1&#x2F;n 是不满足c1的那部分， 1-m2&#x2F;n 是不满足c2 的那部分， 因此结果是如此</li></ol><p>假设r（a， b）有 T(r) &#x3D; 10000 个元组， s &#x3D; σ a&#x3D;10 or b &lt; 20， 继续v（r）&#x3D;50， 则s &#x3D; 10000 ( 1- (1 - 1&#x2F;50)(1 - 1&#x2F;3))  最后结果为3466</p><p><a name="S323y"></a></p><h3 id="连接cost"><a href="#连接cost" class="headerlink" title="连接cost"></a>连接cost</h3><p>这里只考虑自然连接， 另外增加2个条件, 这里只是算连接的selectivity， 但连接的cost涉及join order和io cost</p><ol><li>等值连接 会转自然连接</li><li>θ连接 可以看作积之后，进行一个条件过滤</li></ol><p>一个初步的计算是：<br />1&#x2F;MAX(V(r, y), v(s, y)), 就是关系r或s中， 谁的y 出现某值得概率更低，就选谁<br />T（r ⋈ s）&#x3D; T（r）T（s）&#x2F;max(v(r, y), v(s, y))</p><p>举例<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573023184638-293be006-8f3e-4165-a2d7-14e8536ea72f.png#align=left&display=inline&height=98&name=image.png&originHeight=98&originWidth=458&size=9883&status=done&width=458" alt="image.png"><br />先r ⋈ s， 然后再⋈ u， （1000 * 2000&#x2F;MAX(20, 50)）* 5000&#x2F;MAX(100, 500) &#x3D; 400000</p><p><a name="tSbDM"></a></p><h3 id="其他运算符的cost"><a href="#其他运算符的cost" class="headerlink" title="其他运算符的cost"></a>其他运算符的cost</h3><p>并<br />selectivity 为 一般为较大者 + 较小者&#x2F;2</p><p>交<br />selectivity 为较小者&#x2F;2</p><p>差 <br />T（r） - T（s）&#x2F;2</p><p>distinct<br />selectivity &#x3D; min（T（r）&#x2F;2, T(r)&#x2F;（v(r, a1) <em>ｖ（ｒ, a2）</em> xxxx)）</p><p>group by &amp; agg<br />计算和distinct 一样</p><p><a name="XmheN"></a></p><h2 id="执行plan-选择"><a href="#执行plan-选择" class="headerlink" title="执行plan 选择"></a>执行plan 选择</h2><p>磁盘io 受下面影响</p><ol><li>物理算子</li><li>中间表大小</li><li>相似运算的排序</li><li>一个物理算子如何传递给下一个物理算子</li></ol><p><a name="aoqKI"></a></p><h4 id="histogram"><a href="#histogram" class="headerlink" title="histogram"></a>histogram</h4><p>histogram 常见有几种</p><ol><li>等宽（singleton），  比如基于 v0 （当前已知最小值） &lt;&#x3D; V &lt; v0+w 一个item， 下一个item 是v0 + w 《&#x3D; v 《 v0 + 2w， 每一个item里面是计数器。 mysql 里面的等宽histogram 是存这个值的频率</li><li>等高（equi-height）， 下界、上界、累积频率以及不同值的个数（Number of Distinct Value，NDV）。 每个item的下界和上届之差是相同的， 是一个等高的item。 </li><li>最频值， 列出高频值和他的出现次数。</li></ol><p> </p><p>基于join可以直接利用histogram 判断是否有重合值。 </p><p>举例：<br />现有最频值的histogram， r(a, b) ⋈ s (b, c), r.b histogram is <br />1: 200, 0 : 150, 5: 100, others : 550. <br />表示有200个1， 150个0， 100个5， 非0、1、5 的有550个。 <br />s.b histogram is<br />0: 100, 1: 80, 2: 70, others : 250</p><p>另外假设v(r, b) &#x3D; 14, v(s, b) &#x3D; 13. </p><p>其中已经匹配了0， 则0的连接数 为 100 * 150 &#x3D; 15000<br />匹配了1， 则1 的连接数为 200 * 80 &#x3D; 16000<br />高频值2， 2的连接数 为(550&#x2F;14 ) * 70 &#x3D; 2750<br />高频值5， 5的连接数为 100 *（２５０／13）&#x3D; 1900<br />其他值 连接数为 （550 * (14 -2)&#x2F;14） * (250 * (13 -2)&#x2F;13)&#x2F;max(14, 13) &#x3D; 7123</p><p>总的结果为 15000 +16000 +２７５０　＋　1900 + 7123 &#x3D; 42000 左右<br />如果按照默认算法 1000 * 500&#x2F;MAX(14, 13) &#x3D; 35174，</p><p>举例： 等宽直方图<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573111793411-d3100094-4f44-459a-a3ff-3bd34e67c72e.png#align=left&display=inline&height=245&name=image.png&originHeight=245&originWidth=217&size=12528&status=done&width=217" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573111801553-56de75cc-3192-4413-9e7f-fe0291d019c4.png#align=left&display=inline&height=135&name=image.png&originHeight=135&originWidth=244&size=7255&status=done&width=244" alt="image.png"></p><p>仅有2行不为0，  10 * 5&#x2F;10 + 5 * 20 &#x2F;10 &#x3D; 15</p><p><a name="MokB3"></a></p><h3 id="减少启发式逻辑计划优化的代价估计"><a href="#减少启发式逻辑计划优化的代价估计" class="headerlink" title="减少启发式逻辑计划优化的代价估计"></a>减少启发式逻辑计划优化的代价估计</h3><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573112514816-afa73a48-ba5d-42f8-b145-698d3fbebca2.png#align=left&display=inline&height=205&name=image.png&originHeight=205&originWidth=256&size=6231&status=done&width=256" alt="image.png"> 是否做下推，需要查看cost <br />连接v（r， a） &#x3D; 50， v（s， b） &#x3D; 200， 则连接时 T（r）* T（s）&#x2F;max（50， 200）<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573112551623-e47379df-438f-474b-9ed8-1a53eef80723.png#align=left&display=inline&height=269&name=image.png&originHeight=269&originWidth=616&size=16044&status=done&width=616" alt="image.png"><br />a 计划cost &#x3D; 5000 + 100 + 50 + 2000 + 1000  &#x3D; 8150 （把根节点排除了）<br />b 计划cost &#x3D; 5000 + 100 + 2000 + 1000  &#x3D; 8100 （把根节点排除了）<br />说明如果连接时， 连接匹配度比较低时， 延迟distinct 收益会更大</p><p><a name="7j2Zh"></a></p><h3 id="枚举物理计划"><a href="#枚举物理计划" class="headerlink" title="枚举物理计划"></a>枚举物理计划</h3><ol><li>穷尽发<ol><li>自顶向下， 对于根节点的运算的每个可能的实现，计算每种组合的代价</li><li>自底向上， 对于逻辑查询树的每个子表达式，计算这个子表达式的所有可能的实现。</li></ol></li></ol><p>启发式规则：</p><ol><li>等值过滤时， 索引一般效率会更高</li><li>join时，连接上有索引时， 则采用索引连接</li><li>join时， sort-merge join 比hash join 更好。 </li><li>3个或多个关系 并&#x2F;交时， 先对小的关系进行操作。</li></ol><p> </p><p>搜索engine</p><p>枚举需要有时间限制， 比如当前找到一个计划，这个计划耗时c， 则最多搜索c的时间即可。  另外很多db， 直接有一些设置搜索时间限制，在这个限制中， 选取最优计划即可。 </p><p>爬山法： 通过结合律和交换律， 对现有一个算子进行小改进， 直到找到目前不能再优化为止。 </p><p>动态规划， 自底向上有明显简化， 计算大的表达式时，只考虑每个子表达式的最佳计划。 不保证整体最优，但可以获得较优，并可能最优。</p><p>system-r （selinger） 风格， 利用子表达式的某个特性来争取整体最优而不是局部最优。这种是在动态规划的基础上进行改进。 先记录每个子表达式的最优计划。 但同时记录 具有较高代价但产生的结果对较高层收益带来更多。比如，特别关注一些算子， sort、group by、join</p><p><a name="RrQSc"></a></p><h3 id="join-reorder"><a href="#join-reorder" class="headerlink" title="join reorder"></a>join reorder</h3><p>可能最重要的连接是一趟连接， 小表（左参数）读入内存，建立hash table， 这个过程称为build table， 大表（右参数）读入进行连接，称为probe table。</p><ol><li>因为参数导致一些不同的算法， 1. 嵌套循环连接， 左参数是外层循环； 2. 索引关系， 习惯右参数带索引。</li></ol><p>  </p><p>连接数量， N!<br />如果4张表进行连接,  则有4！ &#x3D; 24 中表排序方式。 </p><p><a name="TvkUt"></a></p><h3 id="左深树、右深树"><a href="#左深树、右深树" class="headerlink" title="左深树、右深树"></a>左深树、右深树</h3><p>右子女都是叶子的称为左深树， 类似左子女都是叶子的称为右深树。  如果既不是左深树，又不是右深树， 则称为bushy tree。</p><p>左深树有一些优点</p><ol><li>将搜索空间缩小， 只需要管join order 即可， 不需要care join的位置</li><li>join 算法比较好选择，  选择一趟连接（hash join）或嵌套循环算法（nest loop join）</li><li>当一个线程进行计算时（mysql），bushy tree发挥不了并行的优势</li><li>比较容易结合动态规划来做。</li></ol><p>  </p><p>如果不是左深树， 则因为join 的位置， 需要额外倍数<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573119410046-85122155-119a-416a-9bb3-3b4c9cfb342a.png#align=left&display=inline&height=77&name=image.png&originHeight=77&originWidth=269&size=3422&status=done&width=269" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573119417457-0bcfb551-c874-429b-8a59-475780e49736.png#align=left&display=inline&height=73&name=image.png&originHeight=73&originWidth=312&size=4129&status=done&width=312" alt="image.png"></p><p>因此，如果考虑所有树的组合， 假设有6张表join， 42 *６！　＝　３０２４０　种树。 </p><ol><li>如果用一趟连接， 选择小表在左边， 对内存消耗容易小</li><li>如果选择嵌套循环算法， 外层循环在左边，减少中间表的建立</li></ol><p><a name="eYAPF"></a></p><h3 id="通过动态规划来选择连接顺序和分组"><a href="#通过动态规划来选择连接顺序和分组" class="headerlink" title="通过动态规划来选择连接顺序和分组"></a>通过动态规划来选择连接顺序和分组</h3><p>不断迭代从局部最优进化到全局最优</p><ul><li>基础， 先选取一个最优的最小集， 根据启发式规则， 选择最小的2张表， 小表为左， 其次小为右</li><li>迭代， 建立大小是3张表join的所有子集，选择最优， 然后扩大候选人，做4张表的最优集，直到全部表</li></ul><p> </p><p>Selinger 风格优化（搜索引擎）， 不仅保持一个代价， 而是利用某些特征，在动态规划的基础上保留几个代价，后面会对这些计划都做一个迭代， 然后再进行cost estimate， 再选择一些，再迭代， 最终选择最优的一个， 搜索空间会指数级增长。 </p><p>贪婪算法， 贪婪算法， 容易让搜索空间指数级增长，导致搜索十分耗时，难以承受。 动态规划或者分支限制， 是一种方式加快搜索方式。 另外常用的是启发式规则， 一旦使用启发式规则，就做了一些限制， 比如使用左深树的贪婪算法。 <br />基于左深树的贪婪算法</p><p><a name="cUVid"></a></p><h2 id="物理计划"><a href="#物理计划" class="headerlink" title="物理计划"></a>物理计划</h2><p><a name="S7V4P"></a></p><h3 id="选择算子"><a href="#选择算子" class="headerlink" title="选择算子"></a>选择算子</h3><p>选择算子需要考虑， 尤其是有多个条件时</p><ol><li>有一个索引</li><li>与选择项之一的一个常量相比较</li></ol><p>当有多个条件与时， </p><ol><li>先选中带索引的</li><li>然后进行过滤器操作</li></ol><p>还有一种可能是</p><ol><li>直接全表扫描， 直接进行过滤器操作</li></ol><p>不过任何时候，还是建议做cost evalutate<br />比如 σ x&#x3D;1 AND y&#x3D;2 and z &lt; 5(r), r 在x、y、z 上都有索引，但只有z是聚集的， 其中T(R) &#x3D; 5000, B(R) &#x3D;200, V(R, x) &#x3D;100， V(R, y) &#x3D; 500</p><ol><li>全表扫描， 代价为B(R), 200次磁盘io</li><li>x 索引， T(R)&#x2F; 100 &#x3D; 50 次io， </li><li>y 索引， T(R)&#x2F;V(R, y) &#x3D; 10次io</li><li>z 索引， B(R)&#x2F;3 &#x3D; 67 次io</li></ol><p>整个计算下来，使用y 索引效率会最高。 </p><p><a name="woVFB"></a></p><h3 id="join"><a href="#join" class="headerlink" title="join"></a>join</h3><ol><li>一趟连接 – hash join， 会期望内存足够， 二趟连接（嵌套循环 – nestloop join），如果可以分配给左参数一些缓冲区， 可以分片。 </li><li>当表已经排序了， 可以考虑sort-merge join</li><li>当多张表 基于一个属性进行join时， 前面的表join 后， 起结果是排序的， 可以考虑sort merge join</li><li>如果左表比较小， 右表在join 属性上有索引， 可以考虑索引连接</li><li>不能考虑nestloop join或sort merge join时， 可以考虑hash join。 扫描的次数取决于较小的参数。</li></ol><p><a name="jhohl"></a></p><h3 id="pipeline-和materize"><a href="#pipeline-和materize" class="headerlink" title="pipeline 和materize"></a>pipeline 和materize</h3><p>pipeline 有更好的性能，更容易进行并行，节省磁盘io，但对内存的消耗更大， 需要共享内存。<br />materize 将结果物化到磁盘上， 消耗更多的磁盘io。 </p><p>一元操作很适合流水线， 比如选择和投影， 流水线时，非常容易调用getnext 从而进行下一层的迭代。<br />二元流水线， 比如join<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1573441593414-53a6e3a1-ab26-493c-972d-e1951e8c3e5a.png#align=left&display=inline&height=189&name=image.png&originHeight=189&originWidth=324&size=10307&status=done&width=324" alt="image.png"><br />假设， </p><ol><li>r 5000个块， s、u 各10000个块 </li><li>共有101 个缓冲块</li><li>中间结果使用k个块</li></ol><p>这种情况采用二趟hash 连接（hybrid hash join）， </p><p>因为有101 个缓冲块， 整除2 （因为有下一级流水，所以一个build hash table， 一个output hash table， 如果没有下一级流水， 则结果直接output 到网络）再向下取整， 则有50个块， 则按照 一个桶50个块来设计 hybrid hash join</p><p>一个桶50块， 则表r 切分100块， 每个桶50块， 从s 进行读入需要消耗一个缓冲块， 下一级流水线从u进行读入需要消耗一个内存块， 第一层join， build hash table消耗50块， 则第一层的output 只能为49块。 当第一层join的49块缓冲块被填满时， 会驱动进行下一层的join。如果一个桶做完了join， 第一层的output hash table依旧没有满，则load 第二个桶</p><ol><li>当中间表 块小于等于 49时， 则第二级的join， 直接使用内存hash join，全表load。 总io 次数<ol><li> （5000 + （5000 -50）） + （ 10000 + （10000 - 50）） +10000  &#x3D; 40000 -100</li></ol></li><li>当中间表的块 》49时， 第二层join 已经不适合nestloop join， 期待hybrid hash join<ol><li>（5000 + （5000 -50）） + （ 10000 + （10000 - 50））</li><li>（（k -49）* 2（读写） +４９）　＋　（１００００　 +10000 -49）</li><li>合计50000 + 2 *ｋ　－　２００ 左右</li></ol></li></ol><p><a name="kznNS"></a></p><h2 id="物理算子"><a href="#物理算子" class="headerlink" title="物理算子"></a>物理算子</h2><p>逻辑树叶子的物理算子</p><ol><li>Tablescan(R)</li><li>SortScan(R)</li><li>IndexScan（R, C）, 一种是全索引扫描， 一种类似过滤条件快速定位（比如利用B+ 树等）</li></ol><p>选择的物理算子</p><ol><li>Filter(C)</li></ol><p>排序的物理算子</p><ol><li>SortScan（r， l）</li><li>在连接或分组 运算中， 常常使用sort</li><li>可以使用一个物理算子sort 对没有排序的关系进行排序</li><li>order by 产生的算子， 往往在物理计划的顶端</li></ol><p>其他算子</p><ol><li>算法基于的策略， 基于排序， 散列或索引</li><li>用于遍历，需要考虑是使用一趟遍历算子， 两趟遍历， 还是三趟遍历算子</li><li>考虑内存消耗量</li></ol><p><a name="3TZPE"></a></p><h3 id="物理算子的排序"><a href="#物理算子的排序" class="headerlink" title="物理算子的排序"></a>物理算子的排序</h3><p>就是执行的顺序，</p><ol><li>有可能是使用迭代器流水线进行操作</li><li>也有可能使用不同节点交替执行 （比如bushy tree）</li><li>如果涉及中间表或spill或稍微再检索 执行物化</li></ol><p>规则：</p><ol><li>在物化处拆解子树， 子树可以一次一个被执行</li><li>一般逻辑， 从下至上， 从左到右</li><li>在子树执行过程中， 使用迭代器pipeline 进行执行</li></ol>]]>
    </content>
    <id>https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter5_query_optimize/</id>
    <link href="https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter5_query_optimize/"/>
    <published>2018-08-09T11:42:57.000Z</published>
    <summary>《数据库实现》第五章查询优化：语法分析树、语义检查、选择投影下推、distinct 与 group by 等代数优化规则</summary>
    <title>《数据库实现》-- 第五章 查询编译器</title>
    <updated>2026-06-09T08:46:25.949Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/categories/Database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《数据库实现》-- 第四章 查询执行","description":"《数据库实现》第四章查询执行：关系代数算子、扫描方式、迭代器模型、嵌套循环连接及操作代价分析，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1572490236735-d7c44cb1-7e66-403f-99db-d4b6eb5e19d0.png#align=left&display=inline&height=189&name=image.png&originHeight=189&originWidth=661&size=20577&status=done&width=661","wordCount":1448,"datePublished":"2018-08-08T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.948Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter4_query_execution/"},"url":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter4_query_execution/","inLanguage":"zh-CN","keywords":["Database","数据库实现"],"articleSection":["Database","数据库实现"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"《数据库实现》-- 第四章 查询执行","item":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter4_query_execution/"}]}</script><h1 id="第四章-查询执行"><a href="#第四章-查询执行" class="headerlink" title="第四章 查询执行"></a>第四章 查询执行</h1><p>每个系统在具体执行上有一些细微的出入， 不过大致的操作差不多<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572490236735-d7c44cb1-7e66-403f-99db-d4b6eb5e19d0.png#align=left&display=inline&height=189&name=image.png&originHeight=189&originWidth=661&size=20577&status=done&width=661" alt="image.png"></p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572490057713-bd65faa1-92a5-4ee8-b9f7-56d120181a8d.png#align=left&display=inline&height=506&name=image.png&originHeight=506&originWidth=747&size=267181&status=done&width=747" alt="image.png"></p><p><a name="63eMQ"></a></p><h2 id="操作符介绍"><a href="#操作符介绍" class="headerlink" title="操作符介绍"></a>操作符介绍</h2><p><a name="8BAGP"></a></p><h4 id="操作符"><a href="#操作符" class="headerlink" title="操作符"></a>操作符</h4><table><thead><tr><th>符号</th><th>使用例子</th><th>解释</th></tr></thead><tbody><tr><td>select: σ</td><td></td><td>选择，类似于SQL中的where。注意，和SQL中的select不一样。</td></tr><tr><td>project: Π</td><td></td><td>投影，类似于SQL中的select</td></tr><tr><td>rename: ρ</td><td></td><td>重命名，类似于SQL中的as</td></tr><tr><td>assignment：←</td><td></td><td>赋值。</td></tr><tr><td>union: ∪</td><td></td><td>集合并，类似于SQL中的union</td></tr><tr><td>set difference: –</td><td></td><td>集合差，sql中except</td></tr><tr><td>intersection: ∩</td><td></td><td>集合交，SQL 中intersect&#x2F;intersect all</td></tr><tr><td>Cartesian product: x</td><td></td><td>笛卡尔积，类似于SQL中不带on条件的inner join</td></tr><tr><td>natural join: ⋈</td><td></td><td>自然连接，类似于SQL中的inner join， join… on..,  join … using…, natural join</td></tr><tr><td>distinct: δ</td><td></td><td></td></tr><tr><td>group by 分组γ</td><td></td><td></td></tr><tr><td>sort Γ</td><td></td><td></td></tr></tbody></table><p><a name="di3nZ"></a></p><h4 id="扫描"><a href="#扫描" class="headerlink" title="扫描"></a>扫描</h4><ul><li>全表扫描</li><li>索引扫描</li></ul><p><a name="PeO11"></a></p><h4 id="衡量代价的参数"><a href="#衡量代价的参数" class="headerlink" title="衡量代价的参数"></a>衡量代价的参数</h4><ol><li>需要一个参数来表达操作符使用内存的大小。 </li><li>磁盘io代价<ol><li>用B&#x2F;T&#x2F;V 来参数化一次io， B 表示读取块的数目(顺序扫描)， T表示记录数（随机扫描）， V 表示cardinality</li></ol></li></ol><p><a name="QwyXD"></a></p><h4 id="迭代器"><a href="#迭代器" class="headerlink" title="迭代器"></a>迭代器</h4><p>迭代器</p><ol><li>open</li><li>getnext</li><li>close</li></ol><p>举例： tablescan的迭代器<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572831969739-3602b8bc-66d5-4b44-b985-374d47254383.png#align=left&display=inline&height=454&name=image.png&originHeight=454&originWidth=330&size=23303&status=done&width=330" alt="image.png"><br />举例 r U s <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572832081120-cff7ccec-5996-4c18-80fe-935788009f21.png#align=left&display=inline&height=543&name=image.png&originHeight=543&originWidth=662&size=35518&status=done&width=662" alt="image.png"></p><p><a name="86GAx"></a></p><h4 id="操作符算法"><a href="#操作符算法" class="headerlink" title="操作符算法"></a>操作符算法</h4><p>算法目的是 将逻辑计划转变维物理计划。<br />算法难度</p><ol><li>1次性全部load 到内存</li><li>2次读取磁盘， 第一次读取出来，做某种操作，写回磁盘， 下一次可以一部分一部分的读取到内存</li><li>多次读取</li></ol><p>操作对象</p><ol><li>一元操作， 一次一个record</li><li>整个关系，  但是一元操作</li><li>整个关系， 2元操作， 比如并&#x2F;交&#x2F;差&#x2F;连接&#x2F;积</li></ol><p>操作代价</p><ol><li>对于像select&#x2F;project 这种都是一元操作， 只要满足cache 能完整容纳一行即可， 操作的代价，如果数据是聚集的就是B， 如果是离散的，就是T。 </li><li>对于像distinct、group by 操作，都是在一个关系上的一元操作。<ol><li>可以使用hash table或b 树来存储key， 当数据量超过内存时， 会出现overload， 容易颠簸</li></ol></li><li>分组， 分组常常伴随聚集计算。 可以每个组用一个record来计算， 但必须扫描全部分组后，才能完整输出。 </li><li>二元操作，比如像并、交、差、积和连接。 常常将较小的放到内存中， 较大的逐行扫描。</li></ol><p><a name="FbURF"></a></p><h3 id="嵌套循环连接"><a href="#嵌套循环连接" class="headerlink" title="嵌套循环连接"></a>嵌套循环连接</h3><p>2个关系中， 一个关系只需读取一次， 而另外一个关系需要循环读取多次。 <br />关系r 自然连接 关系s时， 有2种改进</p><ol><li>查找r上匹配的record时，可以使用r上索引， 前提是连接键含有索引</li><li>执行内层循环时， 可以用缓存减少io。 （memory hash join和 grace hash join和hybrid hash join）</li></ol><p>nestloop join的算法<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572850512403-6d577b48-b248-4a90-ad28-9ebedc2f4672.png#align=left&display=inline&height=573&name=image.png&originHeight=573&originWidth=485&size=31972&status=done&width=485" alt="image.png"><br />r 通常是大表， 带索引， s是小表， s是驱动表， 又称外表。 r是被驱动表又称内表。 </p><p>做缓存改进， 一次将一个block 读进内存， 然后再对这个block数据进行遍历<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572851288514-7f1b7af5-d471-4847-95b3-a01a05424e62.png#align=left&display=inline&height=305&name=image.png&originHeight=305&originWidth=489&size=25554&status=done&width=489" alt="image.png"></p><p><a name="eA7ju"></a></p><h3 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h3><p>排序常常需要2趟循环。 <br><a name="gFc5h"></a></p><h4 id="两阶段多路归并排序"><a href="#两阶段多路归并排序" class="headerlink" title="两阶段多路归并排序"></a>两阶段多路归并排序</h4><p>假设现在是1千万个record， 一个record 100个字节， 可进行merge sort 的内存为10MB<br />第一阶段， 从文件中一次读取10MB， 然后在内存中，对他进行排序， 将这次排序结果存到文件中， 依次完成遍历1千万个record， 这样大概100个文件。 <br />第二阶段， </p><ol><li>从每个文件读取 [（10MB）&#x2F;（(100＋1)* 100）] （真正在操作中是10M &#x3D; 100 * 100 * x + 100 * y, x 为预读记录数， y为输出记录数， x不一定等于y， 不过100 * x 和100 * y 都应该是页大小的整数倍）， 大概每个文件读取90个record， 其中100个为input block， 1个为output block</li><li>从这100个cache block 中挑选最小的90 个record 存到 一个output block中</li><li>当output block 满了， 刷到磁盘，然后清空output</li><li>当某个排序子文件的input block 的记录被使用完， 读取下一个block， 直到本文件全部读取结束。</li></ol><p> </p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572857322053-fd7c54e4-1824-4384-9a42-a732f2c53294.png#align=left&display=inline&height=384&name=image.png&originHeight=384&originWidth=358&size=20388&status=done&width=358" alt="image.png"><br />磁盘io 代价，基本为3次文将读写</p><p>归并排序算法上，还可以进行</p><ol><li>distinct</li><li>aggregate， 排序算法可以被用到group by的操作中。 </li><li>表的二元操作， 并</li><li>表的二元操作， 交、 差</li></ol><p><a name="OdcBT"></a></p><h4 id="基于排序的简单merge-sort-join"><a href="#基于排序的简单merge-sort-join" class="headerlink" title="基于排序的简单merge-sort join"></a>基于排序的简单merge-sort join</h4><p>R(X, Y) 和S(Y, Z)</p><ol><li>用y作为关键字， 进行R 排序</li><li>用y做关键字， 对s 进行排序</li><li>各用一个block， 给r或s 做当前块<ol><li>做join操作， 如果join</li></ol></li><li>输出结果</li></ol><p>整个io次数差不多5 * (r + s)</p><p>基于排序的merge-sort join 增强版本。 可以做一些优化， 排序-归并-连接</p><ol><li>以y 为关键字，分别对r和s 进行一个block 一个block的排序， 则会有很多排序的子表</li><li>将每一个子表的第一块调入缓冲区， </li><li>直接进行join</li><li>将join结果输出</li></ol><p>整个io次数差不多 3 * (r +s)</p><p><a name="qMwUt"></a></p><h3 id="基于hash的2趟算法"><a href="#基于hash的2趟算法" class="headerlink" title="基于hash的2趟算法"></a>基于hash的2趟算法</h3><ol><li>在内存中分了M块， 其中M-1 块用于 hash table的桶， 剩下一块为读取的cache。</li></ol><p> </p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572858804011-921a66a2-17f9-423d-b044-e0f6a2db5f26.png#align=left&display=inline&height=404&name=image.png&originHeight=404&originWidth=376&size=29842&status=done&width=376" alt="image.png"></p><p><a name="2ovvF"></a></p><h4 id="基于hash的distinct算法"><a href="#基于hash的distinct算法" class="headerlink" title="基于hash的distinct算法"></a>基于hash的distinct算法</h4><p>将每个数据hash到每个桶中， 差不多每个桶的大小为B(R)&#x2F;M-1, 如果 这个桶的大小可以被load到内存， 可以用2次hash 进行去重。 <br />磁盘io 大概是3B(R)</p><p><a name="fOi6z"></a></p><h4 id="基于hash的group-by或aggregate"><a href="#基于hash的group-by或aggregate" class="headerlink" title="基于hash的group by或aggregate"></a>基于hash的group by或aggregate</h4><p>hash 分桶后，还是需要依次处理</p><p><a name="pMsJY"></a></p><h3 id="缓冲区管理"><a href="#缓冲区管理" class="headerlink" title="缓冲区管理"></a>缓冲区管理</h3><p>LRU 算法<br />FIFO 算法<br />时钟算法<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572864890270-b0ff9ccc-79a3-49cd-a13f-f97924a7aed4.png#align=left&display=inline&height=287&name=image.png&originHeight=287&originWidth=241&size=10927&status=done&width=241" alt="image.png"></p><ol><li>每个block 都有一个标志0或1</li><li>当一个块被load到缓冲时，标志改为1， 当一个块被访问过，也被设为1</li><li>当指针旋转一圈后， 会把没有改变的缓冲设为0</li><li>当块要load到缓冲时，选择为0的缓冲区</li></ol>]]>
    </content>
    <id>https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter4_query_execution/</id>
    <link href="https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter4_query_execution/"/>
    <published>2018-08-08T11:42:57.000Z</published>
    <summary>《数据库实现》第四章查询执行：关系代数算子、扫描方式、迭代器模型、嵌套循环连接及操作代价分析，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>《数据库实现》-- 第四章 查询执行</title>
    <updated>2026-06-09T08:46:25.948Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/categories/Database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《数据库实现》-- 第三章 索引","description":"《数据库实现》第三章索引：稠密稀疏多级与辅助索引、B+ 树插入分裂、倒排索引及顺序文件查找结构，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1572341287114-4df4c52a-23b9-4a38-9ef7-44d78f71d1ee.png#align=left&display=inline&height=297&name=image.png&originHeight=594&originWidth=788&size=32896&status=done&width=394","wordCount":2263,"datePublished":"2018-08-07T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.948Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter3_index/"},"url":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter3_index/","inLanguage":"zh-CN","keywords":["Database","数据库实现"],"articleSection":["Database","数据库实现"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"《数据库实现》-- 第三章 索引","item":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter3_index/"}]}</script><h1 id="第三章-索引"><a href="#第三章-索引" class="headerlink" title="第三章 索引"></a>第三章 索引</h1><p><a name="eZZE0"></a></p><h3 id="索引结构基础"><a href="#索引结构基础" class="headerlink" title="索引结构基础"></a>索引结构基础</h3><p>索引的数据文件， 必须建立索引和记录之间的关联， 索引的指针指向的记录必须含索引对应的值。<br />索引如果是稠密的， 则为每个记录设一个索引。<br />当索引是稀疏的， 则需要主索引和辅助索引。 </p><p><a name="nx2IV"></a></p><h4 id="顺序文件"><a href="#顺序文件" class="headerlink" title="顺序文件"></a>顺序文件</h4><p>按照主键进行排序生成的文件。 表中的元祖按照这个次序分布在多个数据块中。<br><a name="J5VZr"></a></p><h4 id="稠密索引"><a href="#稠密索引" class="headerlink" title="稠密索引"></a>稠密索引</h4><p>当索引文件索引块保持键的顺序和数据文件中排序一直。 <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572341287114-4df4c52a-23b9-4a38-9ef7-44d78f71d1ee.png#align=left&display=inline&height=297&name=image.png&originHeight=594&originWidth=788&size=32896&status=done&width=394" alt="image.png"></p><p>这种方式， 因为索引是排序的，可以用二分查找来加速查找， 另外， 因为索引的数据量小，可以完全被load到内存中。 </p><p><a name="09S19"></a></p><h4 id="稀疏索引"><a href="#稀疏索引" class="headerlink" title="稀疏索引"></a>稀疏索引</h4><p>这个里面有一个条件， 数据文件必须是按照查找键进行排序。 <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572341928867-ba844051-9591-4318-ae08-5f646f5890da.png#align=left&display=inline&height=280&name=image.png&originHeight=560&originWidth=520&size=55254&status=done&width=260" alt="image.png"><br><a name="QYBzu"></a></p><h4 id="多级索引"><a href="#多级索引" class="headerlink" title="多级索引"></a>多级索引</h4><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572341982473-89a4f56f-119f-4793-982a-9201c212f6e1.png#align=left&display=inline&height=285&name=image.png&originHeight=570&originWidth=798&size=37364&status=done&width=399" alt="image.png"><br />类似上图， 但多级索引效率依旧比较低，因此更常规的做法是使用b+树。</p><p><a name="LIofG"></a></p><h4 id="辅助索引（非主键索引）"><a href="#辅助索引（非主键索引）" class="headerlink" title="辅助索引（非主键索引）"></a>辅助索引（非主键索引）</h4><p>辅助索引表示索引键的排序和数据文件的排序是不同的， 因此必须是稠密索引， 因此稀疏索引是毫无意义。<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572342210978-ea8de8f2-f3d0-4956-8016-d3b4092ac1a1.png#align=left&display=inline&height=271&name=image.png&originHeight=542&originWidth=536&size=61091&status=done&width=268" alt="image.png"><br />因为索引键非唯一性， 因此，对一个值进行查找时， 可能还是会查找多个数据文件的块。<br />辅助索引一种用途就是做聚集文件， 比如表r 和s， r 中多条记录对于s中一条记录。<br />比如<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572342694455-47491969-c72a-4ca7-985c-84e0759afc5c.png#align=left&display=inline&height=35&name=image.png&originHeight=70&originWidth=812&size=7614&status=done&width=406" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572342707941-4ec35f03-74eb-4065-8aaf-31900fc19f5b.png#align=left&display=inline&height=53&name=image.png&originHeight=106&originWidth=742&size=8558&status=done&width=371" alt="image.png"><br />基于studioname 和pressc# 建立一个聚集文件<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572342740601-d5a5d5cc-85ed-4a11-9c6f-4291096f99b7.png#align=left&display=inline&height=111&name=image.png&originHeight=222&originWidth=978&size=23506&status=done&width=489" alt="image.png"><br><a name="plroa"></a></p><h4 id="辅助索引的间接层"><a href="#辅助索引的间接层" class="headerlink" title="辅助索引的间接层"></a>辅助索引的间接层</h4><p>当辅助索引重复率非常高时， 索引文件的效率比较低，可以引入一个间接索引<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572342873163-2bfbb4cc-779e-410a-a329-54c1b71dcf95.png#align=left&display=inline&height=301&name=image.png&originHeight=602&originWidth=786&size=42560&status=done&width=393" alt="image.png"></p><p><a name="OkAZc"></a></p><h3 id="倒排索引"><a href="#倒排索引" class="headerlink" title="倒排索引"></a>倒排索引</h3><ul><li>一个文档可以被看作是doc 的一个元组</li><li>每个元组类似doc(hascat, hasdog, hascar…), 每个属性表示文档含有该关键字</li><li>关系doc 上为每个属性建立一个辅助索引， 只需为出现该关键字的文档建索引</li><li>不是为每个属性建一个索引，而是把所有的索引合成一个，称为倒排索引。 </li><li><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572348354208-74224698-557e-4e06-8e29-fd6dd15f16ef.png#align=left&display=inline&height=356&name=image.png&originHeight=712&originWidth=860&size=35025&status=done&width=430" alt="image.png"></li></ul><p><a name="Xh7Ji"></a></p><h3 id="b-树"><a href="#b-树" class="headerlink" title="b- 树"></a>b- 树</h3><ul><li>平衡二叉树， 每个块都处在半满和全满之间</li><li>根， 至少被2个节点使用</li><li>叶节点中， 最后一个指针指向下一个叶节点的存储块</li><li>内层节点， 含有n+1个指针指向下一层的块， 摒弃[(n+1)&#x2F;2]  个指针被使用了。 如果含有j个指针，则含有j-1个键， 为k1, k2, k3, …, k(j-1). 第一个指针指向小于k1的块， 第二个指针指向k1《&#x3D;v 《 k2</li><li>所有的键和指针，存放在数据块的头部。 </li><li><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572349592249-5a2cc5ab-3dd9-4a52-bd90-0b6760c76c77.png#align=left&display=inline&height=227&name=image.png&originHeight=454&originWidth=942&size=26866&status=done&width=471" alt="image.png"></li><li>b- 树是数据文件的主键， 索引是稠密的， 数据文件可以不按主键排序</li><li>当数据文件按主键排序，且b+树是稀疏索引， 则叶节点为数据文件每一个块设一个键-指针对</li><li>当数据文件按主键排序，但b+ 树属性非主键。 则索引是稠密的，叶节点为每个属性都设置键-指针对，k指向第一个记录。</li><li>b-树可扩展带重复。</li></ul><p> <br><a name="2XuCA"></a></p><h4 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h4><ul><li>在合适的节点中找空间，如果节点有空间，则放入到该节点。</li><li>如果合适节点没有空间， 将合适节点分裂为2个新节点， 将键分到2个新节点， 2个新节点满足有一半或超过一半的键。</li><li>对于刚才分裂的节点的上一层来说，就相当于在上一层新增节点， 重复上一层的插入操作，直到根节点。</li></ul><p>对图3-13 进行插入40节点操作， 第一步如下<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572360677454-84c4a778-1437-4248-9b92-173bc665af78.png#align=left&display=inline&height=325&name=image.png&originHeight=650&originWidth=956&size=36668&status=done&width=478" alt="image.png"><br />第二步<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572360700165-b43cd7f7-1fd5-4ad5-9e95-3ac787c1fbd0.png#align=left&display=inline&height=321&name=image.png&originHeight=642&originWidth=892&size=36762&status=done&width=446" alt="image.png"><br><a name="PKHMk"></a></p><h4 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h4><ul><li>如果删除键的节点，删除后 键还是超过最小要求，则直接删除， 不做合并操作</li><li> 如果删除键的节点n 的邻居节点m（是他的兄弟节点， 同一个父节点下的其他的相邻节点），可以拆一个节点过来， 则拆解一个节点过来， 并更新他们共同的父节点</li><li>如果n节点的相邻节点都是最小要求， 则n节点和一个相邻节点进行合并。 然后对他们的父节点进行一个删除键操作， 重复上叙过程，直到完成删除动作。</li></ul><p>对图3-13 进行删除7操作<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572361328825-95084164-c9d1-48ed-967a-fa8366ec6f67.png#align=left&display=inline&height=240&name=image.png&originHeight=480&originWidth=912&size=28559&status=done&width=456" alt="image.png"><br />对图3-17 进行删除11的操作<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572361438683-4fb03a7f-e3f7-4cb4-a05d-7d3374a5bcab.png#align=left&display=inline&height=241&name=image.png&originHeight=482&originWidth=860&size=26593&status=done&width=430" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572361643297-59e972a4-1bf6-400f-a29c-815cfcd25f65.png#align=left&display=inline&height=250&name=image.png&originHeight=500&originWidth=1010&size=28646&status=done&width=505" alt="image.png"></p><p><a name="E75Vs"></a></p><h4 id="效率分析"><a href="#效率分析" class="headerlink" title="效率分析"></a>效率分析</h4><p>如果每个节点容纳的键比较多， 则分裂和合并的操作就会比较少。 <br />访问效率， i&#x2F;o效率是树的深度 + 1次（查找） &#x2F;2次（插入或删除）。 </p><p>优化手段</p><ol><li>一些b-树，删除时，并不会立刻做平衡树调整， 它会相信马上就会添加数据让它平衡， 或者大部分树发展比较平衡，节点比较多，删除不会立刻破坏平衡性。</li></ol><p> </p><p><a name="7DGrh"></a></p><h3 id="hash-表"><a href="#hash-表" class="headerlink" title="hash 表"></a>hash 表</h3><p>通过一个hash 函数，将键 hash到一个含有b个桶的hash 表中， hash表可能每个指针，指向一个链表<br />通常算法是hash(k)%b， b 通常是一个素数， 素数更有利于hash后值平均分布。<br />适合冲突比较少的算法， 另外不适合范围查找。</p><p><a name="EPdoV"></a></p><h4 id="动态hash表"><a href="#动态hash表" class="headerlink" title="动态hash表"></a>动态hash表</h4><p>在静态hash表的基础上，增加</p><ul><li>增加一个中间层， 中间层存放指针，中间层是一个指针数组</li><li>指针数组长度是2的幂， 当增长时， 数组长度就翻倍</li></ul><p><a name="5iPKc"></a></p><h5 id="动态hash-表的插入"><a href="#动态hash-表的插入" class="headerlink" title="动态hash 表的插入"></a>动态hash 表的插入</h5><ul><li>插入一个值到目标块中， 如果目标块可存放， 则ok</li><li>当目标块溢出时， 对目标块进行分裂， 取hash值后的第n位做判断位，为0， 保留在老块中，为1放到新块中。将中间层的指针重新指向新块</li><li><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572399656631-04358a29-d3c7-41a5-9899-88ae2faaa303.png#align=left&display=inline&height=214&name=image.png&originHeight=428&originWidth=482&size=39821&status=done&width=241" alt="image.png"></li><li>缺点： 分裂的代价比较重</li></ul><p><a name="oDF60"></a></p><h3 id="linear-hash-table"><a href="#linear-hash-table" class="headerlink" title="linear hash table"></a>linear hash table</h3><p><a name="BLugl"></a></p><h4 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h4><ul><li>p: 平均每个桶能装的元素个数， 固定值</li><li>e： 取hash值的e位来有效</li><li>r： 实际元素个数</li><li>n： 桶个数</li></ul><p>原则：</p><ul><li>r&#x2F;n &lt;&#x3D; p， 不符合时， 增加n</li><li>2^(e -1) &lt;&#x3D; n &lt; 2^e</li></ul><p>hash表中，每个元素为（hash， key， value）<br />hash &#x3D; hash（key） &amp; (2^e - 1)</p><p>插入时：</p><ol><li>当hash 值&lt; n 时， 直接放入hash 对应的桶中</li><li>当hash 值&gt;&#x3D; n 时， hash &#x3D; hash（key）&amp; （2^ (e -1) - 1)， 放入对应的桶中。</li></ol><p>当插入值后， 因为r在增加， 可以会破坏原则：</p><ol><li>r&#x2F;n &lt; &#x3D; p, 增加n</li><li>当n增加后，如果破坏了原则 2^(e -1) &lt;&#x3D; n &lt; 2^e， 则增加e</li><li>当e增加后， 找个空闲的时间，可以rehash一下， 对原来老的一些桶内的数据，从老的桶内挪到新的桶内。</li></ol><p><a name="sSatE"></a></p><h3 id="多维索引"><a href="#多维索引" class="headerlink" title="多维索引"></a>多维索引</h3><p><a name="Jcs4M"></a></p><h4 id="hash-结构"><a href="#hash-结构" class="headerlink" title="hash 结构"></a>hash 结构</h4><p>一种方式是使用网格文件（grid file）， 每一维上把空间分成条状， 按照空间2维进行组队。 <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572484439057-b51270f7-8e34-4d58-85cf-2754a1fdc335.png#align=left&display=inline&height=166&name=image.png&originHeight=166&originWidth=789&size=30981&status=done&width=789" alt="image.png"></p><p>hash 结构的设计， 我觉得场景不多， 实现的效率不高， 不仔细研究了</p><p><a name="BHCqI"></a></p><h4 id="多维树结构"><a href="#多维树结构" class="headerlink" title="多维树结构"></a>多维树结构</h4><ul><li>多键索引</li><li>kd树</li><li>四叉树</li><li>r-树</li></ul><p><a name="rCH9q"></a></p><h5 id="多键索引"><a href="#多键索引" class="headerlink" title="多键索引"></a>多键索引</h5><p>每一层是某一个索引，下一层是另外一个索引， 2个索引相互嵌套。 <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572485722654-56ebc6b5-feae-494f-8c05-e2b06841dcd4.png#align=left&display=inline&height=442&name=image.png&originHeight=442&originWidth=335&size=24091&status=done&width=335" alt="image.png"></p><p><a name="YtJDG"></a></p><h5 id="kd-树"><a href="#kd-树" class="headerlink" title="kd 树"></a>kd 树</h5><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572485861030-b2b43d95-d006-4d25-9415-e715cbbe0572.png#align=left&display=inline&height=319&name=image.png&originHeight=319&originWidth=581&size=25169&status=done&width=581" alt="image.png"><br />每一层都是一个索引， 每个节点都是一个值， 然后以这个值做搜索， 大于的往左边，小于的往右边，这样自动进行切分</p><p><a name="0gqyR"></a></p><h5 id="四叉树"><a href="#四叉树" class="headerlink" title="四叉树"></a>四叉树</h5><p>四叉树是2叉树的一种变种， 每个节点是一个二维的节点， 他的4个子节点代表着4个方向。 <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572486567335-34bd1870-9cf1-4e23-b3b8-9e59b23c1f98.png#align=left&display=inline&height=304&name=image.png&originHeight=304&originWidth=832&size=163035&status=done&width=832" alt="image.png"><br />如果支持3维， 则变成8叉树。 他不是一种平衡树。 </p><p><a name="Jl8by"></a></p><h5 id="R-树"><a href="#R-树" class="headerlink" title="R 树"></a>R 树</h5><p>r树 是一种高维空间索引， 基于B树的衍生树， 是一种平衡树。 是使用非常多的一种多维空间索引<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572487407954-8dc580f2-8cfb-4acb-a75a-ce20d567e391.png#align=left&display=inline&height=456&name=image.png&originHeight=456&originWidth=358&size=78901&status=done&width=358" alt="image.png"><br />每一个节点都是一个小小的rectangle， 他的父节点就是下面几个rectangle组成起来的一个大的rectangle， 相邻的几个小rectangle合起来组成一个大的rectangle。 一个rectangle就是一个记录<br />原则：</p><ol><li>除了根节点， 所有的节点至少m个rectangle， 最多M个rectangle， m&#x3D;M&#x2F;2， m&lt;&#x3D;n&lt; M。</li><li>除了根节点， 他最多m&lt;&#x3D;n&lt;M个指针，指向下一层节点</li><li>所有叶子位于同一层</li><li>叶子节点的记录是最小rectangle， 他的父节点同样是最小rectangle</li><li><br /></li><li>叶子节点结构</li><li><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572488175075-f10d13bf-d9bf-48e6-8821-6c373529d767.png#align=left&display=inline&height=144&name=image.png&originHeight=144&originWidth=467&size=53658&status=done&width=467" alt="image.png"></li></ol><p>搜索的时候， 有一点像地图搜索， 比如，搜索杭州， 找到杭州这个rectangle， 搜索西湖区， 找到西湖区这个rectangle， 一层一层往下分解， 如果跨节点，则进行拆分。  </p><p><a name="jHpwr"></a></p><h3 id="位图索引"><a href="#位图索引" class="headerlink" title="位图索引"></a>位图索引</h3><p>位图索引， 很多时候， 都是表示这个记录在某个位置是否出现， 尤其是在一些readonly的存储上非常有用，常常标识记录是否被删除。 </p><p>举个例子<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572488682854-c8442fe9-3a9d-43a6-8262-1539903aa333.png#align=left&display=inline&height=167&name=image.png&originHeight=167&originWidth=780&size=36354&status=done&width=780" alt="image.png"><br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572488693178-abaa6732-642a-4b96-97d1-415e62e2b7a4.png#align=left&display=inline&height=124&name=image.png&originHeight=124&originWidth=191&size=4356&status=done&width=191" alt="image.png"></p><p>压缩位图<br />可以用很多算法来对位图进行压缩。 <br />一种思路是进行分段长度编码， <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572488807594-9a71c540-5ab8-4701-a24a-977e033119bc.png#align=left&display=inline&height=155&name=image.png&originHeight=155&originWidth=798&size=30348&status=done&width=798" alt="image.png"></p>]]>
    </content>
    <id>https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter3_index/</id>
    <link href="https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter3_index/"/>
    <published>2018-08-07T11:42:57.000Z</published>
    <summary>《数据库实现》第三章索引：稠密稀疏多级与辅助索引、B+ 树插入分裂、倒排索引及顺序文件查找结构，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>《数据库实现》-- 第三章 索引</title>
    <updated>2026-06-09T08:46:25.948Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/categories/Database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《数据库实现》-- 第二章 存储概述","description":"《数据库实现》第二章存储概述：CPU/内存/磁盘层次、RAID 与校验、定长变长记录格式及增删改插入策略，更多细节与示例见正文。","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1572274485630-c07f784f-8968-4c92-af6d-c554afdd7d05.png#align=left&display=inline&height=309&name=image.png&originHeight=618&originWidth=1302&size=63165&status=done&width=651","wordCount":979,"datePublished":"2018-08-06T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.947Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter2_storage/"},"url":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter2_storage/","inLanguage":"zh-CN","keywords":["Database","数据库实现"],"articleSection":["Database","数据库实现"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"《数据库实现》-- 第二章 存储概述","item":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter2_storage/"}]}</script><h1 id="第二章-存储概述"><a href="#第二章-存储概述" class="headerlink" title="第二章 存储概述"></a>第二章 存储概述</h1><p><a name="tdlWX"></a></p><h1 id="存储层次"><a href="#存储层次" class="headerlink" title="存储层次"></a>存储层次</h1><p>cpu cache  几纳秒<br />内存     10纳秒～ 100ns<br />本地磁盘&#x2F;san     10ms    通常是非易失性<br />第三级存储  秒级 – 如光盘&#x2F;磁带等</p><p>cache 和内存之间， 以line 为单位， 通常32个字节<br />磁盘到内存 相互之间数据传输， 通常会划分成块或页  4～64kb</p><p>为了优化性能， line 内的指令能被顺序执行或者需要， 这要cache 命中率高， cpu流水线指令效率高。</p><p><a name="JolSn"></a></p><h2 id="磁盘"><a href="#磁盘" class="headerlink" title="磁盘"></a>磁盘</h2><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572274485630-c07f784f-8968-4c92-af6d-c554afdd7d05.png#align=left&display=inline&height=309&name=image.png&originHeight=618&originWidth=1302&size=63165&status=done&width=651" alt="image.png"><br />存取延迟</p><ol><li>寻道时间</li><li>旋转延迟</li></ol><p>为了提高数据的存取效率， 因此，很多优化手段</p><ol><li>使用上通常会使用电梯算法， 强化顺序读取或写入， 减少额外的寻道时间和旋转延迟。</li><li>磁盘内部， 关联数据放到同一柱面，减少寻道和旋转时间</li><li>并行 – 多个磁盘同时获取数据， 常用raid算法</li><li>数据预取</li></ol><p><a name="hhFip"></a></p><h2 id="故障"><a href="#故障" class="headerlink" title="故障"></a>故障</h2><ol><li>磁盘是有寿命限制， 可出现间歇性故障或介质损耗，最严重是磁盘崩溃</li><li>校验和， 利用额外的附加位来存储扇区的校验和<ol><li>奇偶校验和。 </li><li>crc &#x2F;md5 等其他校验和</li></ol></li><li>稳定存储<ol><li>冗余扇区， 当写一个扇区是，写入正常数据或校验和， 检验校验和是否正确， 如果错误， 写入另一份存储</li><li>raid 算法</li></ol></li><li>平均失效时间 mean time to failure</li><li>raid 算法<ol><li>raid0 无冗余</li><li>raid1 镜像</li><li>raid4</li></ol></li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572275332217-9e4b4d83-5a33-4f55-b8ed-723c602d4cc2.png#align=left&display=inline&height=128&name=image.png&originHeight=256&originWidth=328&size=7818&status=done&width=164" alt="image.png"> </p><ol><li>raid5， 非固定盘的奇偶校验</li><li>raid6， 海明码纠错码<ol><li>2(power k) -1 块盘， k为冗余盘， 2（power k） - k - 1 为数据盘。</li></ol></li></ol><p> </p><br /><p><a name="oh0aO"></a></p><h2 id="数据格式"><a href="#数据格式" class="headerlink" title="数据格式"></a>数据格式</h2><p><a name="trw9r"></a></p><h3 id="定长记录"><a href="#定长记录" class="headerlink" title="定长记录"></a>定长记录</h3><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572276648831-b4914edd-9083-436f-9461-164d6a6ab21d.png#align=left&display=inline&height=78&name=image.png&originHeight=156&originWidth=904&size=11877&status=done&width=452" alt="image.png"><br />物理地址：</p><ol><li>主机地址</li><li>磁盘标识符</li><li>柱面号</li><li>磁道号</li><li>扇区号</li></ol><p>逻辑地址</p><ol><li>通常使用逻辑地址， 然后通过映射表映射逻辑地址道物理地址， 物理地址比较长，而且物理地址可能由于扩容或故障发生一些变更， 而上层直接使用逻辑地址</li><li>一种是， 逻辑地址是offset， 结合物理地址一起使用， 物理地址到块地址</li></ol><p><a name="qVIYJ"></a></p><h3 id="变长字段"><a href="#变长字段" class="headerlink" title="变长字段"></a>变长字段</h3><ol><li>当是变长字段时， 比如varchar时， 我们会在记录头上记录长度， 指向变长字段起始处</li></ol><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572336958538-de6f0241-1f1e-4760-a79b-25883c435455.png#align=left&display=inline&height=173&name=image.png&originHeight=346&originWidth=910&size=23214&status=done&width=455" alt="image.png"></p><ol start="2"><li>当出现重复字段， 如果定长字段f出现次数可变， 在记录头， 放起始地址，</li></ol><p> </p><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572337094137-7596f4de-1611-4f9c-9ce7-158e488e6989.png#align=left&display=inline&height=186&name=image.png&originHeight=372&originWidth=862&size=22345&status=done&width=431" alt="image.png"><br />另外一种方式， 分开存储， <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572339265424-62b1a478-6668-4e1d-9b00-b04b425d5082.png#align=left&display=inline&height=328&name=image.png&originHeight=656&originWidth=942&size=41176&status=done&width=471" alt="image.png"><br />可变格式的记录， 比如xml<br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572339313689-1ca3dee9-2baf-4303-ae75-0ee9a40ec917.png#align=left&display=inline&height=148&name=image.png&originHeight=296&originWidth=910&size=25523&status=done&width=455" alt="image.png"><br />类似kv的记录， key 一个变长记录， value 一个变长记录</p><p>当不能装入一个块时， 通常需要跨块记录， 对于一个跨块记录格式</p><ol><li>头，首先需要标识， 记录是否为一个块</li><li>如果记录跨块， 必须标识，当前块是记录的第几个片段</li><li>记录尾得含有下个块的指针。 </li><li><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572339677146-4f7a8924-b522-46a4-89bf-70e9a8867ec5.png#align=left&display=inline&height=182&name=image.png&originHeight=364&originWidth=936&size=23610&status=done&width=468" alt="image.png"></li></ol><p><a name="HQfSc"></a></p><h3 id="记录的增删改"><a href="#记录的增删改" class="headerlink" title="记录的增删改"></a>记录的增删改</h3><p><a name="hf9ct"></a></p><h4 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h4><p>插入通常会直接修改偏移量来做到， 尤其是当记录是按主键排序时， <br /><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1572340084639-acfb23cc-62f1-4a07-938a-3d0887d93714.png#align=left&display=inline&height=192&name=image.png&originHeight=384&originWidth=1054&size=30045&status=done&width=527" alt="image.png"><br />很多系统都是使用系统自增的id， 保证系统都是只做append操作。</p><p>对于跨块操作， 会留一个指针指向下一个block， </p><p><a name="Qihbg"></a></p><h4 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h4><p>如果可以移动偏移量表， 则可以直接操作偏移量表。<br />如果不能移动偏移量， 需要一个空间列表，标识记录是否有效或否。 <br />一种方式，在记录的某块地方标识这个记录是否有效或已经被删除<br />当删除记录时， 有的时候，需要对这个记录的指针进行处理， 可以将指针指向删除记录地址处的新记录。</p><p><a name="zAUD4"></a></p><h4 id="修改"><a href="#修改" class="headerlink" title="修改"></a>修改</h4><p>对于定长记录，可以直接进行修改<br />对于非定长记录， 一种方式转化为insert和delete 组合。<br />一种方式还是原地修改，但当记录长度变长时， 需要进行跨块处理。</p>]]>
    </content>
    <id>https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter2_storage/</id>
    <link href="https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter2_storage/"/>
    <published>2018-08-06T11:42:57.000Z</published>
    <summary>《数据库实现》第二章存储概述：CPU/内存/磁盘层次、RAID 与校验、定长变长记录格式及增删改插入策略，更多细节与示例见正文。</summary>
    <title>《数据库实现》-- 第二章 存储概述</title>
    <updated>2026-06-09T08:46:25.947Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"数据库","description":"数据库知识库目录：收录《数据库实现》系列阅读笔记，涵盖架构、存储、索引、查询与事务等主题，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://ilongda.com/img/my.jpg","wordCount":1,"datePublished":"2018-08-05T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.951Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/database/index/"},"url":"https://ilongda.com/2018/docs/database/index/","inLanguage":"zh-CN","keywords":["Database"],"articleSection":["Database"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"数据库","item":"https://ilongda.com/2018/docs/database/index/"}]}</script><ul><li><a href="/knowledge/database/database_implementation_reading_notes/">database_implementation_reading_notes</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2018/docs/database/index/</id>
    <link href="https://ilongda.com/2018/docs/database/index/"/>
    <published>2018-08-05T11:42:57.000Z</published>
    <summary>数据库知识库目录：收录《数据库实现》系列阅读笔记，涵盖架构、存储、索引、查询与事务等主题，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>数据库</title>
    <updated>2026-06-09T08:46:25.951Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/categories/Database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《数据库实现》-- 第一章 数据库基本概念","description":"《数据库实现》第一章笔记：DML/DDL 与约束、E-R 模型、关系模型范式及键、元组与属性等基本概念梳理，更多细节与示例见正文。","image":"https://cdn.nlark.com/yuque/0/2018/png/106206/1535874942262-365cd74a-1598-4c9a-a30b-1dca35e3bf98.png#align=left&display=inline&height=301&originHeight=696&originWidth=1728&status=done&width=747","wordCount":2623,"datePublished":"2018-08-05T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.947Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter1_basic_conception/"},"url":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter1_basic_conception/","inLanguage":"zh-CN","keywords":["Database","数据库实现"],"articleSection":["Database","数据库实现"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"《数据库实现》-- 第一章 数据库基本概念","item":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter1_basic_conception/"}]}</script><h1 id="第一章-–-数据库基本概念"><a href="#第一章-–-数据库基本概念" class="headerlink" title="第一章 – 数据库基本概念"></a>第一章 – 数据库基本概念</h1><p><a name="020mxm"></a></p><h2 id="数据库语言"><a href="#数据库语言" class="headerlink" title="数据库语言"></a>数据库语言</h2><p><a name="pg69eh"></a></p><h3 id="DML-data-manipulation-language"><a href="#DML-data-manipulation-language" class="headerlink" title="DML(data manipulation language)"></a>DML(data manipulation language)</h3><p>对数据进行增删改查<br><a name="f4moes"></a></p><h3 id="DDL-data-definition-language"><a href="#DDL-data-definition-language" class="headerlink" title="DDL (data definition language)"></a>DDL (data definition language)</h3><p>数据模式有一些定义来说明， 这些定义由DDL 来表达。ddl 以一些指令为输入， 输出放在数据字典中。<br><a name="ug64ib"></a></p><h4 id="一致性约束"><a href="#一致性约束" class="headerlink" title="一致性约束"></a>一致性约束</h4><ul><li>域约束（domain constraint）：一个所有取值构成的约束。 是完整性约束的基本形式。 整数型就表示只能取值整数。<br /></li><li>参照完整性（referential integrity）： 一个关系中某个属性的值必须在另外一个关系某个属性中出现。其实就是满足相关性。<br /></li><li>断言（assertion）：数据库某个时刻必须满足的某个条件。 域约束和参照完整性是断言的子集。<br /></li><li>授权（authorization）： 读权限， 插入权限，更新权限，删除权限<br /></li></ul><p><a name="0ZpKt"></a></p><h2 id=""><a href="#" class="headerlink" title=""></a></h2><p><a name="Awr53"></a></p><h2 id="模型设计"><a href="#模型设计" class="headerlink" title="模型设计"></a>模型设计</h2><p><a name="4LYW3"></a></p><h3 id="E-R-模型"><a href="#E-R-模型" class="headerlink" title="E-R 模型"></a>E-R 模型</h3><p>模型设计避免2个问题：</p><ol><li>冗余， 这个理念类似 范式概念；比如， 一个课程中， 有老师列， 在老师列中，可以只要老师id或老师姓名， 而不需要把老师的所有信息，比如系，性别 等信息带进去。<br /></li><li>不完整， 不要漏掉一些信息，导致无法建模； 比如整个设计只为 带课程的老师进行设计，但实际上还有未带课程的老师；<br /></li></ol><p><a name="hgqegc"></a></p><h3 id="实体-联系模型"><a href="#实体-联系模型" class="headerlink" title="实体-联系模型"></a>实体-联系模型</h3><ul><li>实体&#x2F;实体集<br /></li><li>联系&#x2F;联系集 : {(e1, e2, e3, … en)| e1∈E1, e2∈E2, ….}<br /></li><li>属性<br /><ul><li>简单&#x2F;复合， 复合表示属性可以拆解或细化更细粒度， 在不同场景下，会对细度有不同的要求<br /></li><li>单值&#x2F;多值， 当多值时，需要{}, 比如一个老师有多个电话号码{phone_number}<br /></li><li>派生， 派生属性不存储，当需要时再计算， 比如一个老师记录了出生日期， 这个时候，可以直接计算出他的生日。<br /></li></ul></li><li>映射基数： mapping cardinality, 基数比率， 一个实体通过一个联系集关联实体的个数<br /><ul><li>1v1： one-to-one<br /></li><li>1vn: one-to-many<br /></li><li>nv1: many-to-one<br /></li><li>nvn: many-to-many<br /></li></ul></li><li>参与约束， 有可能一个实体集全部参与联系集中，也可能部分。<br /></li></ul><p><a name="c232db"></a></p><h2 id="关系模型"><a href="#关系模型" class="headerlink" title="关系模型"></a>关系模型</h2><p><a name="r2qqce"></a></p><h3 id="范式"><a href="#范式" class="headerlink" title="范式"></a>范式</h3><ul><li>第一范式， 属性是原子不能细分的<br /></li><li>BCNF Boyce-Codd Normal Form: 消除利用函数依赖发现的冗余，<br /><ul><li>函数依赖， 对于所有元组t1和t2， 如果t1[a] &#x3D; t2[a], 则t1[b] &#x3D; t2[b]<br /></li></ul></li></ul><p><a name="AsRGy"></a></p><h2 id="-1"><a href="#-1" class="headerlink" title=""></a></h2><p><a name="b7fvz"></a></p><h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><p><a name="5y9nlp"></a></p><h3 id="结构"><a href="#结构" class="headerlink" title="结构"></a>结构</h3><p>关系relation用表table来指代， 元组（tuple）指代行， 属性attribute指代列。对于每个属性，都存在一个取值范围， 它被称为该属性的域domain。 如果域中元素不可再分， 则域是原子的atomic。</p><p><a name="a9slyr"></a></p><h3 id="键"><a href="#键" class="headerlink" title="键"></a>键</h3><ul><li><p>超键(superkey)： 一个或多个属性的集合， 这个集合的组合可以唯一标识一个元组。</p></li><li><p>候选键(candidate key)：最小的超键。 如果id 能唯一表示一个tuple， 那么含id的超集都可以是超键，但候选键表示满足唯一表示的最小子集（即id）。</p></li><li><p>主键(primary key)： 被设计者选中的候选键。 主键很少改动</p></li><li><p>外键（foreign key）：一个关系（r1）中属性集（a1）为另外一个关系（r2）的主键，a1 被称为r2的外键； r1 称为外键依赖的referencing relation， r2 称为外键的referenced relation。</p></li></ul><p><a name="gvl8xn"></a></p><h3 id="索引"><a href="#索引" class="headerlink" title="索引"></a>索引</h3><p>为了高效找到索引属性上给定值的元组，而不是扫描所有的元组，可以对某属性简历索引。 默认主键会创建索引。<br /><code>create index studentid_index on student(ID)</code></p><p><a name="gumpgk"></a></p><h3 id="大对象"><a href="#大对象" class="headerlink" title="大对象"></a>大对象</h3><p>大对象数据类型clob， 二进制大对象 blob。 通常查询语言不会直接获取大对象， 而是获取大对象定位器， 然后利用大对象定位器来一点点读取数据（有点类似文件系统read）</p><p><a name="azu3fn"></a></p><h3 id="自定义类型"><a href="#自定义类型" class="headerlink" title="自定义类型"></a>自定义类型</h3><ul><li><p>distinct type， 本节介绍</p></li><li><p>structured data type</p></li></ul><p><a name="7377xv"></a></p><h4 id="distinct-type"><a href="#distinct-type" class="headerlink" title="distinct type"></a>distinct type</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">create type dollars as numeric(12, 2) final;</span><br><span class="line">create type pounds as numeric(12, 2) final;</span><br><span class="line"></span><br><span class="line">drop type</span><br><span class="line">alert type</span><br><span class="line"></span><br><span class="line">create table department (budget dollars);</span><br></pre></td></tr></table></figure><p>因此不能直接对dollars和pounds 进行加减直接操作，尽管他们都是numeric(12, 2)， 也不可用 dollars + 20， 这个适合需要cast 转到另外一个域<br />cast (department.budget to numeric(12,  2))</p><p>另外域和自定义类型相似但有区别<br />create domain ddollars as numeric(12, 2) constraint dollar_test check (value &gt; 29000.00);</p><ol><li><p>域可以加声明约束， 比如not null 或default 或 constraint， 而自定义类型不可以</p></li><li><p>域不是强类型， 可以一个类型转另外一个类型， 只要它们基本类型相同。</p></li></ol><p><a name="ol8fpq"></a></p><h3 id="关系运算"><a href="#关系运算" class="headerlink" title="关系运算"></a>关系运算</h3><ul><li><p>自然连接， 其实就是inner join</p></li><li><p>笛卡尔积， 2个关系的所有对，不关注属性值匹配</p></li></ul><p><a name="w65gco"></a></p><h3 id="关系代数"><a href="#关系代数" class="headerlink" title="关系代数"></a>关系代数</h3><table><thead><tr><th>符号</th><th>使用例子</th><th>解释</th></tr></thead><tbody><tr><td>select: σ</td><td></td><td>选择，类似于SQL中的where。注意，和SQL中的select不一样。</td></tr><tr><td>project: Π</td><td></td><td>投影，类似于SQL中的select</td></tr><tr><td>rename: ρ</td><td></td><td>重命名，类似于SQL中的as</td></tr><tr><td>assignment：←</td><td></td><td>赋值。</td></tr><tr><td>union: ∪</td><td></td><td>集合并，类似于SQL中的union</td></tr><tr><td>set difference: –</td><td></td><td>集合差，sql中except</td></tr><tr><td>intersection: ∩</td><td></td><td>集合交，SQL 中intersect&#x2F;intersect all</td></tr><tr><td>Cartesian product: x</td><td></td><td>笛卡尔积，类似于SQL中不带on条件的inner join</td></tr><tr><td>natural join: ⋈</td><td></td><td>自然连接，类似于SQL中的inner join， join… on..,  join … using…, natural join</td></tr><tr><td></td><td></td><td></td></tr></tbody></table><p><a name="zquikc"></a></p><h2 id="SQL"><a href="#SQL" class="headerlink" title="SQL"></a>SQL</h2><ul><li><p>类型：</p><ul><li><p>DDL Data definition languation</p><ul><li>视图定义 view definition : 定义视图的命令</li></ul></li><li><p>DML data manipulation languation</p></li></ul></li><li><p>属性：</p><ul><li><p>完整性（integrity）： 完整性约束</p></li><li><p>嵌入式SQL&#x2F;动态sql embeded sql&#x2F;dynamic sql： 如何潜入到其他编程语言中</p></li><li><p>授权</p></li><li></li></ul></li></ul><p><a name="w4q5nt"></a></p><h3 id="外连接"><a href="#外连接" class="headerlink" title="外连接"></a>外连接</h3><ul><li><p>左外连接 left outer join</p></li><li><p>右外连接 right outer join</p></li><li><p>全外连接 full outer join</p></li></ul><p>外连接中 on 和where 是不一样的， on 不会过滤器元组， 但where 会过滤</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">select * from student left outer join takes on true</span><br><span class="line">where student.id = takes.id;</span><br></pre></td></tr></table></figure><p><a name="o49krv"></a></p><h3 id="视图"><a href="#视图" class="headerlink" title="视图"></a>视图</h3><p>为什么需要视图</p><ol><li>处于安全考虑， 不让某些用户看到所有的字段。</li></ol><p><a name="uvtmgo"></a></p><h4 id="视图更新"><a href="#视图更新" class="headerlink" title="视图更新"></a>视图更新</h4><p>一般不允许对视图进行更新操作， 但如果定义视图的ddl 满足：</p><ul><li><p>只从一个table进行from</p></li><li><p>select 只包含属性名，没有额外的表达式，如聚集或distinct</p></li><li><p>任何没有出现在select子句中的属性可以取空值</p></li><li><p>不包含group by 或having</p></li></ul><p><a name="uu80xk"></a></p><h4 id="物化视图"><a href="#物化视图" class="headerlink" title="物化视图"></a>物化视图</h4><p>数据库允许存储视图关系， 如果用于定义视图的实际关系改变， 视图也跟着改变。</p><p><a name="t5dmig"></a></p><h3 id="完整性约束"><a href="#完整性约束" class="headerlink" title="完整性约束"></a>完整性约束</h3><p>alert table xxx add tttt<br />ttt 即为限制 constraint</p><p><a name="q19zci"></a></p><h4 id="单个关系上的约束"><a href="#单个关系上的约束" class="headerlink" title="单个关系上的约束"></a>单个关系上的约束</h4><ul><li><p>not null</p></li><li><p>unique： unique(a1, a2, a3, ….), 只能从属性集中取值， 也可以为null， 除非限制了not null</p></li><li><p>check : check （semester in (‘fall’, “winter”, “spring”, “summer”)）， check (time_slot_id in select time_slot_id from time_slot),  后一个开销可能有点大， 因为更新time_slot 或section 时都需要做检查。</p></li></ul><p><a name="uwvavu"></a></p><h4 id="参照完整性"><a href="#参照完整性" class="headerlink" title="参照完整性"></a>参照完整性</h4><p>一个关系中某个属性的取值必须在另外一个关系的某个属性的取值集内. referential-intergrity constraint&#x2F;subset dependency.</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">create table department</span><br><span class="line">(dept_name varchar(20),</span><br><span class="line">building varchar(15),</span><br><span class="line">budget   numveric(12,2),</span><br><span class="line">primary key (dept_name)</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">create table course</span><br><span class="line">(</span><br><span class="line">course_id varchar(7),</span><br><span class="line">title varchar(50),</span><br><span class="line">dept_name varchar(20),</span><br><span class="line">credits numeric(2, 0)m</span><br><span class="line">primary key (course_id),</span><br><span class="line">foreign key (dep_name) references department.</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>foreign key (dept_name) references department.</p><p>还有一种简单方式  </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">create table course</span><br><span class="line">(</span><br><span class="line">course_id varchar(7),</span><br><span class="line">title varchar(50),</span><br><span class="line">dept_name varchar(20) references department.</span><br><span class="line">credits numeric(2, 0)m</span><br><span class="line">primary key (course_id)</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>course 中dept_name 为参照关系department中dept_name的外键。<br />参照性约束不要求dept_name为主键， 但sql中，如果使用references , 则属性列必须是被参照关系（department）的候选键， 要么是primary key， 要么使用unique 约束。<br />违法完整性约束时， 通常会拒绝导致完整性被破坏的操作（更新操作的事务会被回滚）<br />foreign key 则可以高级操作， 如果被参照关系上（department）上进行删除或修改时， 破坏了约束， 可以采取一些措施来修改参照关系（course）中元组来恢复完整性约束， 而不是简单拒绝操作。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">create table course</span><br><span class="line">(</span><br><span class="line">course_id varchar(7),</span><br><span class="line">title varchar(50),</span><br><span class="line">dept_name varchar(20),</span><br><span class="line">credits numeric(2, 0)m</span><br><span class="line">primary key (course_id),</span><br><span class="line">foreign key (dep_name) references department.</span><br><span class="line">        on delete cascade</span><br><span class="line">        on update cascade,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>延迟约束检查<br />比如 一张表内， name 为主键， spouse（配偶） 是person的一个外键（依赖name）， 加速mary和john 是夫妻， 无论是第一次插入john还是mary， 都会违反完整性约束， 直到2个人都插入时， 才能满足完整性约束。</p><p>通过增加<code>initially deferred</code>加入到约束语句中或者’set constraints xxxx deferred’做为事务的一部分， 在一个事务中， 约束性检查会延迟到事务提交时再进行。</p><p><a name="n4gqkk"></a></p><h4 id="断言"><a href="#断言" class="headerlink" title="断言"></a>断言</h4><p>断言就是一个谓词， 域约束和参照完整性都是断言的子集。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">create assertion credit_earned_constraint check</span><br><span class="line">(</span><br><span class="line">    not exists (</span><br><span class="line">        select id from student</span><br><span class="line">        where tot_cred &lt;&gt; (</span><br><span class="line">                            select sum(credits) from takes natural join course</span><br><span class="line">                            where students.id = takes.id and grade is not null </span><br><span class="line">                            )</span><br><span class="line">    ) </span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><a name="7o40zr"></a></p><h2 id="事务"><a href="#事务" class="headerlink" title="事务"></a>事务</h2><p>acd：</p><ul><li>atomicity : 事务要么成功， 要么失败， 没有中间状态， 成功了就提交了所有修改， 失败了就没有任何修改<br /></li><li>consistency：所有操作符合预期<br /></li><li>isolation： 隔离性， 事务的隔离级别<br /></li><li>durability： 一旦事务提交成功后， 修改的数据就持久化存储下去， 不可逆的修改<br /></li></ul><p>事务将隔离性分为几个级别：</p><ol><li>read uncommitted, 一个事物可以读到另外一个事物还未提交的数据， 隔离级别最低<br /></li><li>read committed， 只有在数据被提交后，他的更新结果才能被别人读取<br /></li><li>repeatable committed， 在一个事物中， 对该数据始终读取都是相同的数据， 无论在这个事物执行的过程中， 其他并行事物是否已经提交或未提交<br /></li><li>serializable， 隔离级别最高，就是串行处理所有事物。<br /></li></ol><p>因为不同的隔离级别，会带来不同的数据读取问题：</p><ol><li>dirty read<br /></li><li>nonrepeatable read<br /></li><li>phantom read<br /></li></ol><p>dirty read：事务1 修改了数据， 但还没有提交， 事务2 读取了事务1 修改的结果， 但随后 事务1 进行了回滚， 因此， 事务2的读取行为，就被称为 脏读。</p><p>nonrepeatable read： 事务1 读取了数据a， 但随后 事务2 修改了数据a 并进行了提交， 随后， 事务1 再次读取数据a ，发现2次数据读取不一致， 这种称为nonrepeatable read。</p><p>phantom read， 事务1 读取了一批数据， 事务2 插入了一条新数据并提交了事务， 事务1 再次进行读取，发现多了这条新数据， 这条新数据就像 幻象一样存在 phantom<br /><img data-src="https://cdn.nlark.com/yuque/0/2018/png/106206/1535874942262-365cd74a-1598-4c9a-a30b-1dca35e3bf98.png#align=left&display=inline&height=301&originHeight=696&originWidth=1728&status=done&width=747" alt="《数据库实现》-- 第一章 数据库基本概念"></p>]]>
    </content>
    <id>https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter1_basic_conception/</id>
    <link href="https://ilongda.com/2018/docs/database/database_implementation_reading_notes/chapter1_basic_conception/"/>
    <published>2018-08-05T11:42:57.000Z</published>
    <summary>《数据库实现》第一章笔记：DML/DDL 与约束、E-R 模型、关系模型范式及键、元组与属性等基本概念梳理，更多细节与示例见正文。</summary>
    <title>《数据库实现》-- 第一章 数据库基本概念</title>
    <updated>2026-06-09T08:46:25.947Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/categories/Database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《数据库实现》-- 第一章 数据库系统架构","description":"《数据库实现》系统架构一章：三层数据抽象、实例与模式、关系/E-R/对象模型及查询处理组件分工，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://cdn.nlark.com/yuque/0/2019/png/106206/1550152009306-96cd768c-1839-4f4b-a536-b972489aca0a.png#align=left&display=inline&height=526&name=image.png&originHeight=526&originWidth=389&size=71313&status=done&width=389","wordCount":1135,"datePublished":"2018-08-05T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.951Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/charpter1_architecture/"},"url":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/charpter1_architecture/","inLanguage":"zh-CN","keywords":["Database","数据库实现"],"articleSection":["Database","数据库实现"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"《数据库实现》-- 第一章 数据库系统架构","item":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/charpter1_architecture/"}]}</script><h1 id="第一章-–-数据库系统架构"><a href="#第一章-–-数据库系统架构" class="headerlink" title="第一章 – 数据库系统架构"></a>第一章 – 数据库系统架构</h1><p><a name="ce87533f"></a></p><h1 id="第一章"><a href="#第一章" class="headerlink" title="第一章"></a>第一章</h1><p><a name="a4d3b02a"></a></p><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p><a name="VdwO4"></a></p><h2 id=""><a href="#" class="headerlink" title=""></a></h2><p>数据库管理系统一般需要：<br />1. 数据定义语言来创建数据库，并指定数据的逻辑结构<br />2. 使用查询语言来进行查询， 使用数据操作语言来进行修改（dml 不改变数据库模式）<br />3. 可对大量数据进行长期保存， 数据具备持久性， 从故障，多种错误中恢复<br />4. 允许高效查询或修改<br />5. 支持多用户，并保证多用户之间独立性。</p><p>信息集成 有2种方式<br />1. 数据仓库， 将数据周期性同步到一个中心数据库<br />2. 使用中间件， 统一提供一个集成的模型，然后在这个模型和各个实际数据库内的实际模型进行转换。 </p><p><a name="nfxgxk"></a></p><h2 id="数据视图"><a href="#数据视图" class="headerlink" title="数据视图"></a>数据视图</h2><p><a name="l6skyf"></a></p><h3 id="数据抽象"><a href="#数据抽象" class="headerlink" title="数据抽象"></a>数据抽象</h3><ul><li>物理层<br /><ul><li>描述数据怎样存储<br /></li></ul></li><li>逻辑层<br /><ul><li>数据库存储什么数据， 以及这些数据存在什么关系。<br /></li><li>可以通过少量的数据结构来描述整个数据库<br /></li></ul></li><li>视图<br /><ul><li>数据库的某个小部分<br /></li><li>屏蔽用户看到一些细节或安全设置<br /></li></ul></li></ul><p><a name="1b4gxr"></a></p><h2 id="实例和模式"><a href="#实例和模式" class="headerlink" title="实例和模式"></a>实例和模式</h2><ul><li>实例<br /><ul><li>某个时刻， 数据库中数据的集合<br /></li></ul></li><li>模式<br /><ul><li>定义：数据库的总体设计<br /></li><li>物理模式： 物理层描述数据库的设计<br /></li><li>逻辑模式：逻辑层描述数据库的设计。 应用通常会依赖逻辑模式<br /></li><li>视图层： 子模式， 描述数据库的不同视图<br /></li></ul></li></ul><p><a name="mn01sr"></a></p><h3 id="数据模型"><a href="#数据模型" class="headerlink" title="数据模型"></a>数据模型</h3><p>描述 数据， 数据联系， 数据语义以及一致性约束的概念工具的集合。</p><ul><li>关系模型（relational model）： 用表的集合来表示数据和数据间的联系<br /></li><li>实体-联系模型（entity-relationship model）：（E-R） 基于现实世界的认识基础上， 世界是由实体的基本对象和对象间的联系构成。<br /></li><li>基于对象的数据模型（object-based data model）： 在E-R 基础上，增加了封装， 方法和对象标识的扩展。 结合了关系模型和E-R 模型。 现代数据库厂商支持 对象–关系数据模型 （objected-relational data model），将关系模型和对象模型结合的数据模型， 在关系模型的基础上，增加了对象特性。<br /></li><li>半结构化数据模型（semistructured data model）： 相同类型的数据项可以含有不同属性集的数据定义。<br /></li></ul><p><a name="NBK6E"></a></p><h2 id="数据库系统架构"><a href="#数据库系统架构" class="headerlink" title="数据库系统架构"></a>数据库系统架构</h2><p><img data-src="https://cdn.nlark.com/yuque/0/2019/png/106206/1550152009306-96cd768c-1839-4f4b-a536-b972489aca0a.png#align=left&display=inline&height=526&name=image.png&originHeight=526&originWidth=389&size=71313&status=done&width=389" alt="image.png"></p><p>————————————————————————————————————<br />proxy<br />parser（DML 查询&#x2F;DDL 解释器）<br />optimizer<br />runtime engine<br />————————————————————————————————————</p><p>授权和完整性管理器<br />事务管理器<br />缓冲管理器<br />文件管理器<br />·································································<br />索引             |    数据字典   |      统计数据<br />数据</p><p><a name="z3bgpm"></a></p><h3 id="查询处理器"><a href="#查询处理器" class="headerlink" title="查询处理器"></a>查询处理器</h3><ul><li>DDL 解释器（interpreter）： 解释DDL, 并产生一些输出到数据字典<br /></li><li>DML compiler: 将DML 翻译成一个物理执行计划<br /></li><li>查询执行引擎： query evaluation engine, 执行执行计划。<br /></li></ul><p><a name="BFpSM"></a></p><h3 id="查询编译器"><a href="#查询编译器" class="headerlink" title="查询编译器"></a>查询编译器</h3><p>通常是</p><ul><li>分parser<ul><li>构建一棵树；</li><li>预处理器， 对查询进行语义检查并进行某些树结构转换，将分析树转化为表示查询计划的代数操作树； </li><li> 查询优化器。</li></ul></li></ul><p> </p><p><a name="q3usxg"></a></p><h3 id="存储器"><a href="#存储器" class="headerlink" title="存储器"></a>存储器</h3><p>负责数据库中数据的存储，检索和更新， 负责与文件管理器进行交互。<br />存储器组件：</p><ul><li>权限及完整性管理器（authorization and integrity manager）<br /></li><li>事务管理器<br /></li><li>文件管理器： 管理磁盘空间的分配，管理用于表示磁盘上数据的数据结构<br /></li><li>缓冲区管理器：<br /></li></ul><p>存储器管理的数据结构：</p><ul><li>数据文件<br /></li><li>数据字典<br /></li><li>索引<br /></li></ul><p><a name="YZo6y"></a></p><h3 id="-1"><a href="#-1" class="headerlink" title=""></a></h3><p><a name="iCOK5"></a></p><h3 id="事务处理器"><a href="#事务处理器" class="headerlink" title="事务处理器"></a>事务处理器</h3><p>分为：<br />1. 并发控制器和调度器<br />    并发控制中， 还含有并发控制管理 也就是调度器， 调度器会禁止执行引擎去访问数据库中被锁定的部分。<br />    死锁监控<br />2. 日志和恢复管理器<br />数据库的每一个变化， 都会使用日志进行跟踪<br />事务管理器包括 并发控制管理器和恢复管理器。<br />事务的原子性和持久性都是由 恢复管理负责的。<br /></p><p><a name="TNvSs"></a></p><h3 id="索引-文件-记录-管理器"><a href="#索引-文件-记录-管理器" class="headerlink" title="索引&#x2F;文件&#x2F;记录 管理器"></a>索引&#x2F;文件&#x2F;记录 管理器</h3><ul><li>视图， 视图是不实际存储， 但需要从实际存储的关系中构建出来一种关系的描述</li></ul><p><a name="XbHWg"></a></p><h3 id="内存和缓冲区管理器"><a href="#内存和缓冲区管理器" class="headerlink" title="内存和缓冲区管理器"></a>内存和缓冲区管理器</h3><p>缓冲区管理器， 将内存分割成缓冲区， 缓冲区是与页面同等大小， 磁盘内容传送<br><a name="emrYp"></a></p><h3 id="存储管理器"><a href="#存储管理器" class="headerlink" title="存储管理器"></a>存储管理器</h3><p>存储内容</p><ol><li>数据</li><li>元数据</li><li>日志</li><li>统计信息</li><li>索引</li></ol>]]>
    </content>
    <id>https://ilongda.com/2018/docs/database/database_implementation_reading_notes/charpter1_architecture/</id>
    <link href="https://ilongda.com/2018/docs/database/database_implementation_reading_notes/charpter1_architecture/"/>
    <published>2018-08-05T11:42:57.000Z</published>
    <summary>《数据库实现》系统架构一章：三层数据抽象、实例与模式、关系/E-R/对象模型及查询处理组件分工，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>《数据库实现》-- 第一章 数据库系统架构</title>
    <updated>2026-06-09T08:46:25.951Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Database" scheme="https://ilongda.com/categories/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/categories/Database/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <category term="Database" scheme="https://ilongda.com/tags/Database/"/>
    <category term="数据库实现" scheme="https://ilongda.com/tags/%E6%95%B0%E6%8D%AE%E5%BA%93%E5%AE%9E%E7%8E%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《数据库实现》","description":"《数据库实现》阅读笔记索引：汇总基本概念、存储、索引、查询执行优化、故障恢复、并发与事务各章链接，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":9,"datePublished":"2018-08-05T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.951Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/index/"},"url":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/index/","inLanguage":"zh-CN","keywords":["Database","数据库实现"],"articleSection":["Database","数据库实现"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Database","item":"https://ilongda.com/categories/Database/"},{"@type":"ListItem","position":3,"name":"《数据库实现》","item":"https://ilongda.com/2018/docs/database/database_implementation_reading_notes/index/"}]}</script><ul><li><a href="/knowledge/database/database_implementation_reading_notes/chapter1_basic_conception.html">chapter1_basic_conception</a></br></li><li><a href="/knowledge/database/database_implementation_reading_notes/chapter2_storage.html">chapter2_storage</a></br></li><li><a href="/knowledge/database/database_implementation_reading_notes/chapter3_index.html">chapter3_index</a></br></li><li><a href="/knowledge/database/database_implementation_reading_notes/chapter4_query_execution.html">chapter4_query_execution</a></br></li><li><a href="/knowledge/database/database_implementation_reading_notes/chapter5_query_optimize.html">chapter5_query_optimize</a></br></li><li><a href="/knowledge/database/database_implementation_reading_notes/chapter6_recovering_and_log.html">chapter6_recovering_and_log</a></br></li><li><a href="/knowledge/database/database_implementation_reading_notes/chapter7_concurrency.html">chapter7_concurrency</a></br></li><li><a href="/knowledge/database/database_implementation_reading_notes/chapter8_transaction.html">chapter8_transaction</a></br></li><li><a href="/knowledge/database/database_implementation_reading_notes/charpter1_architecture.html">charpter1_architecture</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2018/docs/database/database_implementation_reading_notes/index/</id>
    <link href="https://ilongda.com/2018/docs/database/database_implementation_reading_notes/index/"/>
    <published>2018-08-05T11:42:57.000Z</published>
    <summary>《数据库实现》阅读笔记索引：汇总基本概念、存储、索引、查询执行优化、故障恢复、并发与事务各章链接，更多细节与示例见正文。</summary>
    <title>《数据库实现》</title>
    <updated>2026-06-09T08:46:25.951Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="读书笔记" scheme="https://ilongda.com/categories/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <category term="读书笔记" scheme="https://ilongda.com/tags/%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"认知革命读后感","description":"李善友《认知革命》课程笔记：梳理第一性原理、归纳演绎思维、非连续性与科学革命等核心理论与个人理解，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":1753,"datePublished":"2017-12-23T11:07:43.000Z","dateModified":"2026-06-09T08:46:25.938Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2017/knowledgerevolution/"},"url":"https://ilongda.com/2017/knowledgerevolution/","inLanguage":"zh-CN","keywords":["读书笔记"],"articleSection":["读书笔记"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"读书笔记","item":"https://ilongda.com/categories/读书笔记/"},{"@type":"ListItem","position":3,"name":"认知革命读后感","item":"https://ilongda.com/2017/knowledgerevolution/"}]}</script><p>认知革命</p><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>花了半天，快速学习了认知革命的课程，不能保证能理解李善友所表达的所有观点， 只能说按照我现有的知识体系，对他的理论有一个消化和吸收。<br>他有几个核心观点，<br>（1）第一性原理<br>（2）认知世界的方式<br>（3）科学革命<br>（4）非连续性。 </p><h1 id="第一性原理"><a href="#第一性原理" class="headerlink" title="第一性原理"></a>第一性原理</h1><p>第一性原理， 我个人非常赞同这个观点， 其实就是哲学中的现象和本质说， 只不过换了一个角度来演绎这个说法， 另外在李善友的观点中， 第一性原理是需要建立在认知世界方式中演绎归纳法的基础上， 认知世界的方式中演绎法，需要一个假设的前提， 这个前提是一个具备自证清白的共识，这个共识就是第一性原理。 我个人观点，这是哲学中现象和本质 这一理论的另外一个说法而已。</p><p>在我们今天人类的发展历史上， 所有的科学进步都是为了一个目标， 如何让人类变得更懒， 更舒适。 曾经paypal 的创始人Peter Thiel就说过关于创新的一段话（原话已经忘记，但意思应该差不多）， 所有的创业第一原则，就是让人们更懒去完成一些事情， 另外，乔布斯信奉的 大道至简（简单就是美） 的产品理念， 其实，就是让用户不要去花时间去思考什么是好的。今天我们程序员开发， 为什么进行模块化，分层化， 就是将每个成果固化，方便别人快速使用。 我们开源一个技术， 就是让别人不要再花时间再去重复建设， 直接站在别人的肩膀上工作。另外，比如，像浏览器之争， 今天google chrome 浏览器做到浏览器的第一把座椅， 就是google chrome 是最快的浏览器（至少号称是最快的）， 对于浏览器的众多需求中， 没有一个需求是能和性能相比， 人们永远都是追求更高，更快， 更远。</p><p>对比今天的产品设计， 如果我们面向用户， 那我们在思考用户的需求中， 哪些是最核心的需求， 哪些是第二原则， 哪些是末等公民。同样，如果我们中间件推出一些产品如果面向程序员， 帮助程序员开发程序， 那我们第一需求是什么， 第一需求就是如何帮助程序员更快的开发程序，帮助他们用更短的代价完成业务目标。 从技术语言的发展上， 从早期c 到c++， 再到java， 再到python， scala， go， 语言的层次愈来愈高， 一份需求需要的代码数量也变得越来越少。 </p><h1 id="认知世界的方式"><a href="#认知世界的方式" class="headerlink" title="认知世界的方式"></a>认知世界的方式</h1><p>在李善友的观点中， 认知世界， 有3种方式， 1. 归纳法， 2. 演绎法， 3. 带假设的归纳法； 归纳法就是通过大量的结果，来抽象出一个事务的内在联系，是一个证明在前，假设在后的逻辑思维， 由果去推因，这种思维会遭遇一个坑， 就是归纳出来的理论，只能证伪，不能存真， 只能用来验证一些东西，而不适合去做为普遍真理；而演绎法， 通过一些假设，再加上一些已知的规律， 由假设一步一步来推导结果， 是一个假设在前， 证明在后的观点， 由因去推果， 这种思维其实是更符合逻辑思维， 符合世界是由本质来推导出原因的过程， 不过这种思维会遭遇一个悖论， 演绎法需要一个证明的起点， 就是能够自证清白的原理，也就是第一性原理，当第一性原理不存在或无法自证清白时，就无法完成整个演绎； 因为2种理论都会有一些瑕疵， 因此李善友提出了一个补充，就是带假设的归纳法。</p><p>对世界的认知方式上， 从逻辑思维角度出发， 演绎论会更全面更严谨； 归纳法会直接，更快速。 对于我们it 工程师来说， 结合第一性原理和科学革命原来来思考， 很多的事情都是要抓住事物的本质， 少受一些干扰项干扰， 当现有的做法是从大量的结果中抽象出一套规律时， 我们需要研究是不是需要从客户的最本质需求上去思考，如何一步一步完成客户最大的痛点。 </p><h1 id="科学革命"><a href="#科学革命" class="headerlink" title="科学革命"></a>科学革命</h1><p>科学革命，在李善友的名词中， 有一个叫范式转换， 就是 只有管道更替， 创新是新的范式出现， 对过去很多东西，是一个颠覆性思考， 或者是完全换道考虑。 就像intel 从存储器转到cpu领域， 苹果推出iphone， 所谓 “重新定义手机”， 对过去的功能机一种完全全新的革命。</p><p>我对这种观点认为， 科学革命是一种颠覆性的创新， 这种创新其实很难， 也是非常少见的。 但有几点，需要注意</p><ol><li>自我革命的勇气，  当意识到一种新的创新出现时， 必须有勇气自我革命， 就是腾讯 就说过， 微信就是要革qq的命， 如果害怕革命， 当别人完成自我蜕变时， 自己就沦为鱼肉， 比如柯达， 当数码出现时，没有及时抛弃传统胶片，最终被市场抛弃。</li><li>这个事情大的创新都是非常非常难的， 只能从几个角度去努力尝试创新， 从认知世界的方式出发， 从用户最本质的需求上， 从另外一个赛道上全新去演绎一件事情， 会有更多的思路。 另外个人观点是， 博采众家之长， 把一个领域的成功经验移植到另外一个领域， 从而对这个领域进行全新的改变。 就像《三体》里面说的降维攻击。</li></ol><h1 id="非连续性"><a href="#非连续性" class="headerlink" title="非连续性"></a>非连续性</h1><p>李善友的观点是 “世界是由非连续性节点组成，而人类的认知是由连续性的节点来贯穿， 在一些非连续的节点之间穿插连续的桥梁， 而这些桥梁就是人类解决问题的各种手段”。 我觉得这个观点可能正确， 但没有什么大的价值。 对于一个程序员来说， 这个世界无论是连续还是非连续性的， 在计算机的世界里面都是非连续性的， 需要一些手段将他们连贯起来。 任何一个曲线都是由一串的节点贯穿起来。 </p>]]>
    </content>
    <id>https://ilongda.com/2017/knowledgerevolution/</id>
    <link href="https://ilongda.com/2017/knowledgerevolution/"/>
    <published>2017-12-23T11:07:43.000Z</published>
    <summary>李善友《认知革命》课程笔记：梳理第一性原理、归纳演绎思维、非连续性与科学革命等核心理论与个人理解，更多细节与示例见正文。</summary>
    <title>认知革命读后感</title>
    <updated>2026-06-09T08:46:25.938Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"工具","description":"运维工具专题索引：汇总 git、Linux 监控工具集与 ulimit 系统资源限制配置相关文章链接，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":3,"datePublished":"2017-10-10T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.964Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2017/docs/tools/index/"},"url":"https://ilongda.com/2017/docs/tools/index/","inLanguage":"zh-CN","keywords":["工具"],"articleSection":["工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"工具","item":"https://ilongda.com/2017/docs/tools/index/"}]}</script><ul><li><a href="/knowledge/tools/git.html">git</a></br></li><li><a href="/knowledge/tools/monitor_tools/">monitor_tools</a></br></li><li><a href="/knowledge/tools/ulimit.html">ulimit</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2017/docs/tools/index/</id>
    <link href="https://ilongda.com/2017/docs/tools/index/"/>
    <published>2017-10-10T11:42:57.000Z</published>
    <summary>运维工具专题索引：汇总 git、Linux 监控工具集与 ulimit 系统资源限制配置相关文章链接，更多细节与示例见正文。</summary>
    <title>工具</title>
    <updated>2026-06-09T08:46:25.964Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具 - iostat","description":"iostat 磁盘 I/O 性能分析工具笔记：解读 tps、Blk_read/s 等指标，及采样间隔与进程级 I/O 瓶颈定位方法","image":"https://ilongda.com/img/my.jpg","wordCount":1304,"datePublished":"2017-10-10T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.964Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2017/docs/tools/monitor_tools/iostat/"},"url":"https://ilongda.com/2017/docs/tools/monitor_tools/iostat/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具 - iostat","item":"https://ilongda.com/2017/docs/tools/monitor_tools/iostat/"}]}</script><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>使用iostat分析IO性能， 对于I&#x2F;O-bond类型的进程，我们经常用iostat工具查看进程IO请求下发的数量、系统处理IO请求的耗时，进而分析进程与操作系统的交互过程中IO方面是否存在瓶颈。 </p><p>下面通过iostat命令使用实例，说明使用iostat查看IO请求下发情况、系统IO处理能力的方法，以及命令执行结果中各字段的含义。 </p><h2 id="不加选项执行iostat"><a href="#不加选项执行iostat" class="headerlink" title="不加选项执行iostat"></a>不加选项执行iostat</h2><p>我们先来看直接执行iostat的输出结果：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">linux # iostat</span><br><span class="line">Linux 2.6.16.60-0.21-smp (linux) 06/12/12</span><br><span class="line"></span><br><span class="line">avg-cpu: %user %nice %system %iowait %steal %idle</span><br><span class="line">          0.07 0.00 0.05 0.06 0.00 99.81</span><br><span class="line"></span><br><span class="line">Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn</span><br><span class="line">sda    0.58 9.95       37.47      6737006  25377400</span><br><span class="line">sdb    0.00 0.00       0.00       824      0</span><br></pre></td></tr></table></figure><p>单独执行iostat，显示的结果为从系统开机到当前执行时刻的统计信息。以上输出中，除最上面指示系统版本、主机名和日期的一行外，另有两部分：</p><ul><li>avg-cpu: 总体cpu使用情况统计信息，对于多核cpu，这里为所有cpu的平均值</li><li>Device: 各磁盘设备的IO统计信息 对于cpu统计信息一行，我们主要看iowait的值，它指示cpu用于等待io请求完成的时间。Device中各列含义如下：<ul><li>Device: 以sdX形式显示的设备名称</li><li>tps: 每秒进程下发的IO读、写请求数量</li><li>Blk_read&#x2F;s: 每秒读扇区数量(一扇区为512bytes)</li><li>Blk_wrtn&#x2F;s: 每秒写扇区数量</li><li>Blk_read: 取样时间间隔内读扇区总数量</li><li>Blk_wrtn: 取样时间间隔内写扇区总数量</li></ul></li></ul><p>我们可以使用-c选项单独显示avg-cpu部分的结果，使用-d选项单独显示Device部分的信息。 </p><h2 id="指定采样时间间隔与采样次数"><a href="#指定采样时间间隔与采样次数" class="headerlink" title="指定采样时间间隔与采样次数"></a>指定采样时间间隔与采样次数</h2><p>与sar命令一样，我们可以以”iostat interval [count] ”形式指定iostat命令的采样间隔和采样次数：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">linux # iostat -d 1 2</span><br><span class="line">Linux 2.6.16.60-0.21-smp (linux) 06/13/12</span><br><span class="line"></span><br><span class="line">Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn</span><br><span class="line">sda    0.55 8.93       36.27      6737086  27367728</span><br><span class="line">sdb    0.00 0.00       0.00       928      0</span><br><span class="line"></span><br><span class="line">Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn</span><br><span class="line">sda     2.00 0.00      72.00      0        72</span><br><span class="line">sdb     0.00 0.00      0.00       0        0</span><br></pre></td></tr></table></figure><p>以上命令输出Device的信息，采样时间为1秒，采样2次，若不指定采样次数，则iostat会一直输出采样信息，直到按”ctrl+c”退出命令。注意，第1次采样信息与单独执行iostat的效果一样，为从系统开机到当前执行时刻的统计信息。 </p><h2 id="以kB为单位显示读写信息-k选项"><a href="#以kB为单位显示读写信息-k选项" class="headerlink" title="以kB为单位显示读写信息(-k选项)"></a>以kB为单位显示读写信息(-k选项)</h2><p>我们可以使用-k选项，指定iostat的部分输出结果以kB为单位，而不是以扇区数为单位：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">linux # iostat -d -k</span><br><span class="line">Linux 2.6.16.60-0.21-smp (linux) 06/13/12</span><br><span class="line"></span><br><span class="line">Device: tps kB_read/s kB_wrtn/s kB_read kB_wrtn</span><br><span class="line">sda     0.55 4.46     18.12     3368543 13686096</span><br><span class="line">sdb     0.00 0.00     0.00      464      0</span><br></pre></td></tr></table></figure><p>以上输出中，kB_read&#x2F;s、kB_wrtn&#x2F;s、kB_read和kB_wrtn的值均以kB为单位，相比以扇区数为单位，这里的值为原值的一半(1kB&#x3D;512bytes*2) </p><h2 id="更详细的io统计信息-x选项"><a href="#更详细的io统计信息-x选项" class="headerlink" title="更详细的io统计信息(-x选项)"></a>更详细的io统计信息(-x选项)</h2><p>为显示更详细的io设备统计信息，我们可以使用-x选项，在分析io瓶颈时，一般都会开启-x选项：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">linux # iostat -x -k -d 1</span><br><span class="line">Linux 2.6.16.60-0.21-smp (linux) 06/13/12</span><br><span class="line"></span><br><span class="line">……</span><br><span class="line">Device: rrqm/s wrqm/s r/s  w/s   rkB/s wkB/s  avgrq-sz avgqu-sz await svctm %util</span><br><span class="line">sda     0.00  9915.00 1.00 90.00 4.00 34360.00 755.25 11.79 120.57 6.33 57.60</span><br></pre></td></tr></table></figure><p>以上各列的含义如下：</p><pre><code>* rrqm/s: 每秒对该设备的读请求被合并次数，文件系统会对读取同块(block)的请求进行合并* wrqm/s: 每秒对该设备的写请求被合并次数* r/s: 每秒完成的读次数* w/s: 每秒完成的写次数* rkB/s: 每秒读数据量(kB为单位)* wkB/s: 每秒写数据量(kB为单位)* avgrq-sz:平均每次IO操作的数据量(扇区数为单位)* avgqu-sz: 平均等待处理的IO请求队列长度* await: 平均每次IO请求等待时间(包括等待时间和处理时间，毫秒为单位)* svctm: 平均每次IO请求的处理时间(毫秒为单位)* %util: 采用周期内用于IO操作的时间比率，即IO队列非空的时间比率</code></pre><p> 对于以上示例输出，我们可以获取到以下信息：</p><pre><code>1. 每秒向磁盘上写30M左右数据(wkB/s值)2. 每秒有91次IO操作(r/s+w/s)，其中以写操作为主体3. 平均每次IO请求等待处理的时间为120.57毫秒，处理耗时为6.33毫秒4. 等待处理的IO请求队列中，平均有11.79个请求驻留</code></pre><p> 以上各值之间也存在联系，我们可以由一些值计算出其他数值，例如：util &#x3D; (r&#x2F;s+w&#x2F;s) * (svctm&#x2F;1000)对于上面的例子有：util &#x3D; (1+90)*(6.33&#x2F;1000) &#x3D; 0.57603</p>]]>
    </content>
    <id>https://ilongda.com/2017/docs/tools/monitor_tools/iostat/</id>
    <link href="https://ilongda.com/2017/docs/tools/monitor_tools/iostat/"/>
    <published>2017-10-10T11:42:57.000Z</published>
    <summary>iostat 磁盘 I/O 性能分析工具笔记：解读 tps、Blk_read/s 等指标，及采样间隔与进程级 I/O 瓶颈定位方法</summary>
    <title>监控工具 - iostat</title>
    <updated>2026-06-09T08:46:25.964Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Machine Learning" scheme="https://ilongda.com/categories/Machine-Learning/"/>
    <category term="Machine Learning" scheme="https://ilongda.com/tags/Machine-Learning/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Machine Learning","description":"机器学习知识库目录：收录《Machine Learning In Action》系列阅读笔记与相关章节学习记录，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":1,"datePublished":"2017-08-05T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.952Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2017/docs/machine_learning/index/"},"url":"https://ilongda.com/2017/docs/machine_learning/index/","inLanguage":"zh-CN","keywords":["Machine Learning"],"articleSection":["Machine Learning"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Machine Learning","item":"https://ilongda.com/categories/Machine-Learning/"},{"@type":"ListItem","position":3,"name":"Machine Learning","item":"https://ilongda.com/2017/docs/machine_learning/index/"}]}</script><ul><li><a href="/knowledge/machine_learning/machine_learning_in_action_reading_notes/">machine_learning_in_action_reading_notes</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2017/docs/machine_learning/index/</id>
    <link href="https://ilongda.com/2017/docs/machine_learning/index/"/>
    <published>2017-08-05T11:42:57.000Z</published>
    <summary>机器学习知识库目录：收录《Machine Learning In Action》系列阅读笔记与相关章节学习记录，更多细节与示例见正文。</summary>
    <title>Machine Learning</title>
    <updated>2026-06-09T08:46:25.952Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Machine Learning" scheme="https://ilongda.com/categories/Machine-Learning/"/>
    <category term="《Machine Learning In Action》" scheme="https://ilongda.com/categories/Machine-Learning/%E3%80%8AMachine-Learning-In-Action%E3%80%8B/"/>
    <category term="Machine Learning" scheme="https://ilongda.com/tags/Machine-Learning/"/>
    <category term="《Machine Learning In Action》" scheme="https://ilongda.com/tags/%E3%80%8AMachine-Learning-In-Action%E3%80%8B/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《Machine Learning In Action》 -- 第一章 概述","description":"《Machine Learning In Action》 -- 第一章 概述","image":"https://cdn.nlark.com/yuque/0/2020/jpeg/106206/1595777622215-acd50fcd-d841-48e5-89eb-c0e326c0409d.jpeg#align=left&display=inline&height=3024&margin=%5Bobject%20Object%5D&name=DINGTALK_IM_3144282691.JPG.JPG&originHeight=3024&originWidth=4032&size=1855638&status=done&style=none&width=4032","wordCount":314,"datePublished":"2017-08-05T11:42:57.000Z","dateModified":"2024-02-02T12:16:43.902Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/chapter1_general/"},"url":"https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/chapter1_general/","inLanguage":"zh-CN","keywords":["Machine Learning","《Machine Learning In Action》"],"articleSection":["Machine Learning","《Machine Learning In Action》"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Machine Learning","item":"https://ilongda.com/categories/Machine-Learning/"},{"@type":"ListItem","position":3,"name":"《Machine Learning In Action》 -- 第一章 概述","item":"https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/chapter1_general/"}]}</script><h1 id="第一章：-概述"><a href="#第一章：-概述" class="headerlink" title="第一章： 概述"></a>第一章： 概述</h1><p>机器学习核心思想是 从众多纷繁复杂的数据中， 识别并归类有价值的信息， 甚至对下一步的趋势做一定的预测。 <br />在机器学习领域，如果有60%的准确度，就是非常惊人的。 <br /><br><br />数据挖掘十大算法</p><ol><li>C4.5 决策树</li><li>k-均值， k-mean</li><li>支持向量机  SVM</li><li>最大期望算法 EM</li><li>PageRank 算法</li><li>AdaBoost 算法</li><li>k-近临算法 KNN</li><li>朴素贝叶斯算法 NB</li><li>分类回归树 CART</li></ol><p><br />监督学习：这类算法必须知道预测什么， 即目标变量的分类信息</p><ul><li>分类： 根据不同的特征量， 对数据进行分类识别</li><li>回归：预测数值型数据</li></ul><p>无监督学习：没有类别信息， 也没有目标变量</p><ul><li>聚类： 将数据分成类似的对象的归类过程</li><li>密度估计： 寻找数据统计值的过程</li></ul><p><br /><img data-src="https://cdn.nlark.com/yuque/0/2020/jpeg/106206/1595777622215-acd50fcd-d841-48e5-89eb-c0e326c0409d.jpeg#align=left&display=inline&height=3024&margin=%5Bobject%20Object%5D&name=DINGTALK_IM_3144282691.JPG.JPG&originHeight=3024&originWidth=4032&size=1855638&status=done&style=none&width=4032" alt="DINGTALK_IM_3144282691.JPG.JPG"><br /><br><br />选择算法， 首先需要判断是否选择监督学习还是无监督学习， 在特征上是离散还是连续， 如果离散， 选择分类更适合， 如果连续，选择回归更适合<br /><br><br /><br><br />机器学习过程</p><ol><li>数据采集</li><li>数据输入</li><li>人工干预数据 </li><li>训练算法</li></ol>]]>
    </content>
    <id>https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/chapter1_general/</id>
    <link href="https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/chapter1_general/"/>
    <published>2017-08-05T11:42:57.000Z</published>
    <summary>《Machine Learning In Action》 -- 第一章 概述</summary>
    <title>《Machine Learning In Action》 -- 第一章 概述</title>
    <updated>2024-02-02T12:16:43.902Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Machine Learning" scheme="https://ilongda.com/categories/Machine-Learning/"/>
    <category term="《Machine Learning In Action》" scheme="https://ilongda.com/categories/Machine-Learning/%E3%80%8AMachine-Learning-In-Action%E3%80%8B/"/>
    <category term="Machine Learning" scheme="https://ilongda.com/tags/Machine-Learning/"/>
    <category term="《Machine Learning In Action》" scheme="https://ilongda.com/tags/%E3%80%8AMachine-Learning-In-Action%E3%80%8B/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《Machine Learning In Action》","description":"《Machine Learning In Action》阅读笔记索引：汇总第一章概述与第二章 K 近邻等章节学习链接，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":2,"datePublished":"2017-08-05T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.952Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/index/"},"url":"https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/index/","inLanguage":"zh-CN","keywords":["Machine Learning","《Machine Learning In Action》"],"articleSection":["Machine Learning","《Machine Learning In Action》"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Machine Learning","item":"https://ilongda.com/categories/Machine-Learning/"},{"@type":"ListItem","position":3,"name":"《Machine Learning In Action》","item":"https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/index/"}]}</script><ul><li><a href="/knowledge/machine_learning/machine_learning_in_action_reading_notes/chapter1_general.html">chapter1_general</a></br></li><li><a href="/knowledge/machine_learning/machine_learning_in_action_reading_notes/chapter2_k.html">chapter2_k</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/index/</id>
    <link href="https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/index/"/>
    <published>2017-08-05T11:42:57.000Z</published>
    <summary>《Machine Learning In Action》阅读笔记索引：汇总第一章概述与第二章 K 近邻等章节学习链接，更多细节与示例见正文。</summary>
    <title>《Machine Learning In Action》</title>
    <updated>2026-06-09T08:46:25.952Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Machine Learning" scheme="https://ilongda.com/categories/Machine-Learning/"/>
    <category term="《Machine Learning In Action》" scheme="https://ilongda.com/categories/Machine-Learning/%E3%80%8AMachine-Learning-In-Action%E3%80%8B/"/>
    <category term="Machine Learning" scheme="https://ilongda.com/tags/Machine-Learning/"/>
    <category term="《Machine Learning In Action》" scheme="https://ilongda.com/tags/%E3%80%8AMachine-Learning-In-Action%E3%80%8B/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"《Machine Learning In Action》 -- 第二章 k近邻算法","description":"《Machine Learning In Action》 -- 第二章 k近邻算法","image":"https://ilongda.com/img/my.jpg","wordCount":232,"datePublished":"2017-08-05T11:42:57.000Z","dateModified":"2024-02-02T12:16:43.902Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/chapter2_k/"},"url":"https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/chapter2_k/","inLanguage":"zh-CN","keywords":["Machine Learning","《Machine Learning In Action》"],"articleSection":["Machine Learning","《Machine Learning In Action》"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Machine Learning","item":"https://ilongda.com/categories/Machine-Learning/"},{"@type":"ListItem","position":3,"name":"《Machine Learning In Action》 -- 第二章 k近邻算法","item":"https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/chapter2_k/"}]}</script><h1 id="第二章-k近邻算法"><a href="#第二章-k近邻算法" class="headerlink" title="第二章 k近邻算法"></a>第二章 k近邻算法</h1><p>k近邻算法： 数据集中每个数据都有一个标签， 这些不同类型标签组合起来标识了这个数据的所属分类， 当出入一个新数据时， 用新数据的每个特征和现存数据集中的特征进行比较， 选取最近似的数据的分类标签。 <br /><br><br /><br><br />举例：<br />对一个电影进行判断是爱情片还是动作片</p><ol><li>收集数据：  拿到一些的片子</li><li>准备数据： 对这些片子进行结构化， 提取其中 接吻次数和打斗次数， 如果接吻次数远大于打斗次数，则为爱情片</li><li>分析数据， 将新片子，提取新片子的接吻次数和打斗次数，插入到二位像限中</li><li>训练算法： k-近邻一般不需要</li><li>测试算法： 计算错误率</li><li>使用算法： 输出结果，</li></ol>]]>
    </content>
    <id>https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/chapter2_k/</id>
    <link href="https://ilongda.com/2017/docs/machine_learning/machine_learning_in_action_reading_notes/chapter2_k/"/>
    <published>2017-08-05T11:42:57.000Z</published>
    <summary>《Machine Learning In Action》 -- 第二章 k近邻算法</summary>
    <title>《Machine Learning In Action》 -- 第二章 k近邻算法</title>
    <updated>2024-02-02T12:16:43.902Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="大数据" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="大数据" scheme="https://ilongda.com/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"The ScreamScope Model： Microsoft ScreamScope 编程模型","description":"Microsoft StreamScope 论文笔记：rVertex/rStream 抽象、sequence 确定性 exactly-once 与 SCOPE 流式扩展架构","image":"http://img3.tbcdn.cn/L1/461/1/c4eeb3b681401ab943322dc9481b941b2df1b770","wordCount":6441,"datePublished":"2016-11-26T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.964Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2016/docs/paper/screamscope/"},"url":"https://ilongda.com/2016/docs/paper/screamscope/","inLanguage":"zh-CN","keywords":["论文","大数据"],"articleSection":["论文","大数据"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"The ScreamScope Model： Microsoft ScreamScope 编程模型","item":"https://ilongda.com/2016/docs/paper/screamscope/"}]}</script><h1 id="插曲"><a href="#插曲" class="headerlink" title="插曲"></a>插曲</h1><p><span class="exturl" data-url="aHR0cHM6Ly93d3cudXNlbml4Lm9yZy9zeXN0ZW0vZmlsZXMvY29uZmVyZW5jZS9uc2RpMTYvbnNkaTE2LXBhcGVyLWxpbi13ZWkucGRm">screamscope<i class="fa fa-external-link-alt"></i></span> 论文读了好几个月了，把之前分享，今天发出来。</p><hr><h1 id="总结："><a href="#总结：" class="headerlink" title="总结："></a>总结：</h1><p><B><U>论文主要是提出一种设计理念 rVertex&#x2F;rStream, 这个设计理念其实还非常先进。尤其是在低延迟和容错性上思考很多</B></U></p><p>这个设计理念换了一种角度来思考常见的分布式计算框架（常见思路，主要是考虑DAG 和节点）， 将计算拆解为计算单元（rVertex）和管道(rStream)2种抽象， 尤其是管道（rStream）将上下游的关系进行解藕， 对failure 问题上还是解的很漂亮。</p><h2 id="核心一些观点："><a href="#核心一些观点：" class="headerlink" title="核心一些观点："></a>核心一些观点：</h2><ul><li>这套系统起源于批处理系统SCOPE, 是对SCOPE 系统一个扩展，并且将原来一些批处理的任务已经迁移到StreamScope 上。已经部署到2万台机器上，每天处理百亿／数十TB 的数据。另外因为它起源于SCOPE, 它应该用sql 解决应用问题上， 会比其他流式框架要更成熟（这个是个人猜测）；</li><li>用户接口上面， 为用户提供申明式语言（类似sql）和udf,</li><li>用户提交一段类sql，将它编译成logic plan(含一些udf和各种算子)，然后用优化器，选择一个最优路径，然后，转化为physical plan，部署到每台机器上， 最后用job manager 来跟踪所有的状态。 很多组件都是用scope的组件完成或稍加改造。 （现代系统大致思路都是这个逻辑）   </li><li>接口抽象上，接口还是适配性非常强； 节点rVertex 提供3个核心接口Load&#x2F;Execute&#x2F;GetSnapshot， 这个模型可以适配到绝大部分计算场景。rStream 接口 Write(seq, e)／Register(seq)／GarbageCollect(seq)</li><li>在容错性上， 首先通过rStream 来将上下游节点进行解藕， 下游的失败不一定需要影响到上游， 然后提供3种策略来，让用户选择。（读者可以看后面的详细介绍）</li><li>在exactly-once 要求上， 依赖3大部分： 1. 容错机制； 2. sequence number（每条消息配置单调递增的） 3. 确定性， 明确每个节点在状态相同时，接受相同输入时（无论是多实例同时计算或者同一个实例重启后计算），必须产生相同结果</li><li>性能上， 1. 通过sequence number来减少ack 数据流；2. rStream 的3层设计 （volatile／Reliable&#x2F;GC）在保障稳定性同时，可以获得一些好的性能。3. 一次读取，可以获取一个小collection</li></ul> <span id="more"></span><h1 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h1><p>StreamScope 是 SCOPE  批处理系统的流式扩展的设计与实现。StreamScope 深度考虑SCOPE中的架构， compiler， 优化器和任务管理器, 对这些组件进行调整或redesign 以适应流式计算。通过对批处理和流计算的整合在实践中提供显著好处。</p><p>在StreamScope 中， 用户程序类似sql。 这个程序倍编译成一个流式 DAG 如图4 所示</p><p><img data-src="http://img3.tbcdn.cn/L1/461/1/c4eeb3b681401ab943322dc9481b941b2df1b770" alt="screenshot"></p><p>为了产生这种DAG, StreamScope 的compiler 执行如下步骤：</p><ul><li>程序首先被转化为logical plan(DAG), logical plan 中含有StreamScope runtime operators， 如临时join， window aggregates和用户自定义函数。</li><li>StreamScope optimizer 评估各种执行计划， 选择一个评估最低成本的执行计划， 基于可获得的资源， 数据参数如数据流入速度和内部成本模型</li><li>创建最终的physical plan(DAG), 映射一个logical 节点到适当的physical 节点上，并发执行。 为每个节点生成code， 并部署到集群上。 这些细节类似SCOPE .</li></ul><p>整个执行由streaming job manager 进行精心安排：</p><ul><li>调度节点到并建立DAG 中的channel到不同的机器上</li><li>监控进程并跟踪snapshots</li><li>当发现错误时提供容错，并初始化恢复动作。</li></ul><p>不像批处理的job manager， 批处理的job manager在不同的时间上调度节点， 流计算job manager在任务的起始阶段就开始调度DAG中所有的节点 。 为了提供容错性和应付动态变更， rVertex 和rStream 用来实现DAG 中的节点和channel， 同job manager协同工作。</p><h1 id="编程模型"><a href="#编程模型" class="headerlink" title="编程模型"></a>编程模型</h1><p>本章将介绍一个high－level的编程模型概述， 重点描述重要概念，包括数据模型和查询语言。</p><p>连续事件流（continuous event StreamScope）。 在StreamScope中， 数据以事件流的形式来表达， 每一个描述一个潜在的无限事件集合， 每一个事件流有一个定义好的schema。 除此之外， 每一个事件有一个事件间隔[Vs, Ve), 描述事件有效的起始时间和终止事件。</p><p>像其他的流计算引擎， StreamScope 支持current time increments(CTI) event, 它可以来保证截止到CTI的Vs的event已经发送完毕； 也就意味着，在CTI event 后再没有消息的timestamp小于Vs。 依赖CTI event的算子决定了当前处理事件从而保证流程继续并丢掉失效的状态信息。</p><p>Declarative query language. (申明式查询语言)， StreamScope 提供一个陈述式查询语言， 这样用户可以编程他们的应用而不用担心分布式环境的一些细节，比如scalability或容错性。 尤其是， StreamScope 扩展了SCOPE 查询语言 来支撑一个full  temporal relational algebra（完整所有关系型代数），通过用户自定义函数， aggregator 和算子来进行扩展。</p><p>StreamScope支持一套复杂的关系型算子包括 projection， filter， grouping和join 适配到所有语法。 举例来说， 一个inner join 适用于存在重叠时间窗口的事件。 window是另外一个重要概念。 一个window明确定义时间窗口和在这个窗口内的事件子集， 这样在这个子集上可以做aggregate。 StreamScope支持几种时间窗口，比如hopping／tumbling／snapshot 窗口。 例如， hopping window是按照固定大小H进行jump的窗口（大小为S）, 一个新的S大小的窗口会被创建每H 单位时间内。</p><p>Example.<br><img data-src="http://aligitlab.oss-cn-hangzhou-zmf.aliyuncs.com/uploads/aloha/aloha/6c58947d56e453105d3828b39a830e08/image.png" alt="image"></p><p>Figure 1 展示一个实例程序， 他执行连续分析行为在Process上并Alert 事件流。 一个StreamScope程序包含一系列在事件流上申明式查询操作。 Process 事件记录了每一个process并且关联用户，而Alert event记录了每一个alert的信息， 包括谁产生这个alert。 这个程序首先join 2个数据流并关联用户信息到alert，然后用一个hopping window计算每个用户 alert的数量每5秒钟 。</p><h1 id="StreamScope-抽象"><a href="#StreamScope-抽象" class="headerlink" title="StreamScope 抽象"></a>StreamScope 抽象</h1><p>StreamScope的程序执行可以用一个DAG 来表述， 每一个vertex 在输入数据流上进行本地计算并产生输出数据流。 每一个数据流描述为一个无限顺序的事件， 每个事件包含一个连续增加的sequence number（序列号）。</p><p><img data-src="http://aligitlab.oss-cn-hangzhou-zmf.aliyuncs.com/uploads/aloha/aloha/12ac8360628ba278f64cab4ab7f334b4/image.png" alt="image"></p><p>图2展示了图1的的DAG, 每一个stage的计算会被分区到多个节点上进行并行执行。StreamScope 根据数据量和计算复杂度来决定每个stage的并发程度。</p><p>一个节点可以维护一个本地状态。 它的执行从它最初的状态开始并逐步执行。 在每一步， 节点从它的输入源上中消费下一个事件， 更新状态，并可能产生新的事件到它的输出流。 这个节点的执行可以通过一系列的snapshot来跟踪， 每一个snapshot都是一个三元组，包含当前输入流的sequence numbers， 输出流的sequence numbers和它的当前状态。</p><p><img data-src="http://aligitlab.oss-cn-hangzhou-zmf.aliyuncs.com/uploads/aloha/aloha/6ca6b875f6804d884878a3d2163a3c0f/image.png" alt="image"></p><p>图3 展示了节点的处理过程， 从snapshot s0 到 s1 （处理了a1）， 然后到s2（处理了b1）. StreamScope 引入2个抽象 rStream 和rVertex 来实现流和节点。</p><h1 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h1><h2 id="rStream-概念"><a href="#rStream-概念" class="headerlink" title="rStream 概念"></a>rStream 概念</h2><p>不同于节点直接通过网络进行通信， StreamScope引入一个rStream抽象来解藕上下游的节点 通过一些属性来达到错误恢复。</p><p>rStream 包含一系列event，这些event包含连续并单调递增的sequence number， 支持多个writer和reader。 一个writer 调用Write(seq, e) 吧sequence number seq加到event中去。  rStream支持多个writer主要是为了允许同一个vertex的2个实例， 它会非常有用，当处理失败和重复执行中的stragglers。</p><p>一个reader可以调用Register(seq) 来显示他的感兴趣的event的起始sequence number seq，并开始通过ReadNext来读取数据，ReadNext 会返回下一个batch的带有sequence numbers的事件并调整读取偏移量。在实现中， event可以被push到一个注册的reader中，而不是被pull。 每一个reader可以异步处理events而不用和同个stream的气体readers或writers同步。一个reader同样可以rewind 一个stream通过重新register sequence number。 rStream 同样支持GarbageCollect(seq)来显示所有的小于seq的event不再需要并可以被丢弃。rStream 保留下面属性</p><ul><li>唯一性(Uniqueness), 每个sequence number是一个唯一值。 当第一个seq的写成功后， 任何后续的关联到seq的写操作都会被扔弃。</li><li>有效性（Validity）假设一个ReadNext 返回一个带seq的event， 那必然有一个成功的Write(seq, e)</li><li>可靠性(Reliability), 假设一个write(seq, e)成功了， 任何ReadNext 读取到seq 位置时，都会返回(seq, e)</li></ul><p>唯一性保证了每一个sequence number的一致性， 有效性保证每个sequence number的event value的正确性， 可靠性保证写到流的所有event 都是available。 rStream 可以通过一个带有后台存储的可靠的pub&#x2F;sub 系统来实现， 但StreamScope采用一个更高效的实现，避免时延变长因为在关键路径上把数据序序列化到存储上， 后面会介绍这个实现。</p><h2 id="实现rStream"><a href="#实现rStream" class="headerlink" title="实现rStream"></a>实现rStream</h2><p>rStream  抽象提供稳定的管道， 允许receiver 可以从任何指定位置进行read。 一个简单直接的实现是持久化和稳定的写数据到底层的Cosmos  分布式文件系统。 这些在关键路径上的同步写会带入显著的延迟。 StreamScope因此使用一个混合schema，将一些关键路径上的写操作移出单提供稳定可靠的channel的效果： event首先在内存中进行缓存起来，可以直接传输给消费方的节点。 内存中的buffer 会异步刷新到 Cosmos 来容错。 当错误发生时， 内存中的buffer会丢失，但当需要时，可以被重新计算。</p><p>为了在失败时，可以重新计算丢失的event， stream 跟踪每条消息的计算，类似TimeStream的依赖跟踪或D-Stream的血统跟踪， job manager跟踪节点的snapshots， 它可以用后来来通知如何重现event到输出流中。 一个节点自己决定什么时候获取snapshot， 保存并汇报给job manager。 举例来说，如图5，<br><img data-src="http://aligitlab.oss-cn-hangzhou-zmf.aliyuncs.com/uploads/aloha/aloha/7338ad5d3520095a76b9865e62798d11/image.png" alt="image"></p><p>节点v4 发送2个更新到job manager， 第一个更新汇报<code>snapshot s1={&lt;2, 7&gt;, &lt;12&gt;, t1}</code>, 它表示这个节点这个秒时， 消费到2的event对第一个数据流，消费到7对第二个数据流， 并且在状态t1，产生event 12. 第二个update s2&#x3D;<code>{&lt;5, 10&gt;, &lt;20&gt;, t2}</code>, 它接受到5的event 在第一个数据流， 在第二个数据流到10， 当前状态为t2. 这种跟踪机制完全对用户透明。 如果输出流中的event 16需要被重新计算， job manager可以找到最大的output sequence number 小于16的snapshot， 这个例子中是s1. 这样他会启动一个新的节点， 加载snapshot s1, 然后持续计算直到产生event 16。这个新实例的执行需要第一个数据流的event 3到5， 第二个数据流的8到10， 这样有可能会触发上游节点重新计算，当这些event也是无法获取时。 当原始输入数据假设为可靠序列化时，这个过程最终会结束（指下游不断触发上游做重新计算，直到需要的原始数据已经持久化好，就不用再触发上游做重新计算）。总的来说， 这种设计移动flush 到可靠持久化存储的过程到关键路径外， 同时，减少需要重新计算的event的数量。 因为rStream 是无限的， 在一个真实的实现中， 垃圾收集是有必要删除失效的event和相关的tracking 信息（用来在错误发生时，重新生成event之用）</p><p><img data-src="http://aligitlab.oss-cn-hangzhou-zmf.aliyuncs.com/uploads/aloha/aloha/1736a2ece8c7db918c1556f23e23aa42/image.png" alt="image"></p><p>图6 展示了rStream 的实现方式。 在本例中， 只有一个writer W 和3个reader R1, R2, R3. 数据流不断增长从左到右。 stream的头（标记为GC）包含失效的event和可以被垃圾回收的event， 紧接着时一系列已经被可靠持久化的event， stream的尾部（最新的event） 是volatile， 当失败发生时，这些数据会丢失。 checkpoint可以定期创建（比如c1,c2, c3, c4, c5）, 当volatile 部分的stream 丢失时， it 可以从c4 snapshot开始做重新计算。 在可靠持久化部分的event可以提供给R1 R2而无序重新计算。 在可靠存储中还有部分数据直接重播。 没有更近一步的重叠重新计算需要重新恢复。</p><h2 id="垃圾回收"><a href="#垃圾回收" class="headerlink" title="垃圾回收"></a>垃圾回收</h2><p>StreamScope 持久化snapshot， StreamScope 和其他跟踪信息（用于恢复错误）， 并且需要确定什么时候开始进行垃圾回收。 StreamScope 为节点／StreamScope维护一个low－water－marks。 对于一个stream， 这个low-water-mark指向需要的最小event的sequence number。</p><p>对于每个节点， snapshot 根据它的输入流和输出流的sequence number来做完全排序。 举例来说， 假设一个节点有2个输入，1个输出， snapshot s 为<code>(&lt;7, 12&gt;, &lt;5&gt;)</code> 将会比比<code>(&lt;7, 20&gt;, &lt;8&gt;)</code>要小， 并且不存在<code>(&lt;6, 16&gt;, &lt;4&gt; )</code>这样的snapshot。</p><p>考虑一个节点v ， I 作为它的输入数据流， O 做为它的输出流。 节点v 维护一个low-water-marker sequence number lm0 为每个输出流， 初始化为0. 节点v实现GC(o, m)来实现垃圾回收， 显示所有小于m的sequence number的event将不再被下游vertex需要（下游vertex 消费输出流o）。 简单假设，每一个数据流只被一个下游节点消费， 但一般来说， 一个流经常被多个下游节点一起消费。</p><ul><li>假如 m &lt;&#x3D; lm0, 返回； ／／ 不需要进一步gc</li><li>设置lm0到m。 假设s是最新的checkpoint的snapshot， s必须满足条件， 在s中输出流o的sequence number并不高于lm0. 丢掉所有小于s的snapshot</li><li>对于每个输入的stream， 让vi 是上游节点，它产生输入流i 并且让si是对应s中输入流i的sequence number si， 调用vi GarbageCollect(si), 丢掉所有小于si的event在输入流i中， 递归调用vi GC(i, si)intuitive</li></ul><p>直觉来说， GC(o, m) 算出哪些信息将不再被下游节点需要， sequence number小于m。 当最重输出event被持久化或消费，或档任何输出的event被持久化。 图7 显示一个low-water-marks的例子。 尽管递归确定算法， 可以通过相反的拓扑遍历顺序来实现。</p><h2 id="rVertex-概念"><a href="#rVertex-概念" class="headerlink" title="rVertex 概念"></a>rVertex 概念</h2><p>rVertex 支持如下的操作<br>Load(s) 节点从snapshot s 开始一个实例。<br>Execute()  从当前snapshot 开始执行一步<br>GetSnapshot() 返回当前snapshot</p><p>一个节点可以以Load(s0) 开始， s0是起始状态并且所有的流以起始的position。 节点然后可以执行一系列的Execute(), Execute  会读取输入数据， 更新状态，产生新数据。任何时候， 节点可以调用GetSnapshot() 获取当前snapshot并保存它。 当节点失败时， 可以用Load(s)来restart。</p><p>确定性(Determinism). 对于一个确定输入流的节点来说， 运行 Execute() 在相同的snapshot上会产生相同的状态（snapshot）和相同的输出结果。</p><p>确定性保证了当从失败中进行重放event时，数据的准确性。 它也暗示了执行从不同输入stream获取的event的顺序是确定的； 第4章会介绍StreamScope是怎么做到顺序性而又不引入不必要的delay。 确定性极大保证正确性，并让应用更容易开发和调试。</p><h2 id="实现rVertex"><a href="#实现rVertex" class="headerlink" title="实现rVertex"></a>实现rVertex</h2><p>实现rVertex的关键是保障如section 3所述的确定性， 它要求function determinism和input determinism。 在StreamScope中， 所有的算子和用户自定义函数必须确定性的。 我们假设一个任务的输入stream是确定性的， event的顺序和值都是确定性的。 唯一的不确定性是穿过不同管道的event的顺序性。因为StreamScope使用CTI event做为标记， 我们插入一个特殊的MERGE 算子在一个接收多个输入的节点的起始位置， 这样可以生成一个确定性的顺序，为后序的操作。通过等待对应的CTI event 来露出并排序event，并按照确定性顺序来emit 它们。 因为， 节点的处理逻辑用相同方式等待CTI event,   这种方式并没有引入额外的dely。<br>StreamScope 对每个stream的event 以连续单调递增的方式进行打标sequence number。 一个节点可以用sequence number来从所有的流中定位最后的消费／产生 event。 在每个步骤， 一个节点消费输入流的一个event，比如调用Execute(),  它可能改变内部状态，产生新的event 到输出流中， 因此达到一个新的snapshot。 GetSnapshot() 返回当前snapshot， 他可以在一个步骤后暂停执行或者在不中断执行的情况，对一些copy－on－write的数据做一些拷贝动作，来保证一致性。Load(s)  启动一个节点并load s 作为当前snapshot在继续执行前。 为了可以从一个snapshot上可以进行断点恢复， 一个节点会定期做checkpoint。</p><h2 id="错误恢复"><a href="#错误恢复" class="headerlink" title="错误恢复"></a>错误恢复</h2><p>rStream 抽象解藕了上下游节点，从而允许单个节点从错误中恢复。当一个节点失败时， 可以简单重启节点通过Load(s) 从最近的保存的snapshot 开始执行。 rVertex 抽象表示恢复后的执行同没有错误发生时的执行结果是相同的。rStream抽象保证重启的节点可以reread 输入数据。 第4章介绍 如何实现rVertex和rStream. 不同的错误恢复策略来达到不同的tradeoff</p><h3 id="错误恢复策略"><a href="#错误恢复策略" class="headerlink" title="错误恢复策略"></a>错误恢复策略</h3><p>StreamScope 必须能够从错误中进行恢复。 rVertex 和rStream 抽象结果下游的节点， 从而很容易查找并恢复错误。 除此之外， 他们还抽象潜在的实现机制， 允许他们共享公共的机制来容错。</p><p>已经实现了集中错误恢复策略， 可以根据一些因子来做判断和选择， 普通情况开销(需要资源消耗)， 普通情况费用（指延迟）， 恢复成本（恢复资源）和恢复时间。</p><p>目前有3种策略， 对于rStream 和rVertex, 每个节点可以从错误中独立恢复出来。 因此，这些策略可以用在这些节点上甚至相同job上不同的节点使用不同的策略。</p><h3 id="基于checkpoint的恢复。"><a href="#基于checkpoint的恢复。" class="headerlink" title="基于checkpoint的恢复。"></a>基于checkpoint的恢复。</h3><p>节点会定期做checkpoint，把snapshot保存到持久化存储上。 当一个节点失败时， 他会加载最近的checkpoint 并继续执行。 checkpoint 的直接实现会引入正常执行的额外开销， 并且在维护一个大的state时并不是很理想。 高级的checkpoint技术会需要特定的数据结构，他们会引入复杂性和额外开销。</p><h3 id="基于重播机制的恢复。"><a href="#基于重播机制的恢复。" class="headerlink" title="基于重播机制的恢复。"></a>基于重播机制的恢复。</h3><p>常见的流计算是无状态或者因为使用window 算子儿拥有一个短期的内存。也就是说， 当前的内部状态依赖最新window的一些event。 这这鞋情况下， 节点可以抛开显示的checkoint，而采用重新加载那个window的event从而从一个起始状态开始rebuild 状态。 然而这是一种特殊情况， 很常见很有用。 利用这种属性， stream能够简单跟踪输入／输出流的sequence number而不用存储节点的本地状态。 这种策略有可能需要重新加载一个大的window， 但它避免checkpoint的一些开销。<br>这种策略有一个潜在暗示在垃圾回收机制。 不是从一个snapshot中加载一个状态， 一个节点必须recover从它输入的早起的event， 这些event必须可以获得。</p><h3 id="replication机制的recovery。"><a href="#replication机制的recovery。" class="headerlink" title="replication机制的recovery。"></a>replication机制的recovery。</h3><p>另外一种策略是对于相同的节点同时有多个instance。 他们连接到相同的输入流和输出流。 rStream 允许多个reader和writer， 自动根据sequence number进行去重。<br>rVertex的确定性属性让replication 方式可行， 因为这些instance 行为是一致性的。 通过replication， 一个节点可以有instance 轮流进行checkpoint而不会影响latency，因为其他的instance正以正常速度进行允许。 当一个instance fail， 它可以获得另外一个instance的当前snapshot，从而直接加速recovery。 这些好处都是来自于同时运行多个instance。</p><h1 id="讨论"><a href="#讨论" class="headerlink" title="讨论"></a>讨论</h1><h2 id="StreamScope-在现有的分布式流计算引擎上选择另外一种方式。"><a href="#StreamScope-在现有的分布式流计算引擎上选择另外一种方式。" class="headerlink" title="StreamScope 在现有的分布式流计算引擎上选择另外一种方式。"></a>StreamScope 在现有的分布式流计算引擎上选择另外一种方式。</h2><p>minibatch 的带RDD 的流处理。 不是支持连续的流模型， D-Streams 模型一个流计算为一系列minibatch的计算在一些小的时间间隔，并且利用immutable RDD 来做错误恢复。 将流计算拆解为一系列minibatch的方式会比较笨重， 因为一些流算子，比如windowing， join， aggregation， 维护状态到处理event 会高效一些。 拆分这些操作到独立的minibatch计算要求重建计算状态从前一个batch在处理当前batch event之前。 比如图1 的inner join。 这个join的算子需要track 所有的event， 这些event 有可能生成匹配结果从当前或将来batch 以高效的数据结构并且可以仅当接收到CTI event 时retie他们， 这种join状态是潜在复杂的join类型， 并且需要在每个batch或连续的mini－batch中进行重构。 更近一步， D-Streams 不考虑低延迟和容错性， 一个minibatch决定节点计算的粒度， 然而一个imuutable RDD, 主要为了错误恢复， 根据每个minibatch来创建。 这种低延迟要求小的small batch size甚至已经没有必要在那个粒度开启错误恢复。</p><h2 id="非确定性。"><a href="#非确定性。" class="headerlink" title="非确定性。"></a>非确定性。</h2><p>rVertex 要求确定性来保证正确性和容易debug。 非确定性容易引起不一致性当一个节点失败重新计算时。 非确定性有可能引起重新执行结果偏离起始的执行并导致一种情况， 下游节点使用2种不一致的输出event。 StreamScope 可以扩展去支持不一致性，但以一定代价。<br>一种方式来避免因为非确定导致的不一致性 是保证节点输出的任何event 不再需要重新计算。 这可以通过在让下游可见event之前，对它做checkpoint并持久化到存储中去。 这就是millwheel的本质。 这个设计会引入显著额外开销，因为在关键路径上的高昂checkpoint。 一种可选的方式是log 非确定性的决定当执行replay是。 log方式的开销会小于checkpoint方式， 但这种方式要求所有的非确定的来源必须被标记，适度log并重放。 StreamScope 并不支持这种实现方式。</p><h2 id="乱序event。"><a href="#乱序event。" class="headerlink" title="乱序event。"></a>乱序event。</h2><p>event可以不按它吗应用时间的顺序来到达， 比如event来自多个消息。 比如storm或millwheel 分配一个唯一的但非顺序性的id给每个event。 下游节点发送带这些id的acks给上游节点来跟踪状态和处理失败。 StreamScope 解藕 event的logical order 于物理发送或消费顺序。 它借用了流数据库中的CTI  理念 来达到乱序event处理在语言和算子层面。 在系统层面， StreamScope 分配唯一病情顺序性的sequence number给event， 从而轻松可以跟踪处理进程和错误恢复， 从而避免显著的acks而引起性能开销。</p><h1 id="生产经验"><a href="#生产经验" class="headerlink" title="生产经验"></a>生产经验</h1><ul><li>从batch到流， StreamScope 是batch处理系统的扩展，并且大量使用现有的组件。并且大量的批处理应用迁移到StreamScope上， 并且提供迁移支持， 允许batch version来验证流部分的正确性。</li><li>伸缩性和健壮性波动。 rStream 有效的保证了错误恢复时和扩容时，新节点能更上节奏。</li><li>简化分布式流计算。 声明式语言让业务方很容易些流应用。StreamScope 提供off-line模式，  有限的数据持久化到存储后， 可以模拟线上数据流， 并且对一个节点进行单独debug</li><li>回溯， 比如发生数据订正时， 就需要对数据进行回溯，修正之前的数据。  StreamScope会保留所有的checkpoints 和输入channel 到一个全局repository， 它实现了一定的保留策略。</li><li>当系统维护时不间断执行。其实就是利用rVetex的确定性，当部分节点需要升级维护时，会启动一个备份，当备份节点ready后， 会把停机维护的节点给杀掉。</li></ul>]]>
    </content>
    <id>https://ilongda.com/2016/docs/paper/screamscope/</id>
    <link href="https://ilongda.com/2016/docs/paper/screamscope/"/>
    <published>2016-11-26T11:42:57.000Z</published>
    <summary>Microsoft StreamScope 论文笔记：rVertex/rStream 抽象、sequence 确定性 exactly-once 与 SCOPE 流式扩展架构</summary>
    <title>The ScreamScope Model： Microsoft ScreamScope 编程模型</title>
    <updated>2026-06-09T08:46:25.964Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="大数据" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="大数据" scheme="https://ilongda.com/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"The Dataflow Model： Google Dataflow 编程模型","description":"Google Dataflow 编程模型论文笔记：统一批流、窗口 trigger/watermark 及迟到数据处理与 Lambda 架构替代思路","image":"https://ilongda.com/img/dataflow/windowtype.jpg","wordCount":12059,"datePublished":"2016-10-26T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.962Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2016/docs/paper/dataflow/"},"url":"https://ilongda.com/2016/docs/paper/dataflow/","inLanguage":"zh-CN","keywords":["论文","大数据"],"articleSection":["论文","大数据"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"The Dataflow Model： Google Dataflow 编程模型","item":"https://ilongda.com/2016/docs/paper/dataflow/"}]}</script><h1 id="插曲"><a href="#插曲" class="headerlink" title="插曲"></a>插曲</h1><p>《The Dataflow Model: A Practical Approach to Balancing Correctness, Latency, and Cost in Massive-Scale, Unbounded, Out-of-Order Data Processing》 2015年，这篇文章就发布了， 每次快速扫描， 理解上总是有些遗漏， 最近，决定将他翻译一下， 仔细阅读一下.<br>补充一下，2.4及之后的章节自己看了，但是没有翻译， copy一下同事默岭的翻译（版权归默岭所有哦）。</p><h1 id="为什么重新设计dataflow-编程模型"><a href="#为什么重新设计dataflow-编程模型" class="headerlink" title="为什么重新设计dataflow 编程模型"></a>为什么重新设计dataflow 编程模型</h1><ol><li>一份代码运行在不同的引擎， 现有很多google的业务方， 使用lambda架构， 实时部分使用millwheel做一些计算，不能保证完全正确， 然后通过离线flumejava作业不停的矫正数据。 （吐槽一句， millwheel 论文里面说自己的强一致多么牛x， 最后在dataflow 论文里面被打脸， 不过，millwheel里面提到的强一致设计是可以保证强一致，但成本非常高）。 业务方只需要实现一次代码，就可以在不同的引擎上切换，按照自己的业务需求。</li><li>会话窗口需求， 需要支持session window</li><li>trigger， accumulate和retraction 需求， 一些业务方使用watermark来标记数据已经完成，但经常有数据延迟到达，因此，需要处理迟到的数据。 整个设计需要考虑： 1. 支持增量处理， accumulate和retraction； 2， trigger机制， 一份相同的数据，根据不同的准确性和扩展性要求，选择不同的模式，从而达到不同的结果。</li><li>水位线百分位触发器， 比如， 部分节点处理特别缓慢， 成为job的长尾， 拖慢了整个项目的进度。 另一个需求是， 很多时候，只需要达到一个很高的准确行就好了，而不是100%准备。 这样，只需要通过percentile watermark trigger， 就可以确定是否要提前终止长尾任务或提前计算。 </li><li>时间trigger， 在google 推荐系统中， 使用大量google 基础设施建立用户行为画像， 这些用户行为画像会被用来根据兴趣来做推荐。注意的是， 这些系统使用处理时间进行数据驱动， 因为，这些系统需要经常更新，并且查看局部view 数据比等待直到当watermark 来了计算完整view 更重要。也就是说，对于一些时效要求高的系统， 必须具备根据处理时间来进行驱动的。</li><li>数据驱动 &amp; 组合trigger， 在MillWheel的论文中，我们描述了一种用来检测谷歌网站搜索查询趋势的异常探测系统。当我们为模型设计触发器的时候，这种微分异常探测系统启发我们设计了数据驱动触发器。这种微分探测器检测网站检索流，通过统计学估计来计算搜索查询请求量是否存在一个毛刺。如果系统认为一个毛刺即将产生，系统将发出一个启动型号。当他们认为毛刺已经消除，那么他们会发出一个停止信号。尽管我们可以采用别的方式来触发计算，比如说Trill的Punctuations，但是对于异常探测你可能希望一旦系统确认有异常即将发生，系统应该立即输出这个判断。Punctuations的使用事实上把流处理系统转换成了微批次处理系统，引入了额外的延迟。在调查过一些用户场景后，我们认为Punctuations不完全适合我们。因此我们在模型中引入了可定制化数据驱动触发器。同时这个场景也驱使我们支持触发器组合，因为在现实场景中，一个系统可能在处理多种微分计算，需要根据定义的一组逻辑来支持多种多样的输出。图9中的AtCount触发器是数据驱动触发器的例子，而图10-14使用了组合触发器。</li></ol><span id="more"></span><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>无限，乱序， global-scale 的数据处理需求不断增长， 与此同时，消费者提出复杂的业务需求，如event-time ordering, window特性，并且追求越快越好。 实际上， 一个系统很难完全从正确性，latency和成本上完全满足所有需求， 因此，不同的系统带来不同的解决方案，不同的tradeoff。 </p><p>数据处理的未来是无边界数据处理。 尽管有边界数据的处理永远都有着重要地位并且有用武之地，但是语义上它会被无边界数据处理模型所涵盖。一方面，无边界数据处理技术发展上步履蹒跚，另一方面对于数据进行处理并消费的要求在不断提高，比如说，需要对按事件发生时间对数据处理，或者支持非对齐窗口等。要发展能够支撑未来业务需要的数据处理系统，当前存在的系统和模型是一个非常好的基础，但我们坚持相信如果要完善地解决用户对无边界数据处理的需求，我们必须根本地改变我们的思维。</p><p>根据我们多年在谷歌处理大规模无边界数据的实践经验，我们相信我们提出的模型一个非常好的进展。它支持非对齐，事件发生时间窗口。这些都是当前用户所需要的。它提供了灵活的窗口触发机制，支持窗口累积和撤回，把关注点从寻求等待数据的完整性变为自动适应现实世界中持续变更的数据源。它对批处理，微批次，流处理提供了统一的抽象，允许数据开发人员灵活从三者中选择。同时，它避免了单一系统容易把系统本身的构建蔓延到数据处理抽象层面中去的问题。它的灵活性让数据开发者能根据使用场景恰当地平衡数据处理的准确性，成本和延迟程度。对于处理多样化的场景和需求来说，这一点很关键。最后，通过把数据处理的逻辑划分为计算什么，在哪个事件发生时间范围内计算，在什么处理时间点触发计算，如何用新的结果订正之前的数据处理结果让整个数据处理逻辑透明清晰。我们希望其他人能够认同这个模型并且和我们一起推进这个复杂而又令人着迷的领域的发展。</p><p>Dataflow 提出一种基础的方式来满足这些需求。 dataflow 并没有将无限的数据切分成有限的数据集，相反地，假定永远不清楚什么时候数据已经结束， 当新数据来时， 老的数据做retracted， 唯一的解决这个问题的方式是通过一个原则性的抽象来对 正确性／latency／cost 做一个合理的tradeoff。</p><p>本文提出一种方式 dataflow model， 详细的语法，核心原则，model validation（实践开发中对模型的检验）。 </p><h1 id="介绍"><a href="#介绍" class="headerlink" title="介绍"></a>介绍</h1><p>现代数据处理系统是一个复杂和令人兴奋的领域， 从最开始的MapReduce 到它的后继者 hadoop，pig，hive，spark，包括各种sql 社区的流引擎，然而，现存的现代计算系统仍然存在一些基础缺点。</p><p>举个例子： 一个streaming video提供商 想要统计 他要支付多少money给内容提供商，并对广告商收取多少费用。这个平台需要支持在线和离线的数据，  video 提供商需要知道每天收取广告商多少money，同时汇总video和广告的各种统计参数， 同时，他想基于历史数据运行一些离线实验。广告／内容 提供商也想知道 他们哪些video／广告被看过，看的频率，看的时长，以及观看者的分类。更关键，他们也想知道他们收费／支付多少money，他们期望越快越好拿到这些信息， 这样他们可以尽快调整预算，投标，改变策略。 因为涉及到money， 因此准确性是基本要求。</p><p>数据处理系统天然很复杂，但video 提供商还是期望一个简单并具备伸缩性的编程模型。 他们同样要求系统能够处理全球 scale的离散数据。</p><p>这个use case是每个video的观看时间和观看时长， 观看者是谁， 哪些内容或广告被看过。 然而，现有的系统依旧不能满足这些需求。 </p><ul><li>MR&#x2F;FlumeJava&#x2F;Spark&#x2F;hive&#x2F;pig&#x2F;  延迟不能满足需求</li><li>Aurora&#x2F;TelegraphCQ&#x2F;Niagara&#x2F;Esper, 在容错性上不行或者代价比较大</li><li>storm&#x2F;samza&#x2F;pulsar 不支持exactly-once 语义， pulsar 具备一定的scalable，并且支持high-level session的非对齐window的概念</li><li>tigon 不支持window 语义</li><li>spark-streaming&#x2F;sonora&#x2F;trident 只支持处理时间或tuple时间的window</li><li>sqlstream 必须强顺序性</li><li>flink， event－time触发机制有些限制， </li><li>CEDR&#x2F;Trill 可以提供准时的触发和增量模型， 但window语义不能完全表达session， 另外定期的打标（punctuation）不足够</li><li>mill wheel 和spark streaming底层是够scalable／fault－tolerant／low－latency，但缺乏应用层编程框架， 在event－time session上还有欠缺</li><li>lamada架构能够满足这些需求， 但需要维护2套系统</li><li>summingbird通过提供一套复杂的接口来抽象batch和streaming， 为了让某些类型计算可以在2套系统上运行，增加了大量的限制， 同样需要维护2套系统。</li></ul><p>上面这些问题并不是无解问题，随着时间推移， 这些系统的开发者会克服这些缺陷。但有一个观念上的错误， 这些系统对于输入的数据上， 认为输入的数据在某个时间点上是complete。我们认为这是一个基本错误， 现实中存在海量，乱序的dataset冲突和消费者的时效要求。因此需要一个简单但powerful的框架，在解决用户的需求的同时来平衡正确性／时延／成本。我们相信任何一个想要大量应用的解决方案必须 简单但强大， 并且以一个合适的成本很好平衡正确性和latency。 最近，我们是时候超越现有流行计算引擎语义， 正确的设计并构建batch， micro-batch和streaming 系统， 这些系统能够提供相同的正确性，3者并能广泛使用在无限的数据处理中。 </p><p>本文核心是一个统一的模型：</p><ul><li>在一个无限，乱序的数据源， 进行计算， 并支持event-time 顺序的结果， 根据feature 执行window操作， 并优化正确性，latency和成本。</li><li>对pipeline 实现以4个维度进行分解， 提供clarity（清晰）， composability（可组合性）和flexibility<ul><li><b>what</b> result are being computed</li><li><b>where</b> in event time they are being computed </li><li><b>when</b> in processing time they are materialized</li><li><b> How</b> earlier results relate to later refinements</li></ul></li><li>将逻辑数据处理与底层的物理实现独立开， 从而通过选择batch， micro-batch或streaming 引擎，来选择不同的侧重点， 正确性／延迟／成本。</li></ul><p>具体上， 本文讨论：</p><ul><li>windowing model， 支持unaligned event-time window, 展示一组简单的api 创建和使用window</li><li>triggering model， 根据流水线的runtime 特性来决定输出结果次数， 使用一个powerful并且flexible 声明式API 来描述trigger 语义。</li><li>增量处理模型incremental processing model， 集成retraction／update到 windowing／triggering 模型上</li><li>scalable implementation, 在mill wheel 流引擎和flumejava batch 引擎上，重新实现cloud dataflow， 包含一个open-source 的sdk</li><li>一组核心原则， 指导这种模型的设计</li><li>简单讨论google 在海量，无限，乱序数据处理的经验</li></ul><p>最后值得提一下， 这个模型也没有神奇的地方， 现有强一致的batch，micro-batch，streaming或lambda系统中不切实际的东西仍然存在， cpu&#x2F;ram&#x2F;disk 限制依旧存在。这个模型是一种框架， 这个框架可以相对简单表达并行计算， 用一种独立底层引擎的方式， 同样它也提供能力切入精确latency， 切入正确性因数据和资源导致的实际问题。 它的目标是简化构建实际，海量数据处理流水线。</p><h2 id="unbounded-bounded-vs-streaming-batch"><a href="#unbounded-bounded-vs-streaming-batch" class="headerlink" title="unbounded&#x2F;bounded vs streaming&#x2F;batch"></a>unbounded&#x2F;bounded vs streaming&#x2F;batch</h2><p>当描述 无限／有限数据集时， 我们倾向使用字符unbounded／bounded， 而不是stream／batch 上， 因为后面会暗示特定的执行引擎。 batch系统可以重复执行来处理unbounded数据集， 同样好的设计streaming 系统同样可以处理bounded 数据。 这个角度上， streaming和batch系统的区别是不相干的。</p><h2 id="windowing"><a href="#windowing" class="headerlink" title="windowing"></a>windowing</h2><p>window 将数据集切成有限的块，并作为一组数据来处理。 当处理无限数据（unbounded）时， 一些operation（aggregation， outer join， time-bouunded operation）需要window，然后另外一些operation（filter，mapping， inner join 等）没有需求。 对于有限数据集， window基本上是可选的， 尽管在一些场景中语义上是有帮助。 window一般是基于时间的， 不过一些系统支持基于tuple的window， 它实际上是在逻辑时间领域上的基于时间window， 并且按顺序的元素在逻辑时间上连续增长。 window 有可能是aligned（window time应用在所有数据上）， 有可能unalign（window of time 应用在部分数据上）。 下图展示了3种window类型。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/windowtype.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <h3 id="Fixed-window-tumbling-window"><a href="#Fixed-window-tumbling-window" class="headerlink" title="Fixed window&#x2F;tumbling window"></a>Fixed window&#x2F;tumbling window</h3><p>定义一个静态的window大小，比如小时或天window。 他们一般是aligned， 每个window 贯穿对应时间的数据。 有的时候，为了分摊window完成时的压力， 系统会shift window以一个随机值。</p><h3 id="sliding-window"><a href="#sliding-window" class="headerlink" title="sliding window"></a>sliding window</h3><p>定义一个window size和slide 时间， 比如window为小时，滑动为分钟。当滑动时间小于窗口大小时，会有一些overlap。 划窗（sliding window）一般也是aligned。 fixed window其实就是划窗的一种特殊场景，sliding 大小等于窗口大小。</p><h3 id="session-window"><a href="#session-window" class="headerlink" title="session window"></a>session window</h3><p>session window是某段时间，数据是活跃的。 通常会定义个timeout gap。 event 时间跨度小于timeout 时间的event 会组成一个session。 session通常是unaligned。 在本例中， window 2 只应用在key 1， window 3 只应用在key 2， window 1和4 应用在key 3上。</p><h2 id="时间领域-time-domains"><a href="#时间领域-time-domains" class="headerlink" title="时间领域 time domains"></a>时间领域 time domains</h2><p>有2种时间领域。 </p><ul><li><b>event time</b>, event 本身出现的时间， 比如系统clock time， 当系统产生这个event的时间</li><li><b>process time</b> 在流水线中 处理的时间， 比如系统当前时间。 注意，并没有假设系统中所有的时间同步。</li></ul><p>event time 从来不变，process time 不停会变， 因为event会随着流转整个pipeline而导致时间会不停向前走。</p><p>在处理过程中， 因为系统的真实使用（communication dely， 调度算法， 用于处理时间， 流水线序列化 等等）导致这两种时间发生差异并且动态变化。 全局进度metrics比如打标（punctuations）或者watermark，提供一个好方式展示这种差异。 我们采用像millwheel的watermark， 它是一种时间戳，在event time上的下限值，小于这个时间戳的都已经被处理. 完成的概念并不兼容正确性， 我们因此并不依赖watermark（来保证正确性）。 系统常常使用一种有用的方式， 系统会认为截止到一个指定的event time前所有的数据已经接收， 因此应用既可以visualizing skew（可视化时间差），又可以监控系统整体的健康和进度， 同时如果不依赖完全正确性的情况下，可以做一些决策，比如常见的垃圾回收。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/timeskew.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>在理想情况下， time domain skew（process time和event time 差异）是为0， 只要event一出现，系统会处理event。 现实并非如此美好， 如上图所示， 从12点开始， watermark都滞后一段时间。 上图在分布式系统中是非常正常的现象， 并且在考虑提供准确性和可重复的result时， 必须要考虑这种情况。</p><h1 id="dataflow-model"><a href="#dataflow-model" class="headerlink" title="dataflow model"></a>dataflow model</h1><p>这章将介绍dataflow model，并解释为什么语义对batch， micro-batch，streaming是通用的。 我们将展示dataflow java sdk （它从flumejava api 演化而来）</p><h2 id="core-primitives"><a href="#core-primitives" class="headerlink" title="core primitives"></a>core primitives</h2><p>开始之前，先想想经典batch 模型的基本元素。 dataflow sdk 有2个核心 transform 操作在(key, value).</p><h3 id="ParDo"><a href="#ParDo" class="headerlink" title="ParDo"></a>ParDo</h3><p>ParDo 是普通的并发处理。 将输入的数据传递给用户的代码， 每个input可能产生0或多个output elements。 在无限数据中， pardo 操作每个input上元素，可能转化为无限的数据。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/pardo.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <h3 id="GroupByKey"><a href="#GroupByKey" class="headerlink" title="GroupByKey"></a>GroupByKey</h3><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/pardo.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>GroupByKey 在发送结果到下游进行reduce前，收集一个key的所有数据。 如果输入数据是无限的，系统不知道什么时候结束。 常见的解决方案是window 这些数据。</p><h2 id="windowing-1"><a href="#windowing-1" class="headerlink" title="windowing"></a>windowing</h2><p>系统重新定义GroupByKey operation 为 GroupByKeyAndWindow. 第一个要做的是支持unaligned window， 因为有可能有2个key 视图。第一个key是它简单把所有window strategies处理为unaligned 并让底层实现对aligned case使用相关优化。 第二个key是window可以切分为2个相关的操作：</p><ul><li>Set<Window> AssignWindows(T datum), 它分配这个元素到0个或多个window上，  这是基本的bucket操作</li><li>Set<Window> MergeWindows(Set<Window> windows)， 在grouping时 merge window。 当数据到达并被group 一起时， 可以在时间上进行构建数据驱动的window。</li></ul><p>对于任何给定的window strategy， 这两种operation是紧密相关。 滑窗assignment需要滑窗merging， session window assignment需要session window merging。 </p><p>为了天然支持event-time window, 修改数据结构为(key, value, event time, window), 每个元素都会带event-time并被初始化到全局window， covering 所有的event time， 这种方式match 默认的标准batch 方式。</p><h3 id="window-assignment"><a href="#window-assignment" class="headerlink" title="window assignment"></a>window assignment</h3><p>window assignment会copy一份新的元素， 每个元素会分配自己的window。 举例来说， 如下图所示，划窗中window为2分钟长度和移动步长为1分钟。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/windowassign.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p> window 同它的元素关联起来， 这表示， 在流水线使用grouping之前的任何地方都可以做window assignment操作。 这很重要， 在一个合成转换中（如Sum.integersPerKey()）有可能将grouping 操作隐藏在下游的什么地方。</p><h3 id="window-merging"><a href="#window-merging" class="headerlink" title="window merging"></a>window merging</h3><p>windows merging是GroupByKeyAndWindow的一部分。 如下例所示：</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/windowmerge.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>上图中， 做session window， session window的timeout为30分钟。 一开始都放在全局window中。 AssignWindows 会把每个元素放到一个window中（在它自己的timestamp 延伸30分钟）。 然后开始GroupByKeyAndWindow， 实际上它进行了5步组合操作：</p><ul><li>DropTimestamps， 丢掉元素的timestamp，因为window是从现在到后面（30分钟），后续的计算只关系窗口</li><li>GroupByKey, 按key 进行group(value, window)</li><li>MergeWindows, 按照一个key merge 一组window。 实际的merge逻辑由window strategy 来决定。 因为v1 和v4 overlap了， 所以merge 它们到一个新的，更大的session</li><li>GroupAlsoByWindow， 对每个key，按照window进行group 操作。 v1和v4是相同window，因此group在一起</li><li>ExpandToElements， 将每个group 扩展为(key, value, event time, window)，在本例，设置timestamp为window的结束时间。 任何timestamp 大于或等于 window中最早event的timestamp都是有效的，符合watermark正确性。</li></ul><h3 id="api"><a href="#api" class="headerlink" title="api"></a>api</h3><p>下例中：</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/apisimple.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>在求和之前，增加一个Window.into 来生成时长30分钟的session window</p><h2 id="Triggers-／-Incremental-Processing"><a href="#Triggers-／-Incremental-Processing" class="headerlink" title="Triggers ／ Incremental Processing"></a>Triggers ／ Incremental Processing</h2><p>构建unaligned／event-time window是一种改进，但有2个缺点：</p><ul><li>需要支持tuple- 和processing-time-based 的window， 否则会退到和其他系统一样。</li><li>需要知道什么时候emit 一个window的result。 因为event-time是乱序， 需要某种signal 告诉我们window 完成了。</li></ul><p>下一节介绍如何解决第一个问题。 对于第二个问题解决方案，最初倾向使用如watermark 这样的全局event-time 进度metric。 然后watermark 对于正确性存在2个缺点：</p><ul><li>有的时候太快， 意味着有些数据晚于watermark到达。 对于分布式系统，很难获得一个完美的event-time watermark， 如果用户想要100%的正确性，很难通过它来达到。</li><li>太慢， 因为它是一个全局进度metric， 因为一个慢的数据可能就拖慢整个pipeline的watermark。 即使是健康的变化少的pipleline， 基线仍然有可能是几分钟甚至更多。 因此，使用watermark作为emit window signal容易导致高的latency。</li></ul><p>假定单独watermark是不够的， 一个可用的方式是lambda架构的高效sidesteps（步进）， 它并没有解决完成问题，但更快提供正确答案。 他提供像streaming流水线一样的更好的低延迟结果，一旦batch pipleline运行则可以保证最终一致性和正确性。如果我们想要通过单个流水线达到同样的效果， 我们需要为任何一个window提供多个答案。 我们称这种特性为trigger， 设定一个说明，什么时候为任何一个window trigger输出结果。</p><p>trigger是一种机制， 当接收到内部或外部的信号输出GroupByKeyAndWindow 的结果。 他们补充window 模型， 他们通过不同的时间轴影响了系统行为：</p><ul><li>windowing 用event time 决定什么地方进行grouped。</li><li>triggering 用processing time 来决定什么时候输出结果</li></ul><p>dataflow 预定义了一套trigger 实现 来trigger 完成评估（比如 watermark， 包括百分比watermark， 他提供有用的语义来处理在batch和streaming引擎延迟的数据，当用户更关注快速处理小比例的数据而不是最后一块数据）。基于processing time， 基于数据到达情况（记录数，字节数，data punctuations， 数据匹配模式等）。 dataflow 同样支持嵌入trigger到 逻辑联合（and／or）， loops，sequences 和其他这种构建。 除此之外， 用户可以定义他们自己的trigger， 可以基于底层的primitives（watermark timer， processing timer， data arrival， composition support）或任何外部相关signal。<br>除了控制什么时候emit result， trigger系统控制一个window的多个pane如何相互关联， 通过3种定义模型：</p><ul><li>discarding, window 内容会被丢弃， 后面的结果不会影响前面的结果。 当下游消费者期望大量的trigger是相互独立时（比如，对inject 到系统的数据做一个sum计算）， 这种模型是有用的。尽管联合和交换的操作可以潜入到dataflow的Combiner 里面， 这是在缓存数据的最有效方式， dela的效率会最小化。在我们的video session例子中， 这样是不够的， 因为要求下游消费者拼装(stitch)部分数据。</li><li>accumulating， window的数据会存到存储中， 后面的结果会对之前的结果进行矫正。 当接收一个window的多个结果时， 下游消费者期望后面的结果能够覆盖之前的结果， 这种方式会非常有用。另外在lambda架构中这种方式很高效， streaming 流水线产生的低延迟的结果会被后面batch 流水线运行的结果给覆盖掉。 在我们的vedio session 例子中， 这种方式可能够用， 如果我们简单计算session，然后更新到支持更新的存储中（比如数据库或kv store）</li><li>accumulating 和retraction， 在accumulating语义上， 一个emitted 拷贝值仍然会存储到持久化存储中。 当后面又触发window trigger， 之前值的retraction会首先发送出去， 紧接着新的计算值。 当流水线中存在多个串行的GroupByKeyAndWindow操作时很有必要这种模式， 一个window产生的多个结果， 因为由一个window产生的多个结果在下游做group的key上结束（没理解这句话什么意思）。那种情况下， 第二个grouping的操作将会产生错误的结果， 除非那些key被通知一个retraction， 从而原始值的影响会被去掉。 dataflow Combiner 的相反操作uncombine 支持retraction。对于video session例子来说，这种方式是最理想的。</li></ul><h2 id="example"><a href="#example" class="headerlink" title="example"></a>example</h2><p>考虑下例中，做整数求和：</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/apisimple.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>我们假设从某个数据源我们观察到了10个数据点，每个数据点都是一个比较小的整数。我们会考虑有边界输入源和无边界输入源两种情况。为了画图简单，我们假设这些数据点的键是一样的，而生产环境里我们这里所描述的数据处理是多个键并行处理的。图5展示了数据在我们关心的两个时间轴上的分布。X轴是事件发生时间（也就是事件发生的时间），而Y轴是处理时间（即数据管道观测到数据的时间）。（译者注：圆圈里的数值是从源头采样到的数值）除非是另有说明，所有例子假设数据的处理执行都是在流处理引擎上。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/exampleinput.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>很多例子都要考虑水位线，因此我们的图当中也包括了理想的水位线，也包括了实际的水位线。直的虚线代表了理想的水位线，即，事件发生时间和数据处理时间不存在任何延迟，所有的数据一产生就马上消费了。不过考虑到分布式系统的不确定性，这两个时间之间有偏差是非常普遍的。在图5中，实际的水位线（黑色弯曲虚线）很好的说明了这一点。另外注意由于实际的水位线是猜测获得的，因此有一个迟到比较明显的数据点落在了水位线的后面。</p><p>如果我们在传统的批处理系统中构建上述的对数据进行求和的数据处理管道，那么我们会等待所有的数据到达，然后聚合成一个批次（因为我们现在假设所有的数据拥有同样的键），再进行求和，得到了结果51。如图6所示黑色的长方形是这个运算的示意图。长方形的区域代表求和运算涵盖的处理时间和参与运算的数据的事件发生时间区间。长方形的上沿代表计算发生，获得结果的管道处理时间点。因为传统的批处理系统不关心数据的事件发生时间，所有的数据被涵盖在一个大的全局性窗口中，因此包含了所有事件发生时间内的数据。而且因为管道的输出在收到所有数据后只计算一次，因此这个输出包含了所有处理时间的数据（译者注：处理时间是数据系统观察到数据的时间，而不是运算发生时的时间）。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/classicbatch.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>注意上图中包含了水位线。尽管在传统批处理系统中不存在水位线的概念，但是在语义上我们仍然可以引入它。批处理的水位线刚开始时一直停留不动。直到系统收到了所有数据并开始处理，水位线近似平行于事件发生时间轴开始平移，然后一直延伸到无穷远处。我们之所以讨论这一点，是因为如果让流处理引擎在收到所有数据之后启动来处理数据，那么水位线进展和传统批处理系统是一模一样的。（译者注：这提示我们其实水位线的概念可以同样适用于批处理）</p><p>现在假设我们要把上述的数据处理管道改造成能够接入无边界数据源的管道。在Dataflow模型中，默认的窗口触发方式是当水位线移过窗口时吐出窗口的执行结果。但如果对一个无边界数据源我们使用了全局性窗口，那么窗口就永远不会触发（译者注：因为窗口的大小在不停地扩大）。因此，我们要么用其他的触发器触发计算（而不是默认触发器），或者按某种别的方式开窗，而不是一个唯一的全局性窗口。否则，我们永远不会获得计算结果输出。</p><p>我们先来尝试改变窗口触发方式，因为这会帮助我们产生概念上一致的输出（一个全局的包含所有时间的按键进行求和），周期性地输出更新的结果。在这个例子中，我们使用了Window.trigger操作，按处理时间每分钟周期性重复触发窗口的计算。我们使用累积的方式对窗口结果进行修正（假设结果输出到一个数据库或者KV数据库，因而新的结果会持续地覆盖之前的计算结果）。这样，如图7所示，我们每分钟（处理时间）产生更新的全局求和结果。注意图中半透明的输出长方形是相互重叠的，这是因为累积窗格处理机制计算时包含了之前的窗口内容。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/globalaccumulate.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>如果我们想要求出每分钟的和的增量，那么我们可以使用窗格的抛弃模式，如图8所示。注意这是很多流处理引擎的处理时间窗口的窗口计算模式。窗格不再相互重合，因此窗口的结果包含了相互独立的时间区域内的数据.</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/globaldiscard.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>另外一种更健壮的处理时间窗口的实现方式，是把数据摄入时的数据到达时间作为数据的事件发生时间，然后使用eventtime window。这样的另一个效果是系统对流入系统的数据的事件发生时间非常清楚，因而能够生成完美的水位线，不会存在迟到的数据。如果数据处理场景中不关心真正的事件发生时间，或者无法获得真正的事件发生时间，那么采用这种方式生成事件发生时间是一种非常低成本且有效的方式。</p><p>在我们讨论其他类型的窗口前，我们先来考虑下另外一种触发器。一种常见的窗口模式是基于记录数的窗口。我们可以通过改变触发器为每多少条记录到达触发一次的方式来实现基于记录数的窗口。图9是一个以两条记录为窗口大小的例子。输出是窗口内相邻的两条记录之和。更复杂的记录数窗口（比如说滑动记录数窗口）可以通过定制化的窗口触发器来支持。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/globaldiscardatcount.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>我们接下来考虑支持无边界数据源的其他选项，不再仅仅考虑全局窗口。一开始，我们来观察固定的2分钟窗口，累积窗格。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/unboundfixedwindow.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>水位线触发器是指当水位线越过窗口底线时窗口被触发。我们这里假设批处理和流处理系统都实现了水位线（详见3.1）。Repeat代表的含义是如何处理迟到的数据。在这里Repeat意味着当有迟于水位线的记录到达时，窗口都会立即触发再次进行计算，因为按定义，此时水位线早已经越过窗口底线了。</p><p>图10-12描述了上述窗口在三种不同的数据处理引擎上运行的情况。首先我们来观察下批处理引擎上这个数据处理管道如何执行的。受限于我们当前的实现，我们认为数据源现在是有边界的数据源，而传统的批处理引擎会等待所有的数据到来。之后，我们会根据数据的事件发生时间处理，在模拟的水位线到达后窗口计算触发吐出计算结果。整个过程如图10所示</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/fixedbatch.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>然后来考虑一下微批次引擎，每分钟做一次批次处理。系统会每分钟收集输入的数据进行处理，反复重复进行。每个批次开始后，水位线会从批次的开始时间迅速上升到批次的结束时间（技术上来看基本上是即刻完成的，取决于一分钟内积压的数据量和数据处理管道的吞吐能力）。这样每轮微批次完成后系统会达到一个新的水位线，窗口的内容每次都可能会不同（因为有迟到的数据加入进来），输出结果也会被更新。这种方案很好的兼顾了低延迟和结果的最终准确性。如图11所示：</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/fixedmicrobatch.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div> <p>接下来考虑数据管道在流处理引擎上的执行情况，如图12所示。大多数窗口在水位线越过它们之后触发执行。注意值为9的那个数据点在水位线之后到达。不管什么原因（移动设备离线，网络故障分区等），系统并没有意识到那一条数据并没有到达，仍然提升了水位线并触发了窗口计算。当值为9的那条记录到达后，窗口会重新触发，计算出一个新的结果值。</p><p>如果说我们一个窗口只有一个输出，而且针对迟到的数据仅做一次的修正，那么这个计算方式还是不错的。不过因为窗口要等待水位线进展，整体上的延迟比起微批次系统可能要更糟糕，这就是我们之前在2.3里所说的，单纯依赖水位线可能引起的问题（水位线可能太慢）</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/fixedstream.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div><p>如果我们想降低整体的延迟，那么我们可以提供按数据处理时间的触发器进行周期性的触发，这样我们能够尽早得到窗口的计算结果，并且在随后得到周期性的更新，直到水位线越过窗口边界。参见图13。这样我们能够得到比微批次系统更低的延迟，因为数据一到达就进入了窗口随后就可能被触发，而不像在微批次系统里必须等待一个批次数据完全到达。假设微批次系统和流处理系统都是强一致的，那么我们选择哪种引擎，就是在能接受的延迟程度和计算成本之间的选择（对微批次系统也是批大小的选择）。这就是我们这个模型想要达到的目标之一。参见图13：固定窗口，流处理，部分窗格</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/fixedstreampartial.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div><p>作为最后一个例子，我们来看一下如何支持之前提到的视频会话需求（为了保持例子之间的一致性，我们继续把求和作为我们的计算内容。改变成其他的聚合函数也是很容易的）。我们把窗口定义为会话窗口，会话超时时间为1分钟，并且支持retraction操作。这个例子也体现了我们把模型的四个维度拆开之后带来的灵活的可组合性（计算什么，在哪段事件发生时间里计算，在哪段处理时间里真正触发计算，计算产生的结果后期如何进行修正）。也演示了对之前的计算结果可以进行撤回是一个非常强力的工具，否则可能会让下游之前接收到的数据无法得到修正。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" >                <img data-src="/img/dataflow/sessionretraction.jpg"  alt="The Dataflow Model： Google Dataflow 编程模型"></div><p>在这个例子中，我们首先接收到了数据5 和数据7。由于5和7之间事件发生时间大于1分钟，因此被当做了两个会话。在第一次窗口被触发时，产生了两条计算结果，和分别为5和7。在第二个因处理时间引起的窗口触发时，我们接收到了数据3,4,3，并且第一个3和上一个7之间时间大于1分钟，因此被分组到一个新的会话窗口，窗口触发计算并输出了计算结果10。紧接着，数据8到达了。数据8的到达使得数据7,3,4,3,8合并成了一个大窗口。当水位线越过数据点8后，新窗口计算被触发。触发后需要先撤回之前两个小窗口的计算结果，撤回方式是往下游发送两条键为之前的两个会话标记，值为-7和-10的记录，然后发送一个新的值为25的新窗口计算结果。同样，当值为9的记录迟于水位线到达后，之前的所有7条记录都合并成了一个会话，因此要对之前的会话再次进行撤回。值为-5和-25的记录又被发送往下游，新的值为39的会话记录随后也被发往下游。</p><p>同样的操作在处理最后3条值为3,8,1的记录时也会发生，先是输出了结果值3，随后回撤了这个计算结果，输出了合并会话后的结果值12。</p><h1 id="3-实现和设计"><a href="#3-实现和设计" class="headerlink" title="3. 实现和设计"></a>3. 实现和设计</h1><h2 id="3-1-实现"><a href="#3-1-实现" class="headerlink" title="3.1 实现"></a>3.1 实现</h2><p>我们已经用FlumeJava实现了这个模型，使用MillWheel作为底层的流执行引擎；在本文写作的时候，针对公有云服务Cloud Dataflow的重新实现也接近完成。由于这些系统要么是谷歌的内部系统，要么是共有云服务，因此为简洁起见，实现的细节我们略掉了。可以提及的让人感兴趣的一点是，核心的窗口机制代码，触发机制代码是非常通用的，绝大部分都同时适用于批处理引擎实现和流处理引擎实现。这个实现本身也值得在将来进行更进一步的分析。</p><h2 id="3-2-设计原则"><a href="#3-2-设计原则" class="headerlink" title="3.2 设计原则"></a>3.2 设计原则</h2><p>尽管我们很多的设计其实是受到3.3节所描述的真实业务场景启发，我们在设计中也遵从了一系列的核心原则。这些原则我们认为是这个模型必须要遵循的。</p><ul><li>永远不要依赖任何的数据完整性标记（译者注：如水位标记）</li><li>灵活性，要能覆盖已知的多样化的使用用例，并且覆盖将来可能的使用用例</li><li>对于每个预期中的执行引擎，（模型抽象）不但要正确合理，而且要有额外的附加价值</li><li>鼓励实现的透明性</li><li>支持对数据在它们产生的上下文中进行健壮的分析。<br>可以这么说，下述的使用案例决定了模型的具体功能，而这些设计原则决定了模型整体的特征和框架。我们认为这两者是我们设计的模型具有完全性，普遍性的根本原因。</li></ul><h2 id="3-3-业务场景"><a href="#3-3-业务场景" class="headerlink" title="3.3 业务场景"></a>3.3 业务场景</h2><p>在我们设计Dataflow模型的过程中，我们考虑了FlumeJava和MillWheel系统在这些年遇到的各种真实场景。那些良好工作的设计，我们保留到了模型中，而那些工作不那么良好的设计激励我们采用新的方法重新设计。下面我们简单介绍一些影响过我们设计的场景。</p><h3 id="3-3-1-大规模数据回写和Lambda架构；统一模型"><a href="#3-3-1-大规模数据回写和Lambda架构；统一模型" class="headerlink" title="3.3.1 大规模数据回写和Lambda架构；统一模型"></a>3.3.1 大规模数据回写和Lambda架构；统一模型</h3><p>有一些团队在MillWheel上跑日志链接作业。这其中有一个特别大的日志链接处理作业在MillWheel上按流模式运行，而另外一个单独的FlumeJava批处理作业用来对流处理作业的结果进行大规模的回写。一个更好的设计是使用一个统一的模型，对数据处理逻辑只实现一次，但是能够在流处理引擎和批处理引擎不经修改而同时运行。这是第一个激发我们思考去针对批处理，微批次处理和流处理建立一个统一模型的业务场景。这也是图10-12所展示的。</p><p>另外一个激发我们设计统一模型的场景是Lambda架构的使用。尽管谷歌大多数数据处理的场景是由批处理系统和流处理系统分别单独承担的，不过有一个MillWheel的内部客户在弱一致性的模式下运行他们的流处理作业，用一个夜间的MR作业来生产正确的结果。他们发现他们的客户不信任弱一致性的实时结果，被迫重新实现了一个系统来支持强一致性，这样他们就能提供可靠的，低延时的数据处理结果。这个场景进一步激励我们能支持灵活地选择不同的执行引擎。</p><h3 id="3-3-2-非对齐窗口：会话"><a href="#3-3-2-非对齐窗口：会话" class="headerlink" title="3.3.2 非对齐窗口：会话"></a>3.3.2 非对齐窗口：会话</h3><p>从一开始我们就知道我们需要支持会话；事实上这是我们窗口模型对现有模型而言一个重大的贡献。会话对谷歌来说是一个非常重要的使用场景（也是MillWheel创建的原因之一）。会话窗口在一系列的产品域中都有应用，如搜索，广告，分析，社交和YouTube。基本上任何关心把用户的分散活动记录进行相互关联分析都需要通过会话来进行处理。因此，支持会话成为我们设计中的最重要考虑。如图14所示，支持会话在Dataflow中是非常简单的。</p><h3 id="3-3-3-支付：触发器，累加和撤回"><a href="#3-3-3-支付：触发器，累加和撤回" class="headerlink" title="3.3.3 支付：触发器，累加和撤回"></a>3.3.3 支付：触发器，累加和撤回</h3><p>有两个在MillWheel上跑支付作业的团队遇到的问题对模型的一部分也有启发作用。当时我们的设计实践是使用水位线作为数据完全到达的指标。然后写额外的逻辑代码来处理迟到的数据或者更改源头数据。由于缺乏一个支持更新和撤回的系统，负责资源利用率方案的团队最终放弃了我们的平台，构建了自己独立的解决方案（他们最后使用的模型和我们同时设计开发的模型事实上非常类似）。另一个支付团队的数据源头有少部分缓慢到达的数据，造成了水位线延迟，这给他们带来了大问题。这些系统上的缺陷成为我们对现有系统需要进行改良设计的重要动因，并且把我们的考虑点从保证数据的完整性转移到了对迟到数据的可适应性。对于这个场景的思考总结带来了两个方面：一个方面是能够精确，灵活地确定何时将窗口内容物化的触发器（如7-14所示），对同样的输入数据集也可以使用多种多样地结果输出模式进行处理。另外一方面是通过累积和撤回能够支持增量处理。（图14）</p><h3 id="3-3-4-统计计算：水位线触发器"><a href="#3-3-4-统计计算：水位线触发器" class="headerlink" title="3.3.4 统计计算：水位线触发器"></a>3.3.4 统计计算：水位线触发器</h3><p>很多MillWheel作业用来进行汇总统计（如平均延迟）。对这些作业来说，100%的准确性不是必须的，但是在合理的时间范围内得到一个接近完整的统计是必须的。考虑到对于结构化的输入（如日志文件），使用水位线就能达到很高程度的准确度。这些客户发现使用单次的的基于水位线的触发器就可以获得高度准确的统计。水位线触发器如图12所示。</p><p>我们有一些滥用检测的作业运行在MillWheel中。滥用检测是另外一种快速处理大部分数据比缓慢处理掉所有数据要远远更有价值的场景。因此，他们会大量地使用水位线百分位触发器。这个场景促使我们在模型中加入了对水位线百分位触发器的支持。</p><p>与此相关的，批处理作业中的一个痛点是部分处理节点的缓慢进度会成为执行时间中的长尾，拖慢整个进度。除了可以通过动态平衡作业来缓解这个问题，FlumeJava也支持基于整体完成百分度来选择是否终止长尾节点。用统一模型来描述批处理中遇到的这个场景的时候，水位线百分位触发器可以很自然地进行表达，不需要在引入额外的定制功能、定制接口。</p><h3 id="3-3-5-推荐：处理时间触发器"><a href="#3-3-5-推荐：处理时间触发器" class="headerlink" title="3.3.5 推荐：处理时间触发器"></a>3.3.5 推荐：处理时间触发器</h3><p>另外一种我们考虑过的场景是从大量的谷歌数据资产中构建用户活动树（本质上是会话树）。这些树用来根据用户的兴趣来做推荐。在这些作业中我们使用处理时间作为触发器。这是因为，对于用户推荐来说，周期性更新的，即便是基于不完备数据的用户活动树比起持续等待水位线越过会话窗口边界（即会话结束）获得完全的数据要有意义的多。这也意味着由于部分少量数据引起的水位线进展延迟不影响基于其他已经到达的数据进行计算并获得有效的用户活动树。考虑到这种场景，我们包含了基于处理时间的触发器（如图7和图8所示）</p><h3 id="3-3-6-异常探测：数据驱动和组合触发器"><a href="#3-3-6-异常探测：数据驱动和组合触发器" class="headerlink" title="3.3.6 异常探测：数据驱动和组合触发器"></a>3.3.6 异常探测：数据驱动和组合触发器</h3><p>在MillWheel的论文中，我们描述了一种用来检测谷歌网站搜索查询趋势的微分异常探测数据处理管道。当我们为模型设计触发器的时候，这种微分异常探测系统启发我们设计了数据驱动触发器。这种微分探测器检测网站检索流，通过统计学估计来计算搜索查询请求量是否存在一个毛刺。如果系统认为一个毛刺即将产生，系统将发出一个启动型号。当他们认为毛刺已经消除，那么他们会发出一个停止信号（译者注：可能会对接系统自动对系统扩容或缩容）。尽管我们可以采用别的方式来触发计算，比如说Trill的标点符(Punctuations)，但是对于异常探测你可能希望一旦系统确认有异常即将发生，系统应该立即输出这个判断。标点符的使用事实上把流处理系统转换成了微批次处理系统，引入了额外的延迟。在调查过一些用户场景后，我们认为标点符不完全适合我们。因此我们在模型中引入了可定制化数据驱动触发器。同时这个场景也驱使我们支持触发器组合，因为在现实场景中，一个系统可能在处理多种微分计算，需要根据定义的一组逻辑来支持多种多样的输出。图9中的AtCount触发器是数据驱动触发器的例子，而图10-14使用了组合触发器。</p>]]>
    </content>
    <id>https://ilongda.com/2016/docs/paper/dataflow/</id>
    <link href="https://ilongda.com/2016/docs/paper/dataflow/"/>
    <published>2016-10-26T11:42:57.000Z</published>
    <summary>Google Dataflow 编程模型论文笔记：统一批流、窗口 trigger/watermark 及迟到数据处理与 Lambda 架构替代思路</summary>
    <title>The Dataflow Model： Google Dataflow 编程模型</title>
    <updated>2026-06-09T08:46:25.962Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="大数据" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="大数据" scheme="https://ilongda.com/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"Google Millwheel","description":"Google Millwheel 流处理论文笔记：exactly-once 容错、key 状态模型、checkpoint 与 BigTable sequencer 机制","image":"https://ilongda.com/img/millwheel/key.jpg","wordCount":6943,"datePublished":"2016-10-23T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.963Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2016/docs/paper/millwheel/"},"url":"https://ilongda.com/2016/docs/paper/millwheel/","inLanguage":"zh-CN","keywords":["论文","大数据"],"articleSection":["论文","大数据"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"Google Millwheel","item":"https://ilongda.com/2016/docs/paper/millwheel/"}]}</script><h1 id="插曲"><a href="#插曲" class="headerlink" title="插曲"></a>插曲</h1><p>google millwheel 《MillWheel: Fault-Tolerant Stream Processing at Internet Scale》， 论文应该2012年就推出来了， 以前总是快速扫一遍， 每次阅读都有一些不同的收获， 这次终于仔细拜读一下， 顺便将它翻译了一遍。</p><h1 id="设计目标"><a href="#设计目标" class="headerlink" title="设计目标"></a>设计目标</h1><ul><li>提供一种编程模型，可以满足复杂的计算逻辑而无需使用者是分布式计算专家<ul><li>framework能处理任何一条边或一个节点发生故障</li><li>可以保证数据处理仅被处理一次， 采用冥等的方式</li><li>用一种合适的粒度进行checkpoint， 从而可以避免外部sender 缓冲数据的buffer在多个checkpoint 之间长时间等待</li></ul></li><li>能够同时高效满足scalability和容错性</li><li>按照论文描述， 可以支持多语言</li></ul><h1 id="个人总结："><a href="#个人总结：" class="headerlink" title="个人总结："></a>个人总结：</h1><p>这篇论文，大篇幅介绍的如何做容错和扩展性， 扩展性很多时候是依赖容错性来做系统伸缩。 整体而言，扩展性和容错性应该非常不错， 但成本比较高，因为对持久化层需求很大， 而且比较偏好mini-batch设计，非常倾向去做window的aggregate， 另外对这个系统时延，感觉会在秒级以上。微软scope streaming 感觉和这个系统有很大的相似性， 不过scope 比较强调确定性（determinacy）。<br>整个设计里面一些亮点：</p><ul><li>整个dag中，组件（compuation）之间是完全解耦， 因此，可以自由对一个computation做迁移，扩容，合并。不过，这些都依赖底层的exactly-once 框架。 </li><li>系统数据传输都是通过rpc， 而非消息中间件， 并且组件（computation）之间可以自由订阅， 这一点超越了samza和scope。 </li><li>数据结构是(key, bytes[], timestamp)， 这个数据结构在后来的dataflow中发生了变化<ul><li>key 非常重要， 目前是对key 做group by， 相同的key保证在一个进程中串行执行， 并且每个用户自己写key-extractor 函数。 并且后续的所有操作／context，都是基于对应的key， 比如checkpoint，状态更新， 发送／更新 timer／low wwatermark。</li><li>bytes[], 用户自己选择序列化和反序列化</li><li>timestamp 完全由用户来确定，</li></ul></li><li>exactly-once<ul><li>当timer 来到时， 会做checkpoint， 这个checkpoint， 会在一个原子操作中， 把这次checkpoint的输入数据id 和输出数据 和状态更新， timer 全部更新到bigtable 中的一行， 如果有外部状态更新，用户需要自己保证冥等</li><li>后台存储更新需要一个sequencer， 拥有了sequencer token后，才能做数据库更新操作， 保证一个key，永远只有一个single-writer。</li><li>接收数据后，需要ack给发送方， 这样发送方可以做 input record id的清理工作。</li><li>系统会对输入消息进行去重</li></ul></li><li>low watermark<ul><li>通过一个外部全局系统injector来做lower watermark， 会订阅injector 来获取lower watermark。 </li><li>每个worker 从injector处获取输入源的low watermark，然后根据自己的工作状态，计算出自己的low watermark， 然后汇报给injector</li></ul></li></ul><span id="more"></span><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>基于模型的流式系统， 类似异常探测器，依赖于基于历史数据的预测，他们的模型必须当数据来临时，时刻更新. 按某个维度进行扩容这些系统不应该带来同等增量的成本增加。</p><p>Millwheel 定位于这样一个编程模型，定制于流式，低延时系统。用户实现dag节点中的应用逻辑， 这些dag可以用来定义任意并且动态topology，数据在dag中持续流动， millwheel 保证任何节点或dag中的任何边发生故障时，数据依然正确。作为系统容错性的一部分， 保证每一条record发送到它的消费者. Millwheel 提供api，通过一种幂等性方式，从用户的视角，保证record exactly-once。Millwhell用一个合适粒度（频率）对过程进行checkpoint， 消灭因为checkpoint之间长时间间隔，需要在外部senders上cache发送数据buffer的需求。</p><p>其他的流式系统并不提供容错，versatility(多功能性)，scalability的结合。Spark streaming和sonora 在高效checkpoint上做的很出色，但限制了用户代码可使用的operator的空间。S4 并不提供一定容错性。Storm 不能保证exactly-once, trident 要求严格的事务顺序。尝试对一些批处理模型，比如hadoop mapreduce为了达到低延时，会牺牲flexibility， 比如某些operator依赖的spark streaming rdd。Streaming sql 系统提供简洁的solution来解决常见的stream问题， 但要想直观抽象或复杂的应用逻辑还是必须使用特定语言而不是描叙语言比如sql.</p><p>我们提供一种流式系统编程模型和millwheel 的实现：</p><ul><li>定义一种编程模型， 允许用户创建一个复杂的流式系统而不需要丰富分布式系统的经验</li><li>millwheel的高效实现了scalable和容错性， 持久化状态。</li><li>按照论文描述， 可以支持多语言</li></ul><h1 id="设计需求"><a href="#设计需求" class="headerlink" title="设计需求"></a>设计需求</h1><p>google的zeitgeist 用于跟踪web queries的趋势。为了展示millwhell的feature， 我们examine the zeitgeist的要求。Zeitgeist 接受不间断的search queries， 执行异常探测， 尽可能快的输出哪些queries是突发（尖峰刺，spike），哪些queries是快速下滑（dip）。 系统为每个query建立一个历史模型，并对traffic做一个预期判断， 这个判断并不会引起反面影响。尽可能快的识别突发（spike）query或跌落(dip)query是非常重要的。Zeitgeist帮助google提供热点跟踪服务，google热点跟踪服务主要依赖新鲜信息。基本流水线如图所示![img](http://img3.tbcdn.cn/5476e8b07b923/TB1BoC4NVXXXXckXXXXXXXXXXXX)图1: 输入是持续不断的查询，输出是spiking或diping的查询</p><p>为了实现zeitgeist系统，合并record到每秒间隔的bucket， 并且比较实际traffic 和基于模型推测的预期流量。 假如持续出现数量不一致， 那么我们可以很确定这个query是spiking或dipping。同时， 我们用新数据更新模型并且存储他们以备将来使用。<h2 id="Persistent-storage"><a href="#Persistent-storage" class="headerlink" title="Persistent storage"></a>Persistent storage</h2><p>注意这个实现需要短期和长期的storage。 一个spike可能仅仅持续几秒钟，因此只依赖一个小时间窗口的state， 然而模型数据会几个月持续不断更新</p><h2 id="Low-watermarks"><a href="#Low-watermarks" class="headerlink" title="Low watermarks"></a>Low watermarks</h2><p>一些zeitgeist用户对探测traffic中的dips很感兴趣， 指一个query的容量是非同寻常的低（比如埃及政府关闭了internet）。在一个输入数据来自世界各地的分布式系统中，数据的到达时间并不严格对应数据的产生时间，因此识别出一个t&#x3D;1296167641的突然查询是因为在线路上delay了，还是根本就不存在。millwhere通过low watermark来跟踪这个问题， low watermark显示所有数据到某个时刻都已经到达。在系统中，low watermark跟踪所有的pending events。在这个例子中， 如果low watermark 提前过去时间t并且没有查询来临， 可以认为这些查询根本就不存在，而不是在网络上的延迟。 这种语义避免了要求在输入数据源上的单调递增，因为在真实环境中，乱序是很正常的。</p><h2 id="防止重复"><a href="#防止重复" class="headerlink" title="防止重复"></a>防止重复</h2><p>重复的record 会导致误认为错误的spike, 另外有一些计费的用户。millwheel在框架层面提供exactly-once， 而非业务方自己去处理这种问题。</p><h2 id="总结-millwheel需求："><a href="#总结-millwheel需求：" class="headerlink" title="总结 millwheel需求："></a>总结 millwheel需求：</h2><ul><li>数据需要尽可能快的被推送到consumer</li><li>持久化状态抽象需要被集成到系统一致性模型中，并可以被用户使用</li><li>乱序的数据可以被优雅处理</li><li>系统产生单调递增的low watermark</li><li>时延不受扩容影响</li><li>系统应该提供exactly-once的delivery能力</li></ul><h1 id="系统概况"><a href="#系统概况" class="headerlink" title="系统概况"></a>系统概况</h1><p>millwheel实际上就是一系列用户定义的transform操作组成的dag 图， 我们称这些transform为compuation操作，任何一个transform都能并行运行在任意的机器上。因此，用户无需考虑在一个合适范围内负载平衡。</p><p>在millwheel里面的输入和输出都是用(key, value, timestamp)来表示, key 是含有语义含义的元数据字段，value是任意的字符串，代表整个record。用户代码运行的context被限定到一个特定的key，每个computation根据自己的逻辑为每个输入源定义key。 比如， zeitgeist中某些computation为了计算每个query的统计信息，会选择搜索字段作为key，而另外一些compuation为了统计基于地理位置的统计信息，会选择地理位置为key。 而triple里面的timestamp可以由用户随意定义（但通常都是墙钟时间），而mill wheel根据timestamp来计算低水位。如果一个用户想要aggregate search term的每秒统计值， 他就需要assign timestamp 为这个search被处理的时间。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" ><img data-src="/img/millwheel/key.jpg"  alt="Google Millwheel"></div><p>一个compuation的output可以成为另外一个computation的输入，从而形成一个pipeline。用户可以动态增加或删除一个compuation到这个topology中而无需重启这个topology。一个compuation可以任意处理records，新建，修改，删除，过滤等操作。</p><p>millwheel提供框架api来冥等处理record。 用户只需要使用系统提供的通信和state抽象，所有failure和重试都被隐藏在系统内部。这样可以让用户代码简单，并只专注用户自己的逻辑。在一个computation context中，用户可以获取一个per-key和per-computation的持久化store，这样就可以基于key的aggregation。这种设计依赖下面的基本原则：</p><p><b>分发保障</b>：所有因处理record而产生的内部update都会自动基于per－key做checkpoint并保证exactly－once delivery。这种特性不需要依赖外部存储。</p><h1 id="核心概念"><a href="#核心概念" class="headerlink" title="核心概念"></a>核心概念</h1><p>数据在一个dag中分发，每一个环节都独立操作和emit数据。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" ><img data-src="/img/millwheel/api.jpg"  alt="Google Millwheel"></div><h2 id="computation："><a href="#computation：" class="headerlink" title="computation："></a>computation：</h2><p>用户代码运行在compuation中。 当接受到数据时，会调用compuation，会触发用户定义的一些操作，比如连接外部存储， 输出数据或操作其它millwhell的数据。如果连接外部存储，则用户来保证操作外部存储的行为具有冥等性。computation在一个单key的context中执行，但key之间是相互不可知的。如下图所示， 另外一个key的所有操作是串行的，但多个不同的key可以并行被处理</p><div align="center" style="width: 480px; height: auto;  text-align: center;" ><img data-src="/img/millwheel/key-serial.jpg"  alt="Google Millwheel"></div><h2 id="key"><a href="#key" class="headerlink" title="key"></a>key</h2><p>key是mill wheel中不同record aggregate和comparison最重要的抽象，系统中每一个record，消费者需要定义一个key－extraction 函数, 这个key-extraction函数会分配一个key给这个record。 用户代码运行在某个key的context下，只能允许访问这个key相关的stat。 举例来说， 在zeitgeist系统中，一个好的选择key的方式是使用search text， 从而，我们可以基于query 的text来进行counts和query模型的aggregate。同样不同的系统，可以使用不同的key-extract函数来处理相同的数据源来获取不同的key。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" ><img data-src="/img/millwheel/key-extract.jpg"  alt="Google Millwheel"></div><h2 id="streams"><a href="#streams" class="headerlink" title="streams:"></a>streams:</h2><p>streams 表示mill wheel内不同computation的分发机制。一个computation订阅零个或多个input，然后产生一个或多个stream， 系统保证分发正确性。每个stream内每个consumer有自己key-extract 函数，一份stream可以被多个consumer订阅. streams 根据名字来唯一标识，任何computation可以消费任何stream， 也可以产生records到任何stream。</p><h2 id="persistent-stat（持久化状态）："><a href="#persistent-stat（持久化状态）：" class="headerlink" title="persistent stat（持久化状态）："></a>persistent stat（持久化状态）：</h2><p>millwheel里面的持久化状态是基于per－key的透明字符串。用户提供序列号和反序列化函数，可以使用类似protocol buffer这种方便的机制来做。persistent stat存在高可用的存储中，从而保证数据一致性，并对终端用户完全透明。常见状态使用，比如一个时间窗口的record或等待join的数据的计数器aggregate。</p><h2 id="low-watermark（低水位）："><a href="#low-watermark（低水位）：" class="headerlink" title="low watermark（低水位）："></a>low watermark（低水位）：</h2><p>computation的low watermark 限制了接受record 的timestamp 的一定范围。</p><p><b>定义</b>： millwheel 提供一个基于数据流水线的low watermarks的迭代定义。 对于一个computation a， 设定为最老的work的timestamp对应最老的未完成的record。定义low watermark为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">min（最老a的工作， c的低水位（c 输出数据到a））。</span><br></pre></td></tr></table></figure><p>如果没有输入数据，则低水位值等同于oldest work 值。<br>低水位值由injectors （从外部系统获取数据，并发送到millwheel）进行seed，经常用外部系统监控pending work 作为评估手段， computation 期望少量的late records（小于低水位）。zeitgeist处理late records的方法就是知己丢弃这种数据， 但会跟踪有多少数据被丢弃掉了（一般在0.001%）。 一些流水线当接受到晚到的数据，可以根据这个进行反向矫正。系统保证一个computation的低水位单调递增，即使对于晚到的数据。</p><p>通过等待compuation 的低水位（提前一定值），用户可以有一个完整的picture 关于他们的到低水位时间的数据，就像之前zeitgeist的dip 探测系统展示的一样。当分配timestamp到新的或aggregate的record， 取决于用户去选择一个时间戳，只要不小于来源record的timestamp。通过millwheel低水位可以测量进度。如图所示：</p><div align="center" style="width: 480px; height: auto;  text-align: center;" ><img data-src="/img/millwheel/key-extract.jpg"  alt="Google Millwheel"></div><p>上图中，watermark像record一样前进。 pending works在时间轴上面，完成的在时间轴下面。新到的数据为pending work， 带着时间戳值提前于低水位值，数据可以乱序执行，低水位值反映出系统所有的pending work</p><h2 id="timers"><a href="#timers" class="headerlink" title="timers"></a>timers</h2><p>timers 是一个基于key的编程hook， 由一定的墙钟时间或低水位值进行触发。 timer 由一个computation的context创建并运行。用户来决定是使用墙钟还是低水位，比如邮件提醒系统使用墙钟，或者基于window进行aggregate的分析系统是基于low watermark。一旦设定，保障以时间戳的递增的顺序触发timer。timer会在持久化存储中记录日志并保障当机器故障时能够重启恢复。 当触发一个timer， 它运行一定的用户函数并像普通输入数据一样需要保障exactly-once。 zeitgeist的dips简单实现就是用一个指定时间的bucket的终止时间设置一个低水位timer， 并当监控的流量低于预测的模型时汇报一个dip。<br>timer的使用是可选的， 用户无timer barrier需求时可以直接跳过。 举例来说， zeitgeist 能够探测spiking 查询而无需timers， 发现一个spike 无需完整的数据视图。 如果观察的流量已经超过预测模型的预测值， 延迟的数据会加到总数据中并增加spike的大小。</p><h1 id="api"><a href="#api" class="headerlink" title="api"></a>api</h1><p>如下图所示， 提供借口访问所有的抽象（状态，timer和输出）。一旦设定，这些代码会自动在framework中运行。 用户无需构建任何per-key的locking语义， 系统是基于key的序列化执行。 </p><div align="center" style="width: 480px; height: auto;  text-align: center;" ><img data-src="/img/millwheel/computation-api.jpg"  alt="Google Millwheel"></div><h2 id="computation-api"><a href="#computation-api" class="headerlink" title="computation api"></a>computation api</h2><p>用户代码的2大入口点是ProcessRecord和ProcessTimer， 当数据来了会触发ProcessRecord，当timer 超时时触发ProcessTimer。这些构成了应用的compation。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" ><img data-src="/img/millwheel/computation-run.jpg"  alt="Google Millwheel"></div><p>在这些hook（ProcessRecord和ProcessTimer）执行过程中， 系统提供api 获取或控制 per-key 状态， 产生record和设置timer。 如下图所示， 展示了这些接口之间交互。 注意并没有错误恢复的逻辑，因为由框架自动进行错误恢复。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" ><img data-src="/img/millwheel/api-interactive.jpg"  alt="Google Millwheel"></div><h2 id="injector／low-watermark-api"><a href="#injector／low-watermark-api" class="headerlink" title="injector／low watermark api"></a>injector／low watermark api</h2><p>在系统层面， 每一个computation会为所有自己的pending work(处理中或队列中等待的deliver)计算一个低水位值。可以分配一个timestamp值给持久化state 。这样系统可以自动roll up，为了提供一种透明的timers 的api 语义， 用户很少直接和低水位值进行交互，但通过分配给record的timestamp间接计算出他们。</p><p><b>injector</b>：injector从外部获取数据，并发送到millwheel中。因为injector会为流水线其他部分seed low watermark，injector可以publish 一个injector low watermark到他的output streams中， 而其他subscriber可以获取他们。举例来说， 一个injector分析日志文件， 可以通过计算未完成文件的最小创建时间来计算low watermark。<br>一个injector可以跨进程运行，因此这些进程的低水位aggregate值会作为injector的低水位。 用户设定一组injector进程，从而防止injector单点故障。 实际上，常见的类型如日志，pubsub service等都有现成的injector， 用户无需再实现。如果一个injector 违反低水位语义并且发送一个迟于低水位的record， 用户代码可以决定丢弃这个record或者不对它进行现成aggregate的update。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" ><img data-src="/img/millwheel/injector.jpg"  alt="Google Millwheel"></div><h1 id="容错性"><a href="#容错性" class="headerlink" title="容错性"></a>容错性</h1><h2 id="分发保障"><a href="#分发保障" class="headerlink" title="分发保障"></a>分发保障</h2><p>大部分millwheel 编程模型的概念性简洁让用户代码无需冥等，但却可以达到冥等的效果， 让用户不用担心这些问题（数据可靠性问题）。</p><h2 id="发送保障"><a href="#发送保障" class="headerlink" title="发送保障"></a>发送保障</h2><h3 id="exactly-once-delivery"><a href="#exactly-once-delivery" class="headerlink" title="exactly-once delivery"></a>exactly-once delivery</h3><p>当computation接收到一个record后：</p><ul><li>record会被检查是否和之前分发的重复，重复的会被丢弃。</li><li>用户代码只处理输入的数据， 会产生timer／state／production的变化。</li><li>pending changes会被提交到backing store</li><li>发送者是acked的</li><li>会发送pending downstream productions</li></ul><p>作为一个优化，上述的操作可能被合并到对多个record的一个checkpoint。发送会不断重试直到他们被ack，这样可以保证at-least-once. 因为机器或网络故障，需要重试机制。然而， 这会带来一种问题， receiver在ack前crash了，即使它已经被成功处理并持久化它的状态。 这种情况下，当sender发送多次时，我们需要防止重复处理数据。<br>当computation产生一个数据（production）时，系统会分配一个唯一的id 给record。通过这个唯一id 可以识别重复的record。 如果相同record后面重发了， 会把它和jounaled id进行比较，然后扔弃并ack这个重复record。因为我们不能存储所有的重复数据到内存中，我们使用bloom filter来提供一个快速判断。对于boolm filter miss的event， 我们需要从backing store去进一步判断这个record是否是重复的。当所有内部sender完成发送时， 会做record id 垃圾回收。 对于经常发送late data的injector， 系统垃圾回收会额外delay一个对应的slake value。然而，exactly－once的数据会被几分钟被清理掉。</p><h3 id="strong-production"><a href="#strong-production" class="headerlink" title="strong production"></a>strong production</h3><p>因为millwheel是乱序处理record， 系统会在发送产生数据前，在原子状态更新里面进行checkpoint 生产数据。称这种checkpoint方式为strong production。举例来说， 一个computation 根据墙钟时间做aggregate并发送count结果给下游； 如果没有checkpoint， 对于一个产生window count 的computation，在存储它的状态前crash。一旦这个computation重启回来， 它可能接受到另外的record在产生相同aggregate，产生一个record在字节上是不同于之前的window但实际上是相同的window。为了正确处理这种逻辑， 下游的消费者需要一个复杂的冲突解决方案。在millwheel使用一个简单可行的solution， 因为用户的逻辑已经被系统保证为冥等来运行。<br>millwheel 会用bigtable在作为storage 系统，它高效实现blind write（直写， 和read-modify-write相反），像日志一样来进行checkpoint。 当一个进程重启后， checkpoint会被加载到内存中并被replay。 checkpoint一旦数据发送成功后（production successful）会被删除。</p><h3 id="weak-production-和冥等"><a href="#weak-production-和冥等" class="headerlink" title="weak production 和冥等"></a>weak production 和冥等</h3><p>通过strong production， exactly-once delivery， token使用从而让计算冥等。然而，一些compuation已经冥等了， 可以不需要这些措施，因为这些措施会消耗资源和加大latency。因此，用户可以自己控制disable strong production或exactly-once delivery 。 系统层面，disable exactly－once可以简单允许重复record 来实现，但禁止strong production需要注意性能影响。</p><p>对于weak production， 不是在发送数据前进行checkpoint 产生数据， 在持久化state之前，乐观的发送给下游。这会带来一个新问题， 流水线的完成时间会被大幅翻倍，尤其是连续stage，因为他们在等待下游的ack records。 这种情况会大大增加端到端的latency因为流水线深度增加。 举例来说，我们假设在某个分钟有1%的概率机器会出现故障， 至少出现一次failure的可能性就会按照流水线的深度大大提高，假设流水线深度是5， 那每分钟出现失败的概率就是5%。为了降低这种失败概率， 通过checkpoint 小比例的产生数据（production）， 允许这些stage 可以ack 他们的sender （说白了，就是strong production就是每个stage 做checkpoint， week production就是 在发送前做checkpoint，并且要求下游能够做数据去重）。 通过选择性checkpoint 这种方式，millwheel可以即提高端到端的latency并减少整体的资源消耗量。</p><div align="center" style="width: 480px; height: auto;  text-align: center;" ><img data-src="/img/millwheel/weekproduction.jpg"  alt="Google Millwheel"></div><p>当流水线是冥等的computation时，上述的方案是可行的， 因为重试不会影响正确性并且downstream production会是重试不可知。 真实的冥等例子就是无状态的filter， 重发的数据不会影响结果。</p><h2 id="state-manipulation（状态控制）"><a href="#state-manipulation（状态控制）" class="headerlink" title="state manipulation（状态控制）"></a>state manipulation（状态控制）</h2><p>在millwheel 用户状态存储中， 有2种状态， hard state 会被持久化到backing 存储上，而soft state 则包含任何在内存中cache或aggregate。 millwheel 会提供下面保证：</p><ul><li>系统不能丢失数据</li><li>更新state必须遵守exactly-once语义</li><li>系统中所有的持久化的数据必须是在任何时间点都是一致性的</li><li>低水位必须反映系统中所有pending state</li><li>timer必须按某个key顺序被触发。</li></ul><p>为了避免在持久化状态时的不一致， millwheel会封装所有的基于key的update到一个原子操作中。 在任何一个时间点，有可能因为非预期事件或处理失败导致中断处理。就像前面所述， exactly-once 数据在相同的操作中被更新， 增加它到基于per－key 一致性封装中。<br>因为工作会在不同机器之间迁移， 对于数据一致性的主要威胁是僵死的writer和网络里残留的写操作到backing store。为了跟踪这些问题， 我们attach 一个sequencer token到每一个write， backing store的代理， 在允许commit write前做检查。新worker在开始工作之前使所有现存的sequencer失效， 因此没有残留的更新能够成功。这个sequencer是一种类似lease 加强机制， 类似Centrifuge系统。 因此，我们可以保证， 对于一个指定的key， 在一个时间点，仅仅一个worker能被更新那个key相关内容。<br>这种single－writer 同样对于soft state非常关键， 但事务无法保证single-writer。 以pending timer来说， 如下图所示：</p><div align="center" style="width: 480px; height: auto;  text-align: center;" ><img data-src="/img/millwheel/softstage.jpg"  alt="Google Millwheel"></div><p>当僵死进程b 触发一个延迟写的transaction 作为response 给a， 在transaction 开始， 新的b， b-prime 执行初始化扫描timer， 当扫描结束， transaction 执行并且a 接收ack， 这样b-prime 就出在一个非一致状态。 就会永远丢失一个timer， 并且这个timer触发的更新操作就会被延迟， 因此，对于一些延迟敏感的系统，这些是不可接受的。</p><p>更进一步， 相同的情况会在checkpoint的production（输出）下会出现， 因为跳过一个backing store的初始化scan使它变的对系统不可知。 这个production将不会对低水位有操作直到它被发现。在一个中间时间，millwheel有可能汇报一个错误的低水位给consumer。 更近一步，因为低水位时单调递增，millwheel不能纠正这个错误值。因为违背low watermark原则，各种检查会出现， 包括发送非完善timer和非结束window 输出。</p><p>为了快速从失效状态中恢复， millwheel中每个computation worker 可以以一个合适的粒度做checkpoint。 millwheel的soft state状态一致性可以最小化意外失效。 可以执行异步扫描时，允许computation继续处理input。</p><h1 id="系统实现"><a href="#系统实现" class="headerlink" title="系统实现"></a>系统实现</h1><h2 id="架构"><a href="#架构" class="headerlink" title="架构"></a>架构</h2><p>millwheel是一个分布式系统， 所有的computation运行在一台或多台机器上， 数据流通过rpc进行相互通信。 每台机器上， millwheel 排列输入work并管理进程级别元数据， 分配合适的用户computation到进程上。</p><p>由master进行调度或负载平衡，  master 将每个computation 切分成一组key intervals，并分配这批key intervals到一组机器上。 当cpu或内存负载大时， 对key intervals做迁移或split，或merge， 每一个interval分配一个sequencer， 当发生改变（迁移，split， merge）时，将老的sequencer失效。</p><p>对于持久化状态， 使用bigtable或spanner系统， 提供原子行更新操作。 一个key的timer， pending production（输出）和持久化状态全部存在一行数据中。</p><p>当一个interval发生迁移时， millwheel从backing store中扫描元数据从而进行恢复。 初始化扫描存储在内存中，pending timer和checkpointed的production（输出）， 从而和后端存储在状态上是一致的。 这种方式 通过single-writer 语义来实现。</p><h2 id="low-watermark"><a href="#low-watermark" class="headerlink" title="low watermark"></a>low watermark</h2><p>为了保证数据一致性， 由一个全局可靠的子系统来实现low watermark。 millwheel 通过一个中央控制系统（带授权）来完成lower mark， 它会跟踪所有的lower watermark并打日志到持久化层， 防止进程失效时产生错误值。<br>每个进程aggregate 当前自己工作的timestamp 信息，并汇报中央控制系统， 它们包括所有的checkpointed或pending的production（输出），pending的timer或持久化状态。 每个进程可以在内存中高效完成这个动作，而无须执行代价昂贵的后段存储查询。 因为进程时基于key interval进行分配的， 因此low watermark也是bucket到key interval中并发送到中央控制系统。<br>为了正确计算系统的low watermark， 中央控制系统可以访问所有的low watermark信息。 当aggregate per-process的更新时， 它通过build 一个 low watermark的interval map为一个compuation，从而跟踪一个compuation的完成信息。当任何interval丢失时， 对应失效interval的low watermark 不变直到汇报一个新的值。 中央控制系统然后广播low watermark值到系统所有的computation。</p><p>消费者可以订阅数据的所有sender的lower watermark，然后计算它所有输入数据的low watermark的最小值。 这个计算最小值的工作在worker中执行，而非中央控制系统，是因为一致性， 中央控制系统的lower watermark是所有worker的最小值，但非输入worker的最小值。同样的，中央控制系统的lower watermark不会修改worker的lower watermark。<br>为了保持一致性， 所有的low watermark更新需要sequencer， 类似single-writer 到所有跟新到key interval state， 这些sequencer保证仅仅这个key的最新owner才能更新它的lower watermark值。 为了扩展性， 这个授权可以在机器之间share。</p>]]>
    </content>
    <id>https://ilongda.com/2016/docs/paper/millwheel/</id>
    <link href="https://ilongda.com/2016/docs/paper/millwheel/"/>
    <published>2016-10-23T11:42:57.000Z</published>
    <summary>Google Millwheel 流处理论文笔记：exactly-once 容错、key 状态模型、checkpoint 与 BigTable sequencer 机制</summary>
    <title>Google Millwheel</title>
    <updated>2026-06-09T08:46:25.963Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"工具 -- git","description":"Git 常用操作笔记：冲突解决、format-patch/rebase、fork 同步、reset soft 删提交记录与分支管理命令汇总","image":"https://ilongda.com/img/my.jpg","wordCount":671,"datePublished":"2016-10-17T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.964Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2016/docs/tools/git/"},"url":"https://ilongda.com/2016/docs/tools/git/","inLanguage":"zh-CN","keywords":["工具"],"articleSection":["工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"工具 -- git","item":"https://ilongda.com/2016/docs/tools/git/"}]}</script><h1 id="git-常用的函数"><a href="#git-常用的函数" class="headerlink" title="git 常用的函数"></a>git 常用的函数</h1><h2 id="冲突解决"><a href="#冲突解决" class="headerlink" title="冲突解决"></a>冲突解决</h2><p><br />git reset SOFT    –&gt; 当commit 后，发现别人已经做了修改， 则先revert commit， 再pull 代码， 再merge， 再commit， push <br /></p><h2 id="提取patch"><a href="#提取patch" class="headerlink" title="提取patch"></a>提取patch</h2><p>git format-patch -1 xxxxxxx-patch-uuid    —&gt; 提取某个patch<br /></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">git format-patch -M master    //当前分支所有超前master的提交</span><br><span class="line"></span><br><span class="line">git format-patch -s SHA值  //此SHA值提交以后的所有PATCH</span><br><span class="line"></span><br><span class="line">git format-patch -1 SHA值 //此SHA值的提交patch</span><br><span class="line"></span><br><span class="line">git format-patch -n //从master售前n个提交的内容</span><br><span class="line"></span><br><span class="line">git format-patch -n SHA值 //从SHA值开始(含SHA值当次)之前的N次提交</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">git format-patch HEAD^ &lt;==最近的1次commit的patch</span><br><span class="line"></span><br><span class="line">git format-patch HEAD^^ &lt;==最近的2次commit的patch</span><br><span class="line"></span><br><span class="line">git format-patch HEAD^ &lt;==最近的3次commit的patch</span><br><span class="line"></span><br><span class="line">git format-patch HEAD^ &lt;==最近的4次commit的patch</span><br><span class="line"></span><br><span class="line">git format-patch HEAD^^^^^ &lt;==不支持！！！！error！！！</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">链接：https://www.jianshu.com/p/f4c2a5d75fed</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2 id="rebase-到master"><a href="#rebase-到master" class="headerlink" title="rebase 到master"></a>rebase 到master</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">1. 提交代码到自己分支</span><br><span class="line">2. 切换到master， 拉取最新的代码</span><br><span class="line">3. 切换到自己分支</span><br><span class="line">4. git rebase master</span><br></pre></td></tr></table></figure><h2 id="合并提交"><a href="#合并提交" class="headerlink" title="合并提交"></a>合并提交</h2><p><span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vYW1vdS9wLzk0NjU4ODAuaHRtbA==">https://www.cnblogs.com/amou/p/9465880.html<i class="fa fa-external-link-alt"></i></span><br /><br>git  rebase -i  checkin-uuid   (最常用的是类似  git rebase -i HEAD~2) <br /><br>git push -f origin branch-name-xxxx<br /></p><h2 id="同步fork的分支"><a href="#同步fork的分支" class="headerlink" title="同步fork的分支"></a>同步fork的分支</h2><ol><li>先添加 remote 源</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git remote add remote_origin git@github.com:***/***.git</span><br></pre></td></tr></table></figure><ol start="2"><li>获取原始仓库分支和对应的提交</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch remote_origin</span><br></pre></td></tr></table></figure><ol start="3"><li>更新git remote 中所有的远程repo 所包含分支的最新commit-id, 将其记录到.git&#x2F;FETCH_HEAD文件中</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git fetch --all</span><br></pre></td></tr></table></figure><ol start="4"><li>把原始remote_origin&#x2F;master的改变合并到你本地的master分支。这会使你fork的分支master 与上层仓库remote_origin repository同步，而不会丢失你本地所做的改变：</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git merge remote_origin/master</span><br><span class="line">// or</span><br><span class="line">git rebase remote_origin/master</span><br></pre></td></tr></table></figure><ol start="5"><li>提交本地代码到github</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push</span><br></pre></td></tr></table></figure><ol start="6"><li>删除远程upstream</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git remote rm remote_origin</span><br></pre></td></tr></table></figure><h2 id="删除提交记录"><a href="#删除提交记录" class="headerlink" title="删除提交记录"></a>删除提交记录</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git reset --soft HEAD~i</span><br></pre></td></tr></table></figure><p>i代表要恢复到多少次提交前的状态，如指定i &#x3D; 2，则恢复到最近两次提交前的版本。–soft代表只删除服务器记录，不删除本地。</p><p>再执行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin master --force</span><br></pre></td></tr></table></figure><p>master代表当前分支</p><h2 id="分支管理"><a href="#分支管理" class="headerlink" title="分支管理"></a>分支管理</h2><ol><li>查看本地及远程所有分支</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git fetch origin</span><br><span class="line">git branch -a</span><br></pre></td></tr></table></figure><ol start="2"><li>删除本地分支</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git branch -d dev</span><br></pre></td></tr></table></figure><ol start="3"><li>删除远程分支</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git push origin --delete dev</span><br></pre></td></tr></table></figure><ol start="4"><li>切换远程分支</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git checkout xxx -b my_edit_branch</span><br></pre></td></tr></table></figure><h2 id="github-仓库迁移"><a href="#github-仓库迁移" class="headerlink" title="github 仓库迁移"></a>github 仓库迁移</h2><ol><li>clone 老的项目 为裸</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git clone --bare git://www.aa.com/project_name.git</span><br></pre></td></tr></table></figure><ol start="2"><li><p>在github 创建一个空项目, 建议任何文件都不创建</p></li><li><p>将所有git 记录推送到新项目中</p></li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">cd project_name</span><br><span class="line">git push --mirror git@www.bbb.com/new_project_name.git</span><br></pre></td></tr></table></figure><ol start="4"><li>删除老文件, 并clone 新项目</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">rm project_name</span><br><span class="line">git clone git@www.bbb.com/new_project_name.git</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2016/docs/tools/git/</id>
    <link href="https://ilongda.com/2016/docs/tools/git/"/>
    <published>2016-10-17T11:42:57.000Z</published>
    <summary>Git 常用操作笔记：冲突解决、format-patch/rebase、fork 同步、reset soft 删提交记录与分支管理命令汇总</summary>
    <title>工具 -- git</title>
    <updated>2026-06-09T08:46:25.964Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="旅游" scheme="https://ilongda.com/categories/%E6%97%85%E6%B8%B8/"/>
    <category term="旅游" scheme="https://ilongda.com/tags/%E6%97%85%E6%B8%B8/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"台湾环岛自由行攻略","description":"台湾环岛自由行攻略：通行证签注入台证办理、半岛游路线安排、垦丁花莲景点与交通机票预订技巧汇总，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://gsnapshot.alicdn.com/imgextra/imgextra/i1/224591973/TB2FA0XuXXXXXXsXpXXXXXXXXXX_!!224591973.jpg?time=1474032664000","wordCount":3576,"datePublished":"2016-10-14T11:07:43.000Z","dateModified":"2026-06-09T08:46:25.946Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2016/traveltaiwan/"},"url":"https://ilongda.com/2016/traveltaiwan/","inLanguage":"zh-CN","keywords":["旅游"],"articleSection":["旅游"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"旅游","item":"https://ilongda.com/categories/旅游/"},{"@type":"ListItem","position":3,"name":"台湾环岛自由行攻略","item":"https://ilongda.com/2016/traveltaiwan/"}]}</script><p>很早之前， 朋友就推荐过台湾， 称台湾绝对值得游玩一趟的， 于是，很早就把台湾通行证给办了。 而且， 台湾说的是国语， 英文不好的朋友，大可不必担心语言沟通问题。 另外， 台湾并没有网上说的，很歧视或排斥大陆游客， 我玩的时候，发现普遍讲话非常有礼貌，开口打扰了，闭口谢谢。另外，运气特别好的是，丈母娘她们报名的跟团游， 导游特别好， 没有强制购物，也没有强制玩景点时快速结束， 全程耐心等待游客， 而且经常扛箱子。 （可能因为第一导游是刚入行， 第二，行程是淡季，非十一或春节等）</p><h1 id="总攻略"><a href="#总攻略" class="headerlink" title="总攻略"></a>总攻略</h1><p>去台湾玩，选择哪些地点，怎么玩。 这里说一些我个人的小技巧， </p><ul><li>第一种办法，上途牛／携程／去哪儿， 挑一个时间和自己差不多跟团游， 看他玩了哪些地方， 然后，记下来。</li><li>另外一个好办法，强烈推荐手机安装“梦想旅行”， 每到一个城市，它会告诉这个城市有哪些非常不错的景点， 你挑选几个，然后，他会给你安排一下行程。</li></ul><p><B>台北有几个地方，值得推荐一下</B></p><ul><li>垦丁， 漂亮的地方特别多， 也有很多小孩玩的地方，比如 海洋生物馆， 尤其是夜宿海底隧道， 至少要提前半年预定， 绝对赞。</li><li>花莲， 和垦丁类似</li><li>日月潭， 类似千岛湖</li><li>清境农场</li><li>中台禅寺， 信佛的人，一定要去</li><li>台北， 台北故宫博物馆，101， 夜市， 中正纪念堂。</li></ul><p>台北如果玩的时间非常长的话，超过2周， 可以推荐环岛游， 如果不足2周，建议半岛游， 如果半岛游的话， 就没有必要到在一个地方往返。<br>最早安排的行程是 台北（1天）–&gt; 清境（1天） –&gt; 日月潭（1天） –&gt; 高雄（1天） –&gt; 垦丁（3天） –&gt; 台北（2天）， 但最后觉得需要在台北买不少东西， 台北需要放到最后， 最后行程安排是 台北（1天） –&gt; 垦丁（3天） –&gt; 高雄（1天） –&gt; 日月潭（1天） –&gt; 清境（1天） –&gt; 台北（2天）。 这个时候才发现机票没有订好， 没有必要在订 杭州到 台北的往返， 完全可以 去程杭州 –&gt; 高雄，返程台北–&gt; 杭州， 这样就可以节省半天 从台北到高雄的时间，也节省了一张高铁票。</p><span id="more"></span><h1 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h1><h2 id="签证："><a href="#签证：" class="headerlink" title="签证："></a>签证：</h2><p>去台湾的签证，相比美国和泰国之类，要麻烦非常多。分为</p><ol><li>台湾通行证</li><li>台湾签注</li><li>入台证</li></ol><p>办理台湾通行证， 需要差不多要2周的时间， 签注基本当天就可以完成， 另外在上海和杭州的朋友，可以在周末办理。<br>不过，办完通行证后， 一定要记得查看签注类型， 是自由行的签注，还是跟团的签注。<br>在直辖市或省会旅游，发的都是自由行的签注， 但在二线城市，很多就是跟团签注， 如果是自由行的签注，可以跟团游也可以自由行，但跟团的签注，就只能跟团，不能自由行。(自由签证是“大陆居民前往台湾签证(G)”, 跟团签证是”大陆居民前往台湾签证（L）”)<br>楼主就是开始不知道签注类型，以为都是自由行的， 结果在办理入台证的时候，才傻眼了， 发现有3份签注是跟团的， 而这个时候，开始退机票，退租车， 退房。<br><img data-src="https://gsnapshot.alicdn.com/imgextra/imgextra/i1/224591973/TB2FA0XuXXXXXXsXpXXXXXXXXXX_!!224591973.jpg?time=1474032664000" alt="img"></p><p>办理入台证， 建议提前2个月先上淘宝，查看办理入台证需要的哪些条件， 一般会卡在资产证明和紧急联系人证明上面， 资产证明，一般需要一张存款表或收入证明或信用卡金卡证明， 如果通过存储证明的话， 那至少要提前1个月办理定期存款，存款3个月。 如果紧急联系人不是在一个户口本上， 则需要到公安局开具联系人关系证明。<br>最后在办理入台证， 最好提前3周左右开始办理， 这样可以在淘宝上选择最便宜的办理方式。</p><h2 id="住"><a href="#住" class="headerlink" title="住"></a>住</h2><p>基本都是在booking上定的， 现在来看的话， 在booking上定会略微有点贵， 建议可以多比较几家订票网站， 比如阿里去啊，携程，自在客，大鱼，airbnb。 在台湾住了快10天， 感觉台湾的酒店都非常不错，酒店之间的差距不像大陆酒店之间的差距那么大， 无论大酒店还是小民宿都很干净，服务人员服务态度也特别好，完全没有歧视网上所说的歧视大陆。所以建议大家选择酒店， 关注2点即可， 价格和地段， 如果是自驾游的话，再关注是否有停车位。</p><p>使用booking， 好的地方：</p><ol><li>酒店非常全， 基本上涵盖面最大</li><li>评价非常好， 挑好酒店后， 看看评价里面有没有硬伤</li></ol><p>使用booking，小心的地方：</p><ol><li>很多酒店，不接受退订， 因此一定要小心是否退订， 我记得订日月潭的酒店的时候， 订完第二天， 就进行了预付款扣除， 在入住的前15天退订，还是扣除费用， 最后被逼无奈，只能再住一天，把能取消的酒店给取消掉。 但很多酒店可以接受因为台风而退订， 所以，当碰上台风时， 可以打电话给酒店进行改签。</li><li>很多酒店在展示房间的时候， 是显示无税的价格， 但当真正入住的时候，往往要15％的税， 所以，记得检查booking 发来确认预订酒店的邮件， 确认预订的真正价格。</li></ol><h2 id="通信和上网"><a href="#通信和上网" class="headerlink" title="通信和上网"></a>通信和上网</h2><ol><li>建议在网上买一张能上网的电话卡和无线wifi，<br>为什么建议买一张电话卡，主要是方便打uber，租车，注册打的软件或者紧急通话， 淘宝上 100元的带10天不限流量和50元台币的电话卡都不错， 另外吐槽一下， 台湾的预付电话卡费用太贵了，6元一分钟， 而直接使用国内电话拨打也就1元1分钟， 所以，可以建议，就用台湾的电话卡接收电话，因为台湾的4g 信号很不错， 好几个工作电话，后面都让对方改用钉钉网络电话， 效果还挺好。</li><li>无线wifi里面的上网卡和电话卡，最好不是一家的， 因为，到一个地方，可能电话卡信号很好，但上网卡信号不好， 反之亦然。</li><li>台北机场有免费的wifi， 也可以直接在台北机场购买卡， 稍微看了一下，价格比淘宝价格贵一点点。</li></ol><h2 id="行"><a href="#行" class="headerlink" title="行"></a>行</h2><p>机票： 越早订越便宜， 现在机票基本上各大网站价格在一个水平线上，不会出现大幅优惠， 阿里去啊有时会搞一些促销，但这些促销产品往往日期都是上班时间，并不适合出行旅游。</p><p>台北高铁： 台北高铁也是越早订越便宜， 如果提前一个月订，可以订到7折的高铁票， 不过，如果订了高铁票，提前退票的话，会扣5%的手续费</p><p>台北铁路： 很多人推荐走台北铁路， 可以一路观光， 不过台北铁路需要台湾身份证才能预订， 不过，可以通过淘宝搞定（淘宝还真是万能的淘宝）</p><h3 id="自驾游"><a href="#自驾游" class="headerlink" title="自驾游"></a>自驾游</h3><p>最后一个行上， 个人强烈推荐 环岛自驾游， 如果经济压力比较大，可以选择在垦丁几天驾车，其他地方使用公共交通或租用机车（摩托车）。 自驾游， 人会轻松很多， 尤其在等车或晚上的时候，可以心不慌，另外如果自己开车，逛的地方会远远超过租用机车（摩托车）的范围。</p><p>如果选择公共交通的话， 需要做更多的攻略， 酒店住的地方需要在交通比较方便的地方， 在什么地方搭乘长途汽车或火车得提前弄清楚，不过长途汽车或火车其实还比较方便， 不过非常容易查到怎么行走，推荐一个网站 <span class="exturl" data-url="aHR0cDovL2d1aWRlLnlvdXR4LmNvbS8lRUYlQkMlOEM=">http://guide.youtx.com/，<i class="fa fa-external-link-alt"></i></span> 输入当前地和目的地， 他会告诉你具体怎么到， 做什么车。</p><h4 id="租车"><a href="#租车" class="headerlink" title="租车"></a>租车</h4><ul><li>首先上淘宝办理 香港本地驾照， 因为台湾是国际idp 成员，但中国不是，台湾是不认中国驾照，而香港是国际idp成员，但香港idp 驾照需要香港本地身份证才能办理， 不过，随着香港人在台湾租车越来越多，台湾大部分组成公司都承认香港的本地驾照。 推荐<span class="exturl" data-url="aHR0cHM6Ly9zaG9wMTA4MTUyNDQ5LnRhb2Jhby5jb20vP3NwbT0yMDEzLjEuMTAwMDEyNi5kMjEubzBsbm82JUVGJUJDJThD">https://shop108152449.taobao.com/?spm=2013.1.1000126.d21.o0lno6，<i class="fa fa-external-link-alt"></i></span> 老板服务态度确实好（免费给他打个广告）。注意办理香港本地驾照需要2周时间，玩家最好预留好足够的时间。</li><li>台湾租车公司有很多租车软件，有avis／rentcars／大鱼， 进行比价一下，选择便宜的一款即可</li></ul><h4 id="租车小经验："><a href="#租车小经验：" class="headerlink" title="租车小经验："></a>租车小经验：</h4><ol><li>在rentcars 下单时， 会提示，要不要购买全包的保险，每天100元， 其实可以不用购买， 因为租车公司会购买一些保险</li><li>一些租车公司，可以提供免费的儿童安全座椅和导航装置， 比如这次选择的中租租车</li><li>特别小心， 租车上不允许吃东西或带宠物，否则需要罚款3000台币。之前，小孩在车上吃饼干，有一点饼干屑，租车公司要求交清洁费3000台币， 我argue， 车子出现故障耽误我们半天行程， 最后，取消的清洁费。 </li><li>记得自己准备车载usb 充电器，台湾租车是不提供usb 充电器（美国租车是提供车载usb 充电器）， 强烈推荐，自己准备车载收音机， 因为，台湾电台广告超多， 听的不爽</li><li>当车子出现问题时， 可以要求租车公司更换， 不过一般情况下， 如果车子没有发生问题，更换车子会收取一定的费用。</li><li>台湾租车公司服务态度普遍比较好， 记得在最后还车时， 需要收取500的高速过路费， 不过，因为车子剩余的油比较多，大概700元，直接冲抵掉了高速费用。 这里投诉一下美国的thrift公司，当时网上下单时，是满借满还， 结果到了租车的时候，变成空借空还， 骗我白送一箱油给他们。</li></ol><h4 id="导航软件"><a href="#导航软件" class="headerlink" title="导航软件"></a>导航软件</h4><p>试过高德， 百度，google， 最后发现 高德，百度无法使用， 只能选择google， google导航和高德导航相比，差的太远， 经常误报，走着走着，突然改变线路，让你调转方向，简直是让人吐血。  </p><h4 id="打的"><a href="#打的" class="headerlink" title="打的"></a>打的</h4><ol><li>台湾打的的费用非常贵， 起步70 台币， 然后随便走一下就200 台币了， 差不多uber 的1.4倍的价格</li><li>uber 台湾还可以用， 就是在定位的时候， 经常定不准，需要手动把自己的位置在google地图上调整好。</li><li>下了台北机场的时候， 找机场大巴的时候， 在机场大巴售票处碰到几位穿衬衫的大哥，咨询怎么坐车到酒店， 当时告诉我们，现在一个大巴到什么地方， 然后再打的到酒店， 然后说，你差不多需要500台币， 不如到前面做的士，800台币可以做到。后来，查询google，发现有直达的长途大巴， 再看这几个人，根本就是拉黑的的人，穿的像工作人员， 专拉大陆游客， 不过，真打的的话，确实也要800多台币。</li></ol><h2 id="购物："><a href="#购物：" class="headerlink" title="购物："></a>购物：</h2><p>这次很遗憾，没有怎么在台北购物，所以不能给玩家一些很好的建议。就在台北101 地下 买了一些化妆品，<br>因为大概是10.2号在台北，正好赶上101 周年纪念，很多化妆品（不过都是台湾的本土品牌）打折，确实比国内便宜很多<br>台北101 下面有苹果专卖店， 计算下来，ipad mini， 国内3688， 那边大概3500， 再加上退税，可以便宜大概300块。不过，iphone 小心 电信4g 不能用，得提前网上调查清楚。</p><p>台北是可以退税的， 只要满2000 台币，就可以让开退税单，然后到机场退税， 退税税率大概是4%。</p><h2 id="吃："><a href="#吃：" class="headerlink" title="吃："></a>吃：</h2><p>台湾的吃还是非常不错，值得赞的，估摸着是中华民族就是一个好吃的民族。</p><ol><li>夜市里有很多小吃， 可以值得去尝试一下， 不过夜市里面很多小吃偏甜，不喜欢甜的玩家，估计会有点遗憾。</li><li>台北101 下面有个鼎泰丰， 值得推荐一下</li><li>去吃了一次 台北， 信义坊， 还不错</li><li>在垦丁大街上， 一路小吃，想找个像样的餐馆，还真难找</li><li>在垦丁可以吃海鲜， 不过，海鲜的价格也不是非常便宜。</li><li>在日月潭 松鹤楼吃的很团餐， 尤其推荐菜总统鱼非常一般，还不如对面的另外一家 做的鱼</li></ol><h2 id="意外"><a href="#意外" class="headerlink" title="意外"></a>意外</h2><ol><li>在台湾，太容易出现台风了， 所以当碰上台风时， 估计会有一天甚至几天闷在酒店， 这个时候，只能在出行前，检查是否有台风，尽量争取绕过台风， 因为机票很多时候，提前一个月就订好了， 所以，时间基本不能修改， 不过，可以修改行程，尽量避免遇到台风中心。</li><li>如果出现受伤或意外， 其实，是购买了保险，可以报销的， 就是需要出诊单和正式发票</li></ol>]]>
    </content>
    <id>https://ilongda.com/2016/traveltaiwan/</id>
    <link href="https://ilongda.com/2016/traveltaiwan/"/>
    <published>2016-10-14T11:07:43.000Z</published>
    <summary>台湾环岛自由行攻略：通行证签注入台证办理、半岛游路线安排、垦丁花莲景点与交通机票预订技巧汇总，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>台湾环岛自由行攻略</title>
    <updated>2026-06-09T08:46:25.946Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="生活" scheme="https://ilongda.com/categories/%E7%94%9F%E6%B4%BB/"/>
    <category term="生活" scheme="https://ilongda.com/tags/%E7%94%9F%E6%B4%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"小谈跳槽","description":"小谈跳槽：从薪水、未来职业与公司前景、家庭变化等维度分析跳槽本质与权衡，引用经典两条原因作探讨，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://ilongda.com/img/my.jpg","wordCount":1660,"datePublished":"2016-09-23T11:07:43.000Z","dateModified":"2026-06-09T08:46:25.937Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2016/jump/"},"url":"https://ilongda.com/2016/jump/","inLanguage":"zh-CN","keywords":["生活"],"articleSection":["生活"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"生活","item":"https://ilongda.com/categories/生活/"},{"@type":"ListItem","position":3,"name":"小谈跳槽","item":"https://ilongda.com/2016/jump/"}]}</script><h1 id="小谈-跳槽"><a href="#小谈-跳槽" class="headerlink" title="小谈 跳槽"></a>小谈 跳槽</h1><p>最近看一个帖子《阿里70w vs IBM 40W》， 帖子里面充斥着阿里和IBM 各种出差补助的争吵， 实在无语， 忍不住，利用周末的时间，总结自己的想法， 抛砖引玉一下， 欢迎各位朋友一起探讨。<br>跳槽其实是一个非常大的话题，可以从梦想，从性格，从经历，从专业等各个维度长篇大论一番，另外每个人都有自己的经历，从而都有自己的解读， 没有哪一种是完全正确的，也没有哪一种是完全错误的，而我只能说，将我的理解表达出来，如果你有更多的想法，不妨也探讨一下。</p><span id="more"></span><h1 id="跳槽的本质"><a href="#跳槽的本质" class="headerlink" title="跳槽的本质"></a>跳槽的本质</h1><p>任何一次跳槽，都是自己人生中的一次选择， 这次选择都会对自己的人生产生一定的影响， 但每次选择出发点都是期望让自己的职业生涯向前一步或者家庭幸福感向前一步。</p><p>如果选择了家庭幸福感， 那在跳槽的权重中，家庭的因素自然放在首要位置。</p><p>如果选择了职业生涯，这次选择有没有让自己工作成就感更上一层楼。当工作成就感增强时，会感觉工作的动力源源不断涌现， 这就是为什么出现那么多的工作狂人的缘故。关于职业生涯，强烈建议拜读一下《你为什么没有好工作》， 里面有前辈很深刻的总结和理解。</p><p>但无论是选择家庭幸福感还是职业生涯，得先问自己，这是一次中长期的跳槽，还是一次短暂的旅途。但无论是短期旅途还是中长期跳槽， 是不是和自己长远的一个目标吻合。</p><h1 id="跳槽的权衡"><a href="#跳槽的权衡" class="headerlink" title="跳槽的权衡"></a>跳槽的权衡</h1><h2 id="薪水"><a href="#薪水" class="headerlink" title="薪水"></a>薪水</h2><p>谈跳槽，不可避免会涉及到薪水。<br>马云曾用2句话，很精辟的解释了为什么跳槽，无非2个原因：</p><ol><li>干的不爽</li><li>钱少了</li></ol><p>背后也说明了， 薪水其实能摆平跳槽中的很多问题。<br>当你很在意薪水时，如果这次跳槽，薪水离自己的预期有很大差距，建议不要跳， 因为一开始，就注定了心情不好， 这个不好的状态会维持到你下一次涨薪前， 又会让你对下一次涨薪充满期待，当期待越高时，也越容易失望。因此，薪水绝对会影响一个人的心情。<br>当你开始关注薪水之外的东西时，薪水的比重会逐渐降低。</p><p>记得第一次跳槽时， 老板的老板对我说，薪水不要看的太重， 多几千少几千都不是问题的关键，关键是你自己有没有机会成长更多。</p><h2 id="未来和当前："><a href="#未来和当前：" class="headerlink" title="未来和当前："></a>未来和当前：</h2><p>随着年纪和阅历不断增加，跳槽会看薪水之外越来越多的东西，也就是马老师的 “干的不爽”， 很多时候就归结于5个字 “未来和当前”<br>未来：</p><ol><li>自己职业的未来， 这次跳槽能否带来自己职业的提升， 视野，技术，能力能否有提升， 或者有一个更好的职位，通俗一点更好的坑， 专业的讲，更好的卡位。</li><li>公司的未来，<br>2.1 公司的未来有没有可能给一个平台给自己更好的展示，<br>2.2 另外这个公司的文化， 适不适合自己， 很多人忽视这个因素，但往往这个因素会影响一个人能不能长久的在一个公司呆下去。<br>家庭：</li><li>会给自己家庭带来什么变化， 一个稳定的工作，必然需要一个稳定的家庭<br>1.1 如果有提升，比如，原来的异地分居，现在合二为一， 那就是加分项<br>1.2 如果有冲击， 这个冲击是否能够接受， 比如异地跳槽或者夫妻分居， 当冲击出现时， 得仔细思考家庭，</li></ol><h2 id="创业"><a href="#创业" class="headerlink" title="创业"></a>创业</h2><p>随着现在政府口号 “大众创业，万众创新”的口号，很多事情，跳槽的过程中，遭遇了创业的问题：<br>创业就相当于一次跳槽过程，通常这次跳槽（创业）的结果是以失败告终，但收获了自己人生的阅历。<br>创业的人，往往需要足够强的脑力和体力，去面对所有已知或未知， 足够的能力去面对折腾，因此创业第一步需要分析自己的性格是否偏好创业，对于一些性格成熟稳重（赌性比较少），偏好安逸的人，建议到大公司工作，可能更适合一些。当然打算创业的原因太多，这里无法一一列举。</p><p>附带一句：<br>创业和情商是没有直接关系的，任何情况下（无论是创业还是在大公司工作，抑或小公司工作），都需要高情商。高情商的人，成功的概率永远都会比普通人高出一大截。， </p><h1 id="跳槽一点小技巧"><a href="#跳槽一点小技巧" class="headerlink" title="跳槽一点小技巧"></a>跳槽一点小技巧</h1><p>跳槽很多时候都是自己去选择公司，选择团队， 而不是被动接受外界猎头的鼓动。 自己心中有个大致概念，自己能去哪些公司，已经想去哪些公司。</p><p>如果你开始迈出跳槽的一步， 很简单， 搜索你想去公司的猎头， 发封邮件给他，如果有回复后，附上一份简历。 同样，也可以搜索这家公司的员工， 也可以发邮件给他，向他求助， 任何一家公司都非常欢迎内部推荐，因为，更可靠和成本更低。</p><p>至于跳槽过程中的技巧， 基本上属于术的范畴， 有很多文章，都有很多介绍， 这里有2个策略吧：<br>（1）一定需要临时抱佛脚， 临阵抹枪，不抹也光， 你准备的越多，机会的大门也向你敞开的越多<br>（2）尽可能的诚实</p><h1 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h1><p>跳槽永远都是一个 围城 的故事， 城外的人拼命想进来， 城内的人拼命想出去。 当前所在的公司， 绝不是你想象中的那么不堪， 新进入的公司，也绝不是梦中的那么美好， 没有一家十全十美的公司， 所以在跳槽的第一步，就要想清楚，自己想要什么，自己想改变什么？</p><p>作为老板其实不喜欢跳槽太多的人。</p>]]>
    </content>
    <id>https://ilongda.com/2016/jump/</id>
    <link href="https://ilongda.com/2016/jump/"/>
    <published>2016-09-23T11:07:43.000Z</published>
    <summary>小谈跳槽：从薪水、未来职业与公司前景、家庭变化等维度分析跳槽本质与权衡，引用经典两条原因作探讨，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>小谈跳槽</title>
    <updated>2026-06-09T08:46:25.937Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Scala" scheme="https://ilongda.com/categories/Scala/"/>
    <category term="Scala" scheme="https://ilongda.com/tags/Scala/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"scala 入门","description":"Scala 入门笔记：解释器、val/var 变量、函数与 Unit 类型、foreach 语法糖、数组与副作用等基础语法要点","image":"http://img3.tbcdn.cn/5476e8b07b923/TB1v6nOMVXXXXatXFXXXXXXXXXX","wordCount":1069,"datePublished":"2016-08-24T11:07:43.000Z","dateModified":"2026-06-09T08:46:25.944Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2016/scala-start/"},"url":"https://ilongda.com/2016/scala-start/","inLanguage":"zh-CN","keywords":["Scala"],"articleSection":["Scala"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Scala","item":"https://ilongda.com/categories/Scala/"},{"@type":"ListItem","position":3,"name":"scala 入门","item":"https://ilongda.com/2016/scala-start/"}]}</script><h1 id="scala-入门初探"><a href="#scala-入门初探" class="headerlink" title="scala 入门初探"></a>scala 入门初探</h1><p>本章介绍scala 入门基本知识</p><span id="more"></span><h2 id="scala-解释器"><a href="#scala-解释器" class="headerlink" title="scala 解释器"></a>scala 解释器</h2><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1v6nOMVXXXXatXFXXXXXXXXXX" alt="scala shell"></p><pre><code>- 变量- 冒号和类型- 等号- 结果</code></pre><p>当对多个文件调用scala时， 需要首先对他们进行编译<br><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1qxvDMVXXXXa1XVXXXXXXXXXX" alt="build"></p><p>但scalar 比较慢， 推荐使用fsc， fsc 会启动一个后台程序，扫描jar文件， 调用fsc时，仅把源码提交给后台进行编译<br><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1bofNMVXXXXa1XFXXXXXXXXXX" alt="fsc build"></p><h2 id="变量定义"><a href="#变量定义" class="headerlink" title="变量定义"></a>变量定义</h2><ul><li>val， 类似java的final变量</li><li>var， 非final变量</li></ul><h2 id="函数定义："><a href="#函数定义：" class="headerlink" title="函数定义："></a>函数定义：</h2><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1gcPLMVXXXXbDXFXXXXXXXXXX" alt="function_def"><br><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1Md6BMVXXXXbUXVXXXXXXXXXX" alt="image"><br>如果函数结果可以推导出来，可以不用写函数结果的类型<br>如果函数体只有一行，可以不用写花括号<br>Unit 表示void 类型</p><h2 id="scala脚本："><a href="#scala脚本：" class="headerlink" title="scala脚本："></a>scala脚本：</h2><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1LZPvMVXXXXXYaXXXXXXXXXXX" alt="image"></p><h2 id="循环语句"><a href="#循环语句" class="headerlink" title="循环语句"></a>循环语句</h2><p>while<br>foreach</p><p>args.foreach(arg &#x3D;&gt; println(arg))<br>等价<br>args.foreach((arg: String) &#x3D;&gt; println(arg))</p><p>如果函数语句只有一行，并且直邮一个参数，可以缩写参数<br>args.foreach(println)</p><p>for 语法</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1XJbyMVXXXXc3XVXXXXXXXXXX" alt="image"></p><h2 id="数组"><a href="#数组" class="headerlink" title="数组"></a>数组</h2><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1vSYAMVXXXXXUXVXXXXXXXXXX" alt="image"></p><p>其中 for(i &lt;- 0 to 2)<br>有一个原则：<br>方法若只有一个参数并且方法返回值有接收者，调用时，可以省略点和括号<br>因此等价于 for (i &lt;- 0.to(2))</p><p>其中greetStrings 指定了数组变量，因此不能修改为其他变量，但内部是可以重新赋值的</p><p>greetString(0) 等价于 greetString.apply(i)<br>任何对于对象值参数应用都（直接用传递一个活多个值参数时）被转化为对apply方法的调用</p><p>当对带有括号，并包括1到若干参数赋值时， 编译器自动调用对象的update函数，对括号里面的参数和等号右边的对象执行调用<br>greetStrings(0) &#x3D; “hello”<br>转化为<br>greetStrings.update(0, “hello”)</p><p>还可以更加简洁</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB16PfwMVXXXXc2XVXXXXXXXXXX" alt="1"></p><p>等价于</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1OvzHMVXXXXXbXVXXXXXXXXXX" alt="1"></p><h2 id="list"><a href="#list" class="headerlink" title="list"></a>list</h2><p>scala.List 不同于java 的java.util.List, 一旦创建就不可改变， 内部的元素也是不可变的。</p><p>:::  实现叠加功能</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1D8zpMVXXXXXGaFXXXXXXXXXX" alt="1"></p><p>:: 发音 cons,  把新元素添加到列表的最前端， 并且它是右操作符</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB13UPMMVXXXXcfXpXXXXXXXXXX" alt="2"></p><p>下面可以得到和上面一样的结果， Nil 是空列表的简写</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1EMrzMVXXXXcKXVXXXXXXXXXX" alt="1"></p><p>列表没有append操作<br>原因是，随列表增长， append耗时也会增长， 而:: 操作耗时是固定的。<br>如果想做append操作</p><ul><li>调用::,  然后调用reverse</li><li>使用ListBuffer</li></ul><p>list 函数列表, List 索引基于0</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1ldTvMVXXXXXOXVXXXXXXXXXX" alt="1"></p><h2 id="Tuple"><a href="#Tuple" class="headerlink" title="Tuple"></a>Tuple</h2><p>Tuple 也是不可变对象， 但和List不同， 它可以含有不同类型的元素</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1o1bwMVXXXXataXXXXXXXXXXX" alt="1"></p><p>tuple的索引是基于1:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">99</span><br><span class="line">Luftballons</span><br></pre></td></tr></table></figure><p>为什么是基于1， 因为对于静态类型元组其他语言 Haskell&#x2F;ML 1是传统。<br>不能使用pair(0), 因为pair(0)就是 pair.apply(0), 但apply不能满足一个函数返回不同的类型， 因此不能这样操作</p><p>＃ set<br>set分为可变set和不可变set</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1TUnvMVXXXXbfaXXXXXXXXXXX" alt="1"></p><p>scala的trait 类似java的interface<br>实现了java的接口，在scala里面称为mix in trait</p><p>默认是不可变set<br>例如：</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1ApTQMVXXXXXlXFXXXXXXXXXX" alt="1"></p><p>当执行+&#x3D; 操作时， 实际上是创建一个新的set</p><p>如果需要可变set， 得必须指定是可变set</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1aBHrMVXXXXamapXXXXXXXXXX" alt="1"></p><h2 id="map"><a href="#map" class="headerlink" title="map"></a>map</h2><p>map也分可变map和不可变map， 不可变map是默认的</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1I.rpMVXXXXanapXXXXXXXXXX" alt="1"></p><p>scala的任何对象都可以调用-&gt;函数， 返回包含键值对的二元组</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1lL6xMVXXXXX.aXXXXXXXXXXX" alt="2"></p><h2 id="副作用"><a href="#副作用" class="headerlink" title="副作用"></a>副作用</h2><p>scala里面有一种行为，常常称为副作用， 就是不返回值的行为称为副作用。暗指一个该函数重复执行不能得到一个明确的结果。<br>有一种仅为了副作用而执行的方式：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">def add(b: Byte): Unit = sum += b</span><br></pre></td></tr></table></figure><p>可以改写为</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">def add(b: Byte) &#123;sum += b&#125;</span><br></pre></td></tr></table></figure><p>去掉类型和＝， 并用花括号来包括</p><p>另外因为任何类型都可以转化为Unit, 因此，当一个函数结果是某个类型比如string时， 但函数返回类型指定了Unit, 则丢弃结果</p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1bDPKMVXXXXaiXFXXXXXXXXXX" alt="1"></p><p>因此这种情况很容易导致用户发生错误， 比如用户其实是想要返回值，但漏写了&#x3D;， 于是导致函数无结果返回</p><h2 id="分号"><a href="#分号" class="headerlink" title="分号;"></a>分号;</h2><p>当一行只有一个语句时，可以不用分号， 当有多个语句时，得用分号</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">val s = &quot;hello&quot;; println(s)</span><br></pre></td></tr></table></figure><p>但注意 scala 是把操作符放到尾部</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">x +</span><br><span class="line">y +</span><br><span class="line">z</span><br></pre></td></tr></table></figure><p>等价于(x+y+z) 但如果是</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">x</span><br><span class="line">+y</span><br><span class="line">+z</span><br></pre></td></tr></table></figure><p>则变成了3个独立语句</p>]]>
    </content>
    <id>https://ilongda.com/2016/scala-start/</id>
    <link href="https://ilongda.com/2016/scala-start/"/>
    <published>2016-08-24T11:07:43.000Z</published>
    <summary>Scala 入门笔记：解释器、val/var 变量、函数与 Unit 类型、foreach 语法糖、数组与副作用等基础语法要点</summary>
    <title>scala 入门</title>
    <updated>2026-06-09T08:46:25.944Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="Scala" scheme="https://ilongda.com/categories/Scala/"/>
    <category term="Scala" scheme="https://ilongda.com/tags/Scala/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"scala 概述","description":"Scala 语言概述：融合面向对象与函数式编程的 JVM 静态类型语言，介绍 Actor 并发模型及思想来源与设计特点，更多细节与示例见正文。","image":"http://img3.tbcdn.cn/5476e8b07b923/TB1eUnZMVXXXXbkXXXXXXXXXXXX","wordCount":465,"datePublished":"2016-08-23T11:07:43.000Z","dateModified":"2026-06-09T08:46:25.944Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2016/scala-general/"},"url":"https://ilongda.com/2016/scala-general/","inLanguage":"zh-CN","keywords":["Scala"],"articleSection":["Scala"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"Scala","item":"https://ilongda.com/categories/Scala/"},{"@type":"ListItem","position":3,"name":"scala 概述","item":"https://ilongda.com/2016/scala-general/"}]}</script><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>scala 就是 将函数式编程和面向对象编程进行融合， 并加入静态类型语言的一种编程语言<br>是一种运行在jvm上的，可以无缝和java 结合的编程语言</p><span id="more"></span><h2 id="函数式编程"><a href="#函数式编程" class="headerlink" title="函数式编程"></a>函数式编程</h2><ul><li>函数就是对象， 函数就是头等值， 函数和字符串，整数处在同一个地位， 可以被当作函数参数或函数返回值保存到变量中， 也可以在函数中定义函数，就像定义整数一样，也可以定义匿名函数，并把函数插到代码任意地方</li><li>不可变数据结构是函数式编程的基石</li><li>方法不应该有任何副作用， 即可重入</li></ul><h2 id="静态类型："><a href="#静态类型：" class="headerlink" title="静态类型："></a>静态类型：</h2><ul><li>确定变量和表达式的类型， 所有类型都是明确制定，并非动态变化， 但却很好的解决了程序的过度冗长</li><li>通过类型推断避免冗余性</li><li>通过模式匹配获得灵活性</li><li>好处：<ul><li>类型检查</li><li>安全重构，</li></ul></li></ul><ul><li>用户可以自定义类或库<ul><li>评论：任何语言不是都可以这么做么， 没有什么特别的</li></ul></li><li>actor并发编程模型<ul><li>评论： 其实就是基于消息的异步编程， 新瓶换旧酒， 异步编程框架都是基于消息的actor模型， 唯一一点， scala的actor编程框架使用会更加简单， 封装性更好。<br><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1eUnZMVXXXXbkXXXXXXXXXXXX" alt="enter image description here"></li></ul></li></ul><h2 id="scala-思想来源"><a href="#scala-思想来源" class="headerlink" title="scala 思想来源"></a>scala 思想来源</h2><ul><li>采用java &amp; c# 大部分， 表达式，句子，代码块 多数和java一样， 还采用了java的很多元素， 基本类型，类库和执行模式</li><li>统一对象模型来自smalltalk</li><li>actor库来着erlang</li><li>函数式编程方式来自和sml&#x2F;ocaml&#x2F;f# 为代表的ml家族语言</li><li>方法调用和字段选择的统一访问原则来自eiffel</li></ul>]]>
    </content>
    <id>https://ilongda.com/2016/scala-general/</id>
    <link href="https://ilongda.com/2016/scala-general/"/>
    <published>2016-08-23T11:07:43.000Z</published>
    <summary>Scala 语言概述：融合面向对象与函数式编程的 JVM 静态类型语言，介绍 Actor 并发模型及思想来源与设计特点，更多细节与示例见正文。</summary>
    <title>scala 概述</title>
    <updated>2026-06-09T08:46:25.944Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="生活" scheme="https://ilongda.com/categories/%E7%94%9F%E6%B4%BB/"/>
    <category term="生活" scheme="https://ilongda.com/tags/%E7%94%9F%E6%B4%BB/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"投诉godaddy","description":"因 GoDaddy 删除个人空间导致五年博文丢失的吐槽：强烈谴责其缺乏责任感，并建议有条件改用阿里云自建博客站点，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":90,"datePublished":"2016-08-10T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.933Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2016/godaddy/"},"url":"https://ilongda.com/2016/godaddy/","inLanguage":"zh-CN","keywords":["生活"],"articleSection":["生活"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"生活","item":"https://ilongda.com/categories/生活/"},{"@type":"ListItem","position":3,"name":"投诉godaddy","item":"https://ilongda.com/2016/godaddy/"}]}</script><p>原博客下面文章， 因为godaddy 删除了个人空间，导致5年的博文，毁于一旦，真是吐血三升， 很多美好的回忆，已付之东流。</p><p>强烈谴责godaddy, 没有任何责任感， 而且多次投诉godaddy无果。</p><p>如果有条件，还是建议在阿里云上购买虚拟机，自建网站</p>]]>
    </content>
    <id>https://ilongda.com/2016/godaddy/</id>
    <link href="https://ilongda.com/2016/godaddy/"/>
    <published>2016-08-10T11:42:57.000Z</published>
    <summary>因 GoDaddy 删除个人空间导致五年博文丢失的吐槽：强烈谴责其缺乏责任感，并建议有条件改用阿里云自建博客站点，更多细节与示例见正文。</summary>
    <title>投诉godaddy</title>
    <updated>2026-06-09T08:46:25.933Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"知识库","description":"博客知识库总目录：汇总数据库、机器学习、MySQL、论文阅读与运维工具等技术专题文章索引，更多细节与示例见正文。，完整内容请阅读正文。","image":"https://ilongda.com/img/my.jpg","wordCount":5,"datePublished":"2015-10-17T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.951Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/index/"},"url":"https://ilongda.com/2015/docs/index/","inLanguage":"zh-CN"}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"知识库","item":"https://ilongda.com/2015/docs/index/"}]}</script><ul><li><a href="/knowledge/database/">database</a></br></li><li><a href="/knowledge/machine_learning/">machine_learning</a></br></li><li><a href="/knowledge/mysql/">mysql</a></br></li><li><a href="/knowledge/paper/">paper</a></br></li><li><a href="/knowledge/tools/">tools</a></br></li></ul>]]>
    </content>
    <id>https://ilongda.com/2015/docs/index/</id>
    <link href="https://ilongda.com/2015/docs/index/"/>
    <published>2015-10-17T11:42:57.000Z</published>
    <summary>博客知识库总目录：汇总数据库、机器学习、MySQL、论文阅读与运维工具等技术专题文章索引，更多细节与示例见正文。，完整内容请阅读正文。</summary>
    <title>知识库</title>
    <updated>2026-06-09T08:46:25.951Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"工具 -- ulimit","description":"ulimit 系统资源限制配置笔记：进程数、文件句柄、栈大小与虚拟内存等参数的临时与永久修改方法，更多细节与示例见正文。","image":"https://ilongda.com/img/my.jpg","wordCount":4125,"datePublished":"2015-10-17T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.966Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/tools/ulimit/"},"url":"https://ilongda.com/2015/docs/tools/ulimit/","inLanguage":"zh-CN","keywords":["工具"],"articleSection":["工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"工具 -- ulimit","item":"https://ilongda.com/2015/docs/tools/ulimit/"}]}</script><h1 id="ulimit-详解"><a href="#ulimit-详解" class="headerlink" title="ulimit 详解"></a>ulimit 详解</h1><p>转载 <span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vemVuZ2tlZnUvcC81NjQ5NDA3Lmh0bWw=">https://www.cnblogs.com/zengkefu/p/5649407.html<i class="fa fa-external-link-alt"></i></span><br /><br><br />Linux对于每个用户，系统限制其最大进程数。为提高性能，可以根据设备资源情况，设置各linux 用户的最大进程数<br />可以用ulimit -a 来显示当前的各种用户进程限制。<br />下面我把某linux用户的最大进程数设为10000个：<br />     ulimit -u 10240<br />     对于需要做许多 socket 连接并使它们处于打开状态的 Java 应用程序而言，<br />     最好通过使用 ulimit -n xx 修改每个进程可打开的文件数，缺省值是 1024。<br />     ulimit -n 4096 将每个进程可以打开的文件数目加大到4096，缺省为1024<br />     其他建议设置成无限制（unlimited）的一些重要设置是：<br />     数据段长度：ulimit -d unlimited<br />     最大内存大小：ulimit -m unlimited<br />     堆栈大小：ulimit -s unlimited<br />     CPU 时间：ulimit -t unlimited<br />     虚拟内存：ulimit -v unlimited<br /><br><br />     暂时地，适用于通过 ulimit 命令登录 shell 会话期间。<br />     永久地，通过将一个相应的 ulimit 语句添加到由登录 shell 读取的文件中， 即特定于 shell 的用户资源文件，如：<br />1)、解除 Linux 系统的最大进程数和最大文件打开数限制：<br />        vi &#x2F;etc&#x2F;security&#x2F;limits.conf<br />        # 添加如下的行<br />        * soft noproc 11000<br />        * hard noproc 11000<br />        * soft nofile 4100<br />        * hard nofile 4100<br />       说明：* 代表针对所有用户，noproc 是代表最大进程数，nofile 是代表最大文件打开数<br />2)、让 SSH 接受 Login 程式的登入，方便在 ssh 客户端查看 ulimit -a 资源限制：<br />        a、vi &#x2F;etc&#x2F;ssh&#x2F;sshd_config<br />             把 UserLogin 的值改为 yes，并把 # 注释去掉<br />        b、重启 sshd 服务：<br />              &#x2F;etc&#x2F;init.d&#x2F;sshd restart<br />3)、修改所有 linux 用户的环境变量文件：<br />    vi &#x2F;etc&#x2F;profile<br />    ulimit -u 10000<br />    ulimit -n 4096<br />    ulimit -d unlimited<br />    ulimit -m unlimited<br />    ulimit -s unlimited<br />    ulimit -t unlimited<br />    ulimit -v unlimited<br /> 保存后运行#source &#x2F;etc&#x2F;profile 使其生效<br />&#x2F;**************************************<br />有时候在程序里面需要打开多个文件，进行分析，系统一般默认数量是1024，（用ulimit -a可以看到）对于正常使用是够了，但是对于程序来讲，就太少了。<br />修改2个文件。<br /> <br />1.&#x2F;etc&#x2F;security&#x2F;limits.conf<br />vi &#x2F;etc&#x2F;security&#x2F;limits.conf<br />加上：<br />* soft nofile 8192<br />* hard nofile 20480<br /> <br />2.&#x2F;etc&#x2F;pam.d&#x2F;login<br />session required &#x2F;lib&#x2F;security&#x2F;pam_limits.so<br />另外确保&#x2F;etc&#x2F;pam.d&#x2F;system-auth文件有下面内容<br />session required &#x2F;lib&#x2F;security&#x2F;$ISA&#x2F;pam_limits.so<br />这一行确保系统会执行这个限制。<br /> <br />3.一般用户的.bash_profile<br />#ulimit -n 1024<br />重新登陆ok<br /> <br />ulimit 的作用<br />  &#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;&#x3D;<br /> <br />ulimit：显示（或设置）用户可以使用的资源的限制（limit），这限制分为软限制（当前限制）和硬限制（上限），其中硬限制是软限制的上限值，应用程序在运行过程中使用的系统资源不超过相应的软限制，任何的超越都导致进程的终止。<br /> <br />参数 描述<br />ulimited 不限制用户可以使用的资源，但本设置对可打开的最大文件数（max open files）<br />和可同时运行的最大进程数（max user processes）无效<br />-a 列出所有当前资源极限<br />-c 设置core文件的最大值.单位:blocks<br />-d 设置一个进程的数据段的最大值.单位:kbytes<br />-f Shell 创建文件的文件大小的最大值，单位：blocks<br />-h 指定设置某个给定资源的硬极限。如果用户拥有 root 用户权限，可以增大硬极限。任何用户均可减少硬极限<br />-l 可以锁住的物理内存的最大值<br />-m 可以使用的常驻内存的最大值,单位：kbytes<br />-n 每个进程可以同时打开的最大文件数<br />-p 设置管道的最大值，单位为block，1block&#x3D;512bytes<br />-s 指定堆栈的最大值：单位：kbytes<br />-S 指定为给定的资源设置软极限。软极限可增大到硬极限的值。如果 -H 和 -S 标志均未指定，极限适用于以上二者<br />-t 指定每个进程所使用的秒数,单位：seconds<br />-u 可以运行的最大并发进程数<br />-v Shell可使用的最大的虚拟内存，单位：kbytes<br />-x<br />范例1：<br />[root@localhost proc]# ulimit -a<br />core file size (blocks, -c) 100<br />data seg size (kbytes, -d) unlimited<br />file size (blocks, -f) unlimited<br />pending signals (-i) 2047<br />max locked memory (kbytes, -l) 32<br />max memory size (kbytes, -m) unlimited<br />open files (-n) 1024<br />pipe size (512 bytes, -p) 8<br />POSIX message queues (bytes, -q) 819200<br />stack size (kbytes, -s) 8192<br />cpu time (seconds, -t) unlimited<br />max user processes (-u) 2047<br />virtual memory (kbytes, -v) unlimited<br />file locks (-x) unlimited<br />[root@localhost proc]#<br />输出的每一行由资源名字、（单位，ulimit命令的参数）、软限制组成。详细解释：<br />参数 描述<br />core file size core文件的最大值为100 blocks，<br />data seg size 进程的数据段可以任意大<br />file size 文件可以任意大<br />pending signals 最多有2047个待处理的信号<br />max locked memory 一个任务锁住的物理内存的最大值为32kB<br />max memory size 一个任务的常驻物理内存的最大值<br />open files 一个任务最多可以同时打开1024的文件<br />pipe size 管道的最大空间为4096字节<br />POSIX message queues POSIX的消息队列的最大值为819200字节<br />stack size 进程的栈的最大值为8192字节<br />cpu time 进程使用的CPU时间<br />max user processes 当前用户同时打开的进程(包括线程)的最大个数为2047<br />virtual memory 没有限制进程的最大地址空间<br />file locks 所能锁住的文件的最大个数没有限制<br />范例2：通过ulimit命令来限制文件的大小，从而导致拷贝命令的失败<br />[root@localhost]ls temp.txt<br />ls: temp.txt: 没有那个文件或目录<br />[root@localhost]ulimit -f 1 #设置创建文件的最大块(一块&#x3D;512字节)<br />[root@localhost]cat a.c &gt; temp.txt<br />文件大小超出限制<br />文件a.c的大小是5002字节,而我们设定的创建文件的大小是512字节x1块&#x3D;512字节 <br /> <br /> <br />1、修改用戶進程可打開文件數限制<br /> <br />在Linux平台上，無論編寫客戶端程序還是服務端程序，在進行高並發TCP連接處理時，最高的並發數量都要受到系統對用戶單一進程同時可打開文件數量的限制(這是因為系統為每個TCP連接都要創建一個socket句柄，每個socket句柄同時也是一個文件句柄)。可使用ulimit命令查看系統允許當前用戶進程打開的文件數限制：<br /> <br />[speng@as4 <del>]$ ulimit -n<br /> <br />1024<br /> <br />這表示當前用戶的每個進程最多允許同時打開1024個文件，這1024個文件中還得除去每個進程必然打開的標準輸入，標準輸出，標準錯誤，服務器監聽socket，進程間通訊的unix域socket等文件，那麼剩下的可用於客戶端socket連接的文件數就只有大概1024-10&#x3D;1014個左右。也就是說缺省情況下，基於Linux的通訊程序最多允許同時1014個TCP並發連接。<br /> <br />對於想支持更高數量的TCP並發連接的通訊處理程序，就必須修改Linux對當前用戶的進程同時打開的文件數量的軟限制(soft limit)和硬限制(hardlimit)。其中軟限制是指Linux在當前系統能夠承受的範圍內進一步限制用戶同時打開的文件數；硬限制則是根據系統硬件資源狀況(主要是系統內存)計算出來的系統最多可同時打開的文件數量。通常軟限制小於或等於硬限制。<br /> <br />修改上述限制的最簡單的辦法就是使用ulimit命令：<br /> <br />[speng@as4 ~]$ ulimit -n<file_num><br /> <br />上述命令中，在<file_num>中指定要設置的單一進程允許打開的最大文件數。如果系統回顯類似於”Operation notpermitted”之類的話，說明上述限制修改失敗，實際上是因為在<file_num>中指定的數值超過了Linux系統對該用戶打開文件數的軟限製或硬限制。因此，就需要修改Linux系統對用戶的關於打開文件數的軟限制和硬限制。<br /> <br />第一步，修改&#x2F;etc&#x2F;security&#x2F;limits.conf文件，在文件中添加如下行：<br /> <br />speng soft nofile 10240<br /> <br />speng hard nofile 10240<br /> <br />其中speng指定了要修改哪個用戶的打開文件數限制，可用’*’號表示修改所有用戶的限制；soft或hard指定要修改軟限制還是硬限制；10240則指定了想要修改的新的限制值，即最大打開文件數(請注意軟限制值要小於或等於硬限制)。修改完後保存文件。<br /> <br />第二步，修改&#x2F;etc&#x2F;pam.d&#x2F;login文件，在文件中添加如下行：<br /> <br />session required &#x2F;lib&#x2F;security&#x2F;pam_limits.so<br /> <br />這是告訴Linux在用戶完成系統登錄後，應該調用pam_limits.so模塊來設置系統對該用戶可使用的各種資源數量的最大限制(包括用戶可打開的最大文件數限制)，而pam_limits.so模塊就會從&#x2F;etc&#x2F;security&#x2F;limits.conf文件中讀取配置來設置這些限制值。修改完後保存此文件。<br /> <br />第三步，查看Linux系統級的最大打開文件數限制，使用如下命令：<br /> <br />[speng@as4 ~]$ cat &#x2F;proc&#x2F;sys&#x2F;fs&#x2F;file-max<br /> <br />12158<br /> <br />這表明這台Linux系統最多允許同時打開(即包含所有用戶打開文件數總和)12158個文件，是Linux系統級硬限制，所有用戶級的打開文件數限制都不應超過這個數值。通常這個系統級硬限制是Linux系統在啟動時根據系統硬件資源狀況計算出來的最佳的最大同時打開文件數限制，如果沒有特殊需要，不應該修改此限制，除非想為用戶級打開文件數限制設置超過此限制的值。修改此硬限制的方法是修改&#x2F;etc&#x2F;rc.local腳本，在腳本中添加如下行：<br /> <br />echo 22158 &gt; &#x2F;proc&#x2F;sys&#x2F;fs&#x2F;file-max<br /> <br />這是讓Linux在啟動完成後強行將系統級打開文件數硬限制設置為22158。修改完後保存此文件。<br /> <br />完成上述步驟後重啟系統，一般情況下就可以將Linux系統對指定用戶的單一進程允許同時打開的最大文件數限制設為指定的數值。如果重啟後用ulimit-n命令查看用戶可打開文件數限制仍然低於上述步驟中設置的最大值，這可能是因為在用戶登錄腳本&#x2F;etc&#x2F;profile中使用ulimit -n命令已經將用戶可同時打開的文件數做了限制。由於通過ulimit-n修改系統對用戶可同時打開文件的最大數限制時，新修改的值只能小於或等於上次ulimit-n設置的值，因此想用此命令增大這個限制值是不可能的。所以，如果有上述問題存在，就只能去打開&#x2F;etc&#x2F;profile腳本文件，在文件中查找是否使用了ulimit-n限制了用戶可同時打開的最大文件數量，如果找到，則刪除這行命令，或者將其設置的值改為合適的值，然後保存文件，用戶退出並重新登錄系統即可。<br /> <br />通過上述步驟，就為支持高並發TCP連接處理的通訊處理程序解除關於打開文件數量方面的系統限制。<br /> <br />2、修改網絡內核對TCP連接的有關限制<br /> <br />在Linux上編寫支持高並發TCP連接的客戶端通訊處理程序時，有時會發現儘管已經解除了系統對用戶同時打開文件數的限制，但仍會出現並發TCP連接數增加到一定數量時，再也無法成功建立新的TCP連接的現象。出現這種現在的原因有多種。<br /> <br />第一種原因可能是因為Linux網絡內核對本地端口號範圍有限制。此時，進一步分析為什麼無法建立TCP連接，會發現問題出在connect()調用返回失敗，查看系統錯誤提示消息是”Can’t assign requestedaddress”。同時，如果在此時用tcpdump工具監視網絡，會發現根本沒有TCP連接時客戶端發SYN包的網絡流量。這些情況說明問題在於本地Linux系統內核中有限制。其實，問題的根本原因在於Linux內核的TCP&#x2F;IP協議實現模塊對系統中所有的客戶端TCP連接對應的本地端口號的範圍進行了限制(例如，內核限製本地端口號的範圍為1024</del>32768之間)。當系統中某一時刻同時存在太多的TCP客戶端連接時，由於每個TCP客戶端連接都要佔用一個唯一的本地端口號(此端口號在系統的本地端口號範圍限制中)，如果現有的TCP客戶端連接已將所有的本地端口號佔滿，則此時就無法為新的TCP客戶端連接分配一個本地端口號了，因此系統會在這種情況下在connect()調用中返回失敗，並將錯誤提示消息設為”Can’t assignrequested address”。有關這些控制邏輯可以查看Linux內核源代碼，以linux2.6內核為例，可以查看tcp_ipv4.c文件中如下函數：<br /> <br />static int tcp_v4_hash_connect(struct sock *sk)<br /> <br />請注意上述函數中對變量sysctl_local_port_range的訪問控制。變量sysctl_local_port_range的初始化則是在tcp.c文件中的如下函數中設置：<br /> <br />void __init tcp_init(void)<br /> <br />內核編譯時默認設置的本地端口號範圍可能太小，因此需要修改此本地端口範圍限制。<br /> <br />第一步，修改&#x2F;etc&#x2F;sysctl.conf文件，在文件中添加如下行：<br /> <br />net.ipv4.ip_local_port_range &#x3D; 1024 65000<br /> <br />這表明將系統對本地端口範圍限制設置為1024~65000之間。請注意，本地端口範圍的最小值必須大於或等於1024；而端口範圍的最大值則應小於或等於65535。修改完後保存此文件。<br /> <br />第二步，執行sysctl命令：<br /> <br />[speng@as4 ~]$ sysctl -p<br /> <br />如果系統沒有錯誤提示，就表明新的本地端口範圍設置成功。如果按上述端口範圍進行設置，則理論上單獨一個進程最多可以同時建立60000多個TCP客戶端連接。<br /> <br />第二種無法建立TCP連接的原因可能是因為Linux網絡內核的IP_TABLE防火牆對最大跟踪的TCP連接數有限制。此時程序會表現為在connect()調用中阻塞，如同死機，如果用tcpdump工具監視網絡，也會發現根本沒有TCP連接時客戶端發SYN包的網絡流量。由於IP_TABLE防火牆在內核中會對每個TCP連接的狀態進行跟踪，跟踪信息將會放在位於內核內存中的conntrackdatabase中，這個數據庫的大小有限，當系統中存在過多的TCP連接時，數據庫容量不足，IP_TABLE無法為新的TCP連接建立跟踪信息，於是表現為在connect()調用中阻塞。此時就必須修改內核對最大跟踪的TCP連接數的限制，方法同修改內核對本地端口號範圍的限制是類似的：<br /> <br />第一步，修改&#x2F;etc&#x2F;sysctl.conf文件，在文件中添加如下行：<br /> <br />net.ipv4.ip_conntrack_max &#x3D; 10240<br /> <br />這表明將系統對最大跟踪的TCP連接數限制設置為10240。請注意，此限制值要盡量小，以節省對內核內存的佔用。<br /> <br />第二步，執行sysctl命令：<br /> <br />[speng@as4 ~]$ sysctl -p<br /> <br />如果系統沒有錯誤提示，就表明系統對新的最大跟踪的TCP連接數限制修改成功。如果按上述參數進行設置，則理論上單獨一個進程最多可以同時建立10000多個TCP客戶端連接。<br /> <br /><em><strong><strong><strong>注意</strong></strong></strong></em><br /> <br />sysctl -p 報錯net.ipv4.ip_conntrack_max” is an unknown key 則：modprobe ip_conntrack</p>]]>
    </content>
    <id>https://ilongda.com/2015/docs/tools/ulimit/</id>
    <link href="https://ilongda.com/2015/docs/tools/ulimit/"/>
    <published>2015-10-17T11:42:57.000Z</published>
    <summary>ulimit 系统资源限制配置笔记：进程数、文件句柄、栈大小与虚拟内存等参数的临时与永久修改方法，更多细节与示例见正文。</summary>
    <title>工具 -- ulimit</title>
    <updated>2026-06-09T08:46:25.966Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具 -- vmstat","description":"vmstat 虚拟内存与系统状态监控笔记：解读 r/b/swpd/free/bi/bo 等字段，判断 CPU、内存与 I/O 瓶颈","image":"https://ilongda.com/assets/vmstat1.png","wordCount":1356,"datePublished":"2015-10-17T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.966Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/tools/monitor_tools/vmstat/"},"url":"https://ilongda.com/2015/docs/tools/monitor_tools/vmstat/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具 -- vmstat","item":"https://ilongda.com/2015/docs/tools/monitor_tools/vmstat/"}]}</script><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>vmstat 命令是最常见的Linux&#x2F;Unix监控工具，可以展现给定时间间隔的服务器的状态值,包括服务器的CPU使用率，内存使用，虚拟内存交换情况,IO读写情况。这个命令是我查看Linux&#x2F;Unix最喜爱的命令，一个是Linux&#x2F;Unix都支持，二是相比top，我可以看到整个机器的CPU,内存,IO的使用情况，而不是单单看到各个进程的CPU使用率和内存使用率(使用场景不一样)。</p><h1 id="命令详解"><a href="#命令详解" class="headerlink" title="命令详解"></a>命令详解</h1><p>一般vmstat工具的使用是通过两个数字参数来完成的，第一个参数是采样的时间间隔数，单位是秒，第二个参数是采样的次数，如:</p><p><img data-src="/assets/vmstat1.png" alt="vmstat"><br>2表示每个两秒采集一次服务器状态，1表示只采集一次。<br>实际上，在应用过程中，我们会在一段时间内一直监控，不想监控直接结束vmstat就行了,例如:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">root@ubuntu:~# vmstat 2</span><br></pre></td></tr></table></figure><p>这表示vmstat每2秒采集数据，一直采集，直到我结束程序，这里采集了5次数据我就结束了程序。<br>好了，命令介绍完毕，现在开始实战讲解每个参数的意思。</p><ul><li>r 表示运行队列(就是说多少个进程真的分配到CPU)，我测试的服务器目前CPU比较空闲，没什么程序在跑，当这个值超过了CPU数目，就会出现CPU瓶颈了。这个也和top的负载有关系，一般负载超过了3就比较高，超过了5就高，超过了10就不正常了，服务器的状态很危险。top的负载类似每秒的运行队列。如果运行队列过大，表示你的CPU很繁忙，一般会造成CPU使用率很高。</li><li>b 表示阻塞的进程,这个不多说，进程阻塞，大家懂的。</li><li>swpd 虚拟内存已使用的大小，如果大于0，表示你的机器物理内存不足了，如果不是程序内存泄露的原因，那么你该升级内存了或者把耗内存的任务迁移到其他机器。</li><li>free   空闲的物理内存的大小，我的机器内存总共8G，剩余3415M。</li><li>buff   Linux&#x2F;Unix系统是用来存储，目录里面有什么内容，权限等的缓存，我本机大概占用300多M</li><li>cache cache直接用来记忆我们打开的文件,给文件做缓冲，我本机大概占用300多M(这里是Linux&#x2F;Unix的聪明之处，把空闲的物理内存的一部分拿来做文件和目录的缓存，是为了提高 程序执行的性能，当程序使用内存时，buffer&#x2F;cached会很快地被使用。)</li><li>si  每秒从磁盘读入虚拟内存的大小，如果这个值大于0，表示物理内存不够用或者内存泄露了，要查找耗内存进程解决掉。我的机器内存充裕，一切正常。</li><li>so  每秒虚拟内存写入磁盘的大小，如果这个值大于0，同上。</li><li>bi  块设备每秒接收的块数量，这里的块设备是指系统上所有的磁盘和其他块设备，默认块大小是1024byte，我本机上没什么IO操作，所以一直是0，但是我曾在处理拷贝大量数据(2-3T)的机器上看过可以达到140000&#x2F;s，磁盘写入速度差不多140M每秒</li><li>bo 块设备每秒发送的块数量，例如我们读取文件，bo就要大于0。bi和bo一般都要接近0，不然就是IO过于频繁，需要调整。</li><li>in 每秒CPU的中断次数，包括时间中断</li><li>cs 每秒上下文切换次数，例如我们调用系统函数，就要进行上下文切换，线程的切换，也要进程上下文切换，这个值要越小越好，太大了，要考虑调低线程或者进程的数目,例如在apache和nginx这种web服务器中，我们一般做性能测试时会进行几千并发甚至几万并发的测试，选择web服务器的进程可以由进程或者线程的峰值一直下调，压测，直到cs到一个比较小的值，这个进程和线程数就是比较合适的值了。系统调用也是，每次调用系统函数，我们的代码就会进入内核空间，导致上下文切换，这个是很耗资源，也要尽量避免频繁调用系统函数。上下文切换次数过多表示你的CPU大部分浪费在上下文切换，导致CPU干正经事的时间少了，CPU没有充分利用，是不可取的。</li><li>us 用户CPU时间，我曾经在一个做加密解密很频繁的服务器上，可以看到us接近100,r运行队列达到80(机器在做压力测试，性能表现不佳)。</li><li>sy 系统CPU时间，如果太高，表示系统调用时间长，例如是IO操作频繁。</li><li>id  空闲 CPU时间，一般来说，id + us + sy &#x3D; 100,一般我认为id是空闲CPU使用率，us是用户CPU使用率，sy是系统CPU使用率。</li><li>wt 等待IO CPU时间。</li></ul>]]>
    </content>
    <id>https://ilongda.com/2015/docs/tools/monitor_tools/vmstat/</id>
    <link href="https://ilongda.com/2015/docs/tools/monitor_tools/vmstat/"/>
    <published>2015-10-17T11:42:57.000Z</published>
    <summary>vmstat 虚拟内存与系统状态监控笔记：解读 r/b/swpd/free/bi/bo 等字段，判断 CPU、内存与 I/O 瓶颈</summary>
    <title>监控工具 -- vmstat</title>
    <updated>2026-06-09T08:46:25.966Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具 -- trace 工具","description":"Java/系统 trace 工具笔记：BTrace 动态跟踪 Java 运行时方法调用，strace 跟踪 mysqld 系统调用耗时分析","image":"https://ilongda.com/assets/btrace_jvisual.png","wordCount":3035,"datePublished":"2015-10-16T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.966Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/tools/monitor_tools/trace/"},"url":"https://ilongda.com/2015/docs/tools/monitor_tools/trace/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具 -- trace 工具","item":"https://ilongda.com/2015/docs/tools/monitor_tools/trace/"}]}</script><h1 id="trace-工具"><a href="#trace-工具" class="headerlink" title="trace 工具"></a>trace 工具</h1><p>有btrace 和strace 2个工具，<br>btrace 主要监控java程序状态，获取运行时数据信息，如方法返回值，参数，调用次数，全局变量，调用堆栈等。<br>strace 常用来跟踪进程执行时的系统调用和所接收的信号。常用它查看mysql系统调用耗时</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">strace -cfp $(pidof mysqld)</span><br></pre></td></tr></table></figure><h2 id="btrace"><a href="#btrace" class="headerlink" title="btrace"></a>btrace</h2><h3 id="btrace概述"><a href="#btrace概述" class="headerlink" title="btrace概述"></a>btrace概述</h3><p>这两天在调试程序时，发现一个比较好用的工具-btrace，能够线上监控程序状态，获取运行时数据信息，如方法返回值，参数，调用次数，全局变量，调用堆栈等。BTrace是Java的安全可靠的动态跟踪工具。 他的工作原理是通过 instrument + asm 来对正在运行的java程序中的class类进行动态增强。</p><p>有几篇文章介绍的不错， 推荐一下： </p><ul><li><span class="exturl" data-url="aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC85M2U5NGI3MjQ0NzY=">https://www.jianshu.com/p/93e94b724476<i class="fa fa-external-link-alt"></i></span>   偏重如何使用</li><li><span class="exturl" data-url="aHR0cDovL2FnYXBwbGUuaXRleWUuY29tL2Jsb2cvMTAwNTkxOA==">http://agapple.iteye.com/blog/1005918<i class="fa fa-external-link-alt"></i></span>    偏重底层实现和原理</li><li><span class="exturl" data-url="aHR0cDovL2FnYXBwbGUuaXRleWUuY29tL2Jsb2cvOTYyMTE5">http://agapple.iteye.com/blog/962119<i class="fa fa-external-link-alt"></i></span>     偏重底层实现</li></ul><p>本文主要示例如何使用</p><h3 id="命令使用"><a href="#命令使用" class="headerlink" title="命令使用"></a>命令使用</h3><p>下载地址：<span class="exturl" data-url="aHR0cHM6Ly9naXRodWIuY29tL2J0cmFjZWlvL2J0cmFjZQ==">https://github.com/btraceio/btrace<i class="fa fa-external-link-alt"></i></span></p><p>位于bin目录下面主要有6个脚本，3个windows的，另外3个是linux的，分别是btrace、btracec、btracer。具体功能如下：      </p><h4 id="btrace-1"><a href="#btrace-1" class="headerlink" title="btrace"></a>btrace</h4><p>功能: 用于运行BTrace跟踪程序。<br>命令格式: </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">btrace [-I &lt;include-path&gt;] [-p &lt;port&gt;] [-cp &lt;classpath&gt;] &lt;pid&gt; &lt;btrace-script&gt; [&lt;args&gt;] </span><br><span class="line">参数含义: </span><br><span class="line"></span><br><span class="line">        include-path指定头文件的路径，用于脚本预处理功能，可选； </span><br><span class="line"></span><br><span class="line">        port指定BTrace agent的服务端监听端口号，用来监听clients，默认为2020，可选； </span><br><span class="line"></span><br><span class="line">        classpath用来指定类加载路径，默认为当前路径，可选； </span><br><span class="line"></span><br><span class="line">        pid表示进程号，可通过jps命令获取； </span><br><span class="line"></span><br><span class="line">        btrace-script即为BTrace脚本；btrace脚本如果以.java结尾，会先编译再提交执行。可使用btracec命令对脚本进行预编译。 </span><br><span class="line"></span><br><span class="line">       args是BTrace脚本可选参数，在脚本中可通过&quot;$&quot;和&quot;$length&quot;获取参数信息。 </span><br><span class="line"></span><br><span class="line">示例: </span><br><span class="line"></span><br><span class="line">          btrace -cp build/  1200 AllCalls1.java </span><br><span class="line"></span><br></pre></td></tr></table></figure><h4 id="btracec"><a href="#btracec" class="headerlink" title="btracec"></a>btracec</h4><p>功能: 用于预编译BTrace脚本，用于在编译时期验证脚本正确性。 </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">btracec [-I &lt;include-path&gt;] [-cp &lt;classpath&gt;] [-d &lt;directory&gt;] &lt;one-or-more-BTrace-.java-files&gt; </span><br></pre></td></tr></table></figure><p>参数意义同btrace命令一致，directory表示编译结果输出目录。</p><h4 id="btracer"><a href="#btracer" class="headerlink" title="btracer"></a>btracer</h4><p>功能: btracer命令同时启动应用程序和BTrace脚本，即在应用程序启动过程中使用BTrace脚本。而btrace命令针对已运行程序执行BTrace脚本。<br>命令格式: </p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">btracer &lt;pre-compiled-btrace.class&gt; &lt;application-main-class&gt; &lt;application-args&gt; </span><br><span class="line">参数说明: </span><br><span class="line"></span><br><span class="line">       pre-compiled-btrace.class表示经过btracec编译后的BTrace脚本。 </span><br><span class="line"></span><br><span class="line">       application-main-class表示应用程序代码； </span><br><span class="line"></span><br><span class="line">       application-args表示应用程序参数。 </span><br><span class="line"></span><br><span class="line">       该命令的等价写法为: </span><br><span class="line"></span><br><span class="line">java -javaagent:btrace-agent.jar=script=&lt;pre-compiled-btrace-script1&gt;[,&lt;pre-compiled-btrace-script1&gt;]*        &lt;MainClass&gt; &lt;AppArguments&gt;</span><br></pre></td></tr></table></figure><h4 id="btrace脚本限制"><a href="#btrace脚本限制" class="headerlink" title="btrace脚本限制"></a>btrace脚本限制</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">In particular, a BTrace class</span><br><span class="line">can not create new objects.</span><br><span class="line">can not create new arrays.</span><br><span class="line">can not throw exceptions.</span><br><span class="line">can not catch exceptions.</span><br><span class="line">can not make arbitrary instance or static method calls - only the public static methods of com.sun.btrace.BTraceUtils class may be called from a BTrace program.</span><br><span class="line">can not assign to static or instance fields of target program&#x27;s classes and objects. But, BTrace class can assign to it&#x27;s own static fields (&quot;trace state&quot; can be mutated).</span><br><span class="line">can not have instance fields and methods. Only static public void returning methods are allowed for a BTrace class. And all fields have to be static.</span><br><span class="line">can not have outer, inner, nested or local classes.</span><br><span class="line">can not have synchronized blocks or synchronized methods.</span><br><span class="line">can not have loops (for, while, do..while)</span><br><span class="line">can not extend arbitrary class (super class has to be java.lang.Object)</span><br><span class="line">can not implement interfaces.</span><br><span class="line">can not contains assert statements.</span><br><span class="line">can not use class literals.</span><br></pre></td></tr></table></figure><h4 id="jvisualvm-插件"><a href="#jvisualvm-插件" class="headerlink" title="jvisualvm 插件"></a>jvisualvm 插件</h4><p>BTrace提供了jvisualvm插件，强烈推荐在jvisualvm中编写和测试BTrace脚本，启动、关闭、发送事件、增加classpath都非常方便。<br><img data-src="/assets/btrace_jvisual.png" alt="jvisualvm"></p><h4 id="btrace实例"><a href="#btrace实例" class="headerlink" title="btrace实例"></a>btrace实例</h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">package baby.btrace;  </span><br><span class="line">  </span><br><span class="line">public   class CaseObject&#123;  </span><br><span class="line">       </span><br><span class="line">       private static int sleepTotalTime=0;   </span><br><span class="line">       private  int sleepTotalTime2=0;   </span><br><span class="line">       </span><br><span class="line">       public boolean execute(int sleepTime) throws Exception&#123;  </span><br><span class="line">           System.out.println(&quot;sleep: &quot;+sleepTime);  </span><br><span class="line">           sleepTotalTime+=sleepTime;  </span><br><span class="line">           sleepTotalTime2=sleepTotalTime+1;  </span><br><span class="line">          sleep(sleepTime);  </span><br><span class="line">           if(sleepTime%2==0)  </span><br><span class="line">               return true;  </span><br><span class="line">           else   </span><br><span class="line">               return false;  </span><br><span class="line">       &#125;  </span><br><span class="line">  </span><br><span class="line">        public void sleep(int sleepTime) throws Exception &#123;     </span><br><span class="line">            Thread.sleep(sleepTime);     </span><br><span class="line">        &#125;  </span><br><span class="line">       </span><br><span class="line">    &#125; </span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">package baby.btrace;  </span><br><span class="line">  </span><br><span class="line">import java.util.Random;  </span><br><span class="line">  </span><br><span class="line">public class CaseObjectMain &#123;  </span><br><span class="line">    int times = 10;  </span><br><span class="line">  </span><br><span class="line">    public static void main(String[] args)  &#123;  </span><br><span class="line">        CaseObjectMain main= new CaseObjectMain();  </span><br><span class="line">        try &#123;  </span><br><span class="line">            main.begin();  </span><br><span class="line">        &#125; catch (Exception e) &#123;  </span><br><span class="line">            // TODO Auto-generated catch block  </span><br><span class="line">            e.printStackTrace();  </span><br><span class="line">        &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">      </span><br><span class="line">    public void begin() throws Exception&#123;  </span><br><span class="line">          </span><br><span class="line">         CaseObject object=new CaseObject();  </span><br><span class="line">         while(true)&#123;  </span><br><span class="line">             times++;  </span><br><span class="line">             boolean result=doWork(object);  </span><br><span class="line">            Thread.sleep(1000);  </span><br><span class="line">         &#125;  </span><br><span class="line">    &#125;  </span><br><span class="line">    public boolean doWork(CaseObject object) throws Exception&#123;  </span><br><span class="line">            Random random=new Random();  </span><br><span class="line">          </span><br><span class="line">            boolean temp=   object.execute(random.nextInt(1000));  </span><br><span class="line">            return  temp;  </span><br><span class="line">    &#125;  </span><br><span class="line">  </span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><h5 id="获取返回值，参数等信息"><a href="#获取返回值，参数等信息" class="headerlink" title="获取返回值，参数等信息"></a>获取返回值，参数等信息</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">/* BTrace Script Template */  </span><br><span class="line">import com.sun.btrace.annotations.*;  </span><br><span class="line">import static com.sun.btrace.BTraceUtils.*;  </span><br><span class="line">  </span><br><span class="line">@BTrace  </span><br><span class="line">public class TracingScript &#123;  </span><br><span class="line">    /* put your code here */  </span><br><span class="line">/*指明要查看的方法，类*/  </span><br><span class="line">  @OnMethod(  </span><br><span class="line">     clazz=&quot;baby.btrace.CaseObject&quot;,  </span><br><span class="line">     method=&quot;execute&quot;,  </span><br><span class="line">     location=@Location(Kind.RETURN)  </span><br><span class="line">  )  </span><br><span class="line">/*主要两个参数是对象自己的引用 和 返回值，其它参数都是方法调用时传入的参数*/  </span><br><span class="line">   public static void traceExecute(@Self baby.btrace.CaseObject object,int sleepTime, @Return boolean result)&#123;  </span><br><span class="line">      println(&quot;调用堆栈！！&quot;);  </span><br><span class="line">       println(strcat(&quot;返回结果是：&quot;,str(result)));  </span><br><span class="line">      jstack();  </span><br><span class="line">      println(strcat(&quot;时间是：&quot;,str(sleepTime)));  </span><br><span class="line">   &#125;  </span><br><span class="line">  </span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h5 id="获取对象属性值"><a href="#获取对象属性值" class="headerlink" title="获取对象属性值"></a>获取对象属性值</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">/* BTrace Script Template */  </span><br><span class="line">import com.sun.btrace.annotations.*;  </span><br><span class="line">import static com.sun.btrace.BTraceUtils.*;  </span><br><span class="line">  </span><br><span class="line">@BTrace  </span><br><span class="line">public class TracingScript &#123;  </span><br><span class="line">    /* put your code here */  </span><br><span class="line">/*指明要查看的方法，类*/  </span><br><span class="line">  @OnMethod(  </span><br><span class="line">     clazz=&quot;baby.btrace.CaseObject&quot;,  </span><br><span class="line">     method=&quot;execute&quot;,  </span><br><span class="line">     location=@Location(Kind.RETURN)  </span><br><span class="line">  )  </span><br><span class="line">/*主要两个参数是对象自己的引用 和 返回值，其它参数都是方法调用时传入的参数*/  </span><br><span class="line">   public static void traceExecute(@Self baby.btrace.CaseObject object,int sleepTime, @Return boolean result)&#123;  </span><br><span class="line">      println(&quot;调用堆栈！！&quot;);  </span><br><span class="line">       println(strcat(&quot;返回结果是：&quot;,str(result)));  </span><br><span class="line">      jstack();  </span><br><span class="line">      println(strcat(&quot;时间是：&quot;,str(sleepTime)));  </span><br><span class="line">   &#125;  </span><br><span class="line">  </span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><h5 id="获取方法执行时长"><a href="#获取方法执行时长" class="headerlink" title="获取方法执行时长"></a>获取方法执行时长</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">import com.sun.btrace.annotations.*;  </span><br><span class="line">import static com.sun.btrace.BTraceUtils.*;  </span><br><span class="line">  </span><br><span class="line">@BTrace  </span><br><span class="line">public class TracingScript3 &#123;  </span><br><span class="line">   @TLS private static long startTime = 0;  </span><br><span class="line">     </span><br><span class="line">   @OnMethod(  </span><br><span class="line">      clazz=&quot;baby.btrace.CaseObject&quot;,  </span><br><span class="line">      method=&quot;execute&quot;  </span><br><span class="line">   )   </span><br><span class="line">   public static void startExecute()&#123;  </span><br><span class="line">     startTime = timeNanos();  </span><br><span class="line">   &#125;   </span><br><span class="line">      </span><br><span class="line">   @OnMethod(  </span><br><span class="line">      clazz=&quot;baby.btrace.CaseObject&quot;,  </span><br><span class="line">      method=&quot;execute&quot;,  </span><br><span class="line">      location=@Location(Kind.RETURN)  </span><br><span class="line">   )   </span><br><span class="line">   public static void endExecute(@Duration long duration)&#123;  </span><br><span class="line">     long time = timeNanos() - startTime;  </span><br><span class="line">     println(strcat(&quot;execute time(nanos): &quot;, str(time)));  </span><br><span class="line">     println(strcat(&quot;duration(nanos): &quot;, str(duration)));  </span><br><span class="line">   &#125;   </span><br><span class="line">&#125;  </span><br></pre></td></tr></table></figure><h5 id="正则匹配和获取方法执行次数"><a href="#正则匹配和获取方法执行次数" class="headerlink" title="正则匹配和获取方法执行次数"></a>正则匹配和获取方法执行次数</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">import com.sun.btrace.annotations.*;  </span><br><span class="line">import static com.sun.btrace.BTraceUtils.*;  </span><br><span class="line">  </span><br><span class="line">@BTrace  </span><br><span class="line">public class TracingScript4 &#123;  </span><br><span class="line">   private static long count;   </span><br><span class="line">       </span><br><span class="line">   @OnMethod(  </span><br><span class="line">      clazz=&quot;/.*/&quot;,  </span><br><span class="line">      method=&quot;execute&quot;,  </span><br><span class="line">      location=@Location(value=Kind.CALL, clazz=&quot;/.*/&quot;, method=&quot;sleep&quot;)  </span><br><span class="line">   )  </span><br><span class="line">   public static void traceExecute(@ProbeClassName String pcm, @ProbeMethodName String pmn,  </span><br><span class="line">   @TargetInstance Object instance,  @TargetMethodOrField String method)&#123;  </span><br><span class="line">     println(&quot;====== &quot;);  </span><br><span class="line">     println(strcat(&quot;ProbeClassName: &quot;,pcm));  </span><br><span class="line">     println(strcat(&quot;ProbeMethodName: &quot;,pmn));  </span><br><span class="line">     println(strcat(&quot;TargetInstance: &quot;,str(classOf(instance))));  </span><br><span class="line">     println(strcat(&quot;TargetMethodOrField : &quot;,str(method)));  </span><br><span class="line">     count++;  </span><br><span class="line">   &#125;  </span><br><span class="line">     </span><br><span class="line">   @OnEvent  </span><br><span class="line">   public static void getCount()&#123;  </span><br><span class="line">       println(strcat(&quot;count: &quot;, str(count)));  </span><br><span class="line">   &#125;  </span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><h5 id="正则和事件交互"><a href="#正则和事件交互" class="headerlink" title="正则和事件交互"></a>正则和事件交互</h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">import com.sun.btrace.annotations.*;  </span><br><span class="line">import static com.sun.btrace.BTraceUtils.*;  </span><br><span class="line">  </span><br><span class="line">@BTrace  </span><br><span class="line">public class TracingScript5 &#123;  </span><br><span class="line">   private static long count;   </span><br><span class="line">       </span><br><span class="line">   @OnMethod(  </span><br><span class="line">      clazz=&quot;/.*/&quot;,  </span><br><span class="line">      method=&quot;execute&quot;,  </span><br><span class="line">      location=@Location(value=Kind.CALL, clazz=&quot;/.*/&quot;, method=&quot;sleep&quot;)  </span><br><span class="line">   )  </span><br><span class="line">   public static void traceExecute(@ProbeClassName String pcm, @ProbeMethodName String pmn,  </span><br><span class="line">   @TargetInstance Object instance,  @TargetMethodOrField String method)&#123;  </span><br><span class="line">     println(&quot;====== &quot;);  </span><br><span class="line">     println(strcat(&quot;ProbeClassName: &quot;,pcm));  </span><br><span class="line">     println(strcat(&quot;ProbeMethodName: &quot;,pmn));  </span><br><span class="line">     println(strcat(&quot;TargetInstance: &quot;,str(classOf(instance))));  </span><br><span class="line">     println(strcat(&quot;TargetMethodOrField : &quot;,str(method)));  </span><br><span class="line">     count++;  </span><br><span class="line">   &#125;  </span><br><span class="line">     </span><br><span class="line">   @OnEvent  </span><br><span class="line">   public static void getCount()&#123;  </span><br><span class="line">       println(strcat(&quot;count: &quot;, str(count)));  </span><br><span class="line">   &#125;  </span><br><span class="line">    @OnEvent(&quot;A&quot;)  </span><br><span class="line">   public static void getCountA()&#123;  </span><br><span class="line">        println(&quot;==AAAA==== &quot;);  </span><br><span class="line">       println(strcat(&quot;count: &quot;, str(count)));  </span><br><span class="line">   &#125;  </span><br><span class="line">    @OnEvent(&quot;B&quot;)  </span><br><span class="line">   public static void getCountB()&#123;  </span><br><span class="line">        println(&quot;==BBB==== &quot;);  </span><br><span class="line">       println(strcat(&quot;count: &quot;, str(count)));  </span><br><span class="line">   &#125;  </span><br><span class="line">&#125; </span><br></pre></td></tr></table></figure><h2 id="strace"><a href="#strace" class="headerlink" title="strace"></a>strace</h2><p>strace常用来跟踪进程执行时的系统调用和所接收的信号。 在Linux世界，进程不能直接访问硬件设备，当进程需要访问硬件设备(比如读取磁盘文件，接收网络数据等等)时，必须由用户态模式切换至内核态模式，通过系统调用访问硬件设备。strace可以跟踪到一个进程产生的系统调用,包括参数，返回值，执行消耗的时间。</p><h3 id="输出参数含义"><a href="#输出参数含义" class="headerlink" title="输出参数含义"></a>输出参数含义</h3><p>每一行都是一条系统调用，等号左边是系统调用的函数名及其参数，右边是该调用的返回值。 strace 显示这些调用的参数并返回符号形式的值。strace 从内核接收信息，而且不需要以任何特殊的方式来构建内核。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">$strace cat /dev/null</span><br><span class="line">execve(&quot;/bin/cat&quot;, [&quot;cat&quot;, &quot;/dev/null&quot;], [/* 22 vars */]) = 0</span><br><span class="line">brk(0)                                  = 0xab1000</span><br><span class="line">access(&quot;/etc/ld.so.nohwcap&quot;, F_OK)      = -1 ENOENT (No such file or directory)</span><br><span class="line">mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f29379a7000</span><br><span class="line">access(&quot;/etc/ld.so.preload&quot;, R_OK)      = -1 ENOENT (No such file or directory)</span><br></pre></td></tr></table></figure><p>参数</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br></pre></td><td class="code"><pre><span class="line">-c 统计每一系统调用的所执行的时间,次数和出错的次数等.</span><br><span class="line">-d 输出strace关于标准错误的调试信息.</span><br><span class="line">-f 跟踪由fork调用所产生的子进程.</span><br><span class="line">-ff 如果提供-o filename,则所有进程的跟踪结果输出到相应的filename.pid中,pid是各进程的进程号.</span><br><span class="line">-F 尝试跟踪vfork调用.在-f时,vfork不被跟踪.</span><br><span class="line">-h 输出简要的帮助信息.</span><br><span class="line">-i 输出系统调用的入口指针.</span><br><span class="line">-q 禁止输出关于脱离的消息.</span><br><span class="line">-r 打印出相对时间关于,,每一个系统调用.</span><br><span class="line">-t 在输出中的每一行前加上时间信息.</span><br><span class="line">-tt 在输出中的每一行前加上时间信息,微秒级.</span><br><span class="line">-ttt 微秒级输出,以秒了表示时间.</span><br><span class="line">-T 显示每一调用所耗的时间.</span><br><span class="line">-v 输出所有的系统调用.一些调用关于环境变量,状态,输入输出等调用由于使用频繁,默认不输出.</span><br><span class="line">-V 输出strace的版本信息.</span><br><span class="line">-x 以十六进制形式输出非标准字符串</span><br><span class="line">-xx 所有字符串以十六进制形式输出.</span><br><span class="line">-a column</span><br><span class="line">设置返回值的输出位置.默认 为40.</span><br><span class="line">-e expr</span><br><span class="line">指定一个表达式,用来控制如何跟踪.格式如下:</span><br><span class="line">[qualifier=][!]value1[,value2]...</span><br><span class="line">qualifier只能是 trace,abbrev,verbose,raw,signal,read,write其中之一.value是用来限定的符号或数字.默认的 qualifier是 trace.感叹号是否定符号.例如:</span><br><span class="line">-eopen等价于 -e trace=open,表示只跟踪open调用.而-etrace!=open表示跟踪除了open以外的其他调用.有两个特殊的符号 all 和 none.</span><br><span class="line">注意有些shell使用!来执行历史记录里的命令,所以要使用\\.</span><br><span class="line">-e trace=set</span><br><span class="line">只跟踪指定的系统 调用.例如:-e trace=open,close,rean,write表示只跟踪这四个系统调用.默认的为set=all.</span><br><span class="line">-e trace=file</span><br><span class="line">只跟踪有关文件操作的系统调用.</span><br><span class="line">-e trace=process</span><br><span class="line">只跟踪有关进程控制的系统调用.</span><br><span class="line">-e trace=network</span><br><span class="line">跟踪与网络有关的所有系统调用.</span><br><span class="line">-e strace=signal</span><br><span class="line">跟踪所有与系统信号有关的 系统调用</span><br><span class="line">-e trace=ipc</span><br><span class="line">跟踪所有与进程通讯有关的系统调用</span><br><span class="line">-e abbrev=set</span><br><span class="line">设定 strace输出的系统调用的结果集.-v 等与 abbrev=none.默认为abbrev=all.</span><br><span class="line">-e raw=set</span><br><span class="line">将指 定的系统调用的参数以十六进制显示.</span><br><span class="line">-e signal=set</span><br><span class="line">指定跟踪的系统信号.默认为all.如 signal=!SIGIO(或者signal=!io),表示不跟踪SIGIO信号.</span><br><span class="line">-e read=set</span><br><span class="line">输出从指定文件中读出 的数据.例如:</span><br><span class="line">-e read=3,5</span><br><span class="line">-e write=set</span><br><span class="line">输出写入到指定文件中的数据.</span><br><span class="line">-o filename</span><br><span class="line">将strace的输出写入文件filename</span><br><span class="line">-p pid</span><br><span class="line">跟踪指定的进程pid.</span><br><span class="line">-s strsize</span><br><span class="line">指定输出的字符串的最大长度.默认为32.文件名一直全部输出.</span><br><span class="line">-u username</span><br><span class="line">以username 的UID和GID执行被跟踪的命令</span><br></pre></td></tr></table></figure><h3 id="跟踪可执行程序"><a href="#跟踪可执行程序" class="headerlink" title="跟踪可执行程序"></a>跟踪可执行程序</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">strace -f -F -o ~/straceout.txt myserver</span><br></pre></td></tr></table></figure><p>-f -F选项告诉strace同时跟踪fork和vfork出来的进程，-o选项把所有strace输出写到~&#x2F;straceout.txt里 面，myserver是要启动和调试的程序。</p><h3 id="跟踪服务程序"><a href="#跟踪服务程序" class="headerlink" title="跟踪服务程序"></a>跟踪服务程序</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">strace -o output.txt -T -tt -e trace=all -p 28979</span><br></pre></td></tr></table></figure><p>跟踪28979进程的所有系统调用（-e trace&#x3D;all），并统计系统调用的花费时间，以及开始时间（并以可视化的时分秒格式显示），最后将记录结果存在output.txt文件里面。</p>]]>
    </content>
    <id>https://ilongda.com/2015/docs/tools/monitor_tools/trace/</id>
    <link href="https://ilongda.com/2015/docs/tools/monitor_tools/trace/"/>
    <published>2015-10-16T11:42:57.000Z</published>
    <summary>Java/系统 trace 工具笔记：BTrace 动态跟踪 Java 运行时方法调用，strace 跟踪 mysqld 系统调用耗时分析</summary>
    <title>监控工具 -- trace 工具</title>
    <updated>2026-06-09T08:46:25.966Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具 -- sar","description":"sar 系统活动报告工具大全：CPU、内存、磁盘 I/O、网络、队列与进程等全方位性能数据采集与解读，更多细节与示例见正文。","image":"https://ilongda.com/assets/sar_csv.jpg","wordCount":3379,"datePublished":"2015-10-15T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.965Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/tools/monitor_tools/sar/"},"url":"https://ilongda.com/2015/docs/tools/monitor_tools/sar/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具 -- sar","item":"https://ilongda.com/2015/docs/tools/monitor_tools/sar/"}]}</script><h1 id="监控神器sar-–-使用大全"><a href="#监控神器sar-–-使用大全" class="headerlink" title="监控神器sar – 使用大全"></a>监控神器sar – 使用大全</h1><h2 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h2><p>sar（System Activity Reporter 系统活动情况报告）是目前 Linux 上最为全面的系统性能分析工具之一，可以从多方面对系统的活动进行报告，包括：文件的读写情况、系统调用的使用情况、磁盘 I&#x2F;O、CPU 效率、内存使用状况、进程活动及 IPC 有关的活动等。<br>我们可以使用sar命令来获得整个系统性能的报告。这有助于我们定位系统性能的瓶颈，并且有助于我们找出这些烦人的性能问题的解决方法。<br>Linux 内核维护着一些内部计数器，这些计数器包含了所有的请求及其完成时间和 I&#x2F;O 块数等信息，sar命令从所有的这些信息中计算出请求的利用率和比例，以便找出瓶颈所在。<br>sar命令主要的用途是生成某段时间内所有活动的报告，因此必需确保sar命令在适当的时间进行数据采集（而不是在午餐时间或者周末）<br>原文地址: <span class="exturl" data-url="aHR0cHM6Ly9zaG9ja2VybGkubmV0L3Bvc3QvbGludXgtdG9vbC1zYXIv">https://shockerli.net/post/li...<i class="fa fa-external-link-alt"></i></span></p><h2 id="命令参数"><a href="#命令参数" class="headerlink" title="命令参数"></a>命令参数</h2><p>用法: sar [ 选项 ] [ &lt;时间间隔&gt; [ &lt;次数&gt; ] ]</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">主选项和报告：</span><br><span class="line">    -b    I/O 和传输速率信息状况    </span><br><span class="line">    -B    分页状况    </span><br><span class="line">    -d    块设备状况    </span><br><span class="line">    -I &#123; &lt;中断&gt; | SUM | ALL | XALL &#125;        中断信息状况</span><br><span class="line">    -m    电源管理信息状况    </span><br><span class="line">    -n &#123; &lt;关键词&gt; [,...] | ALL &#125;        网络统计信息</span><br><span class="line">        关键词可以是：</span><br><span class="line">        DEV     网卡</span><br><span class="line">        EDEV    网卡 (错误)</span><br><span class="line">        NFS     NFS 客户端</span><br><span class="line">        NFSD    NFS 服务器</span><br><span class="line">        SOCK    Sockets (套接字)    (v4)</span><br><span class="line">        IP      IP  流          (v4)</span><br><span class="line">        EIP     IP 流       (v4) (错误)</span><br><span class="line">        ICMP    ICMP 流    (v4)</span><br><span class="line">        EICMP   ICMP 流    (v4) (错误)</span><br><span class="line">        TCP     TCP 流  (v4)</span><br><span class="line">        ETCP    TCP 流  (v4) (错误)</span><br><span class="line">        UDP     UDP 流  (v4)</span><br><span class="line">        SOCK6   Sockets (套接字)    (v6)</span><br><span class="line">        IP6     IP 流       (v6)</span><br><span class="line">        EIP6    IP 流       (v6) (错误)</span><br><span class="line">        ICMP6   ICMP 流 (v6)</span><br><span class="line">        EICMP6  ICMP 流 (v6) (错误)</span><br><span class="line">        UDP6    UDP 流       (v6)</span><br><span class="line">    -q    队列长度和平均负载    </span><br><span class="line">    -r    内存利用率    </span><br><span class="line">    -R    内存状况    </span><br><span class="line">    -S    交换空间利用率    </span><br><span class="line">    -u [ ALL ]        CPU 利用率</span><br><span class="line">    -v    Kernel table 状况    </span><br><span class="line">    -w    任务创建与系统转换统计信息    </span><br><span class="line">    -W    交换信息    </span><br><span class="line">    -y    TTY 设备状况    </span><br><span class="line">    -o &#123;&lt;文件路径&gt;&#125;       将命令结果以二进制格式存放在指定文件中</span><br></pre></td></tr></table></figure><h3 id="依赖"><a href="#依赖" class="headerlink" title="依赖"></a><strong>依赖</strong></h3><p>sar命令来自于sysstat工具包，如果提示sar命令不存在，需先安装sysstat。</p><h3 id="网络统计信息"><a href="#网络统计信息" class="headerlink" title="网络统计信息"></a><strong>网络统计信息</strong></h3><p>sar -n &lt;关键词&gt; [ &lt;时间间隔&gt; [ &lt;次数&gt; ] ]</p><p><strong>示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sar -n DEV 1 5</span><br><span class="line"></span><br><span class="line">平均时间:     IFACE   rxpck/s   txpck/s    rxkB/s    txkB/s   rxcmp/s   txcmp/s  rxmcst/s</span><br><span class="line">平均时间:        lo      2.21      2.21      0.18      0.18      0.00      0.00      0.00</span><br><span class="line">平均时间:      eth0      4.62      3.82      0.37      1.90      0.00      0.00      0.00</span><br></pre></td></tr></table></figure><p>命令中 1 5 表示每一秒钟取 1 次值，一共取 5 次。<br>命令执行后会列出每个网卡这 5 次取值的平均数据，根据实际情况来确定带宽跑满的网卡名称，默认情况下 eth0 为内网网卡，eth1 为外网网卡。</p><h3 id="CPU-利用率"><a href="#CPU-利用率" class="headerlink" title="CPU 利用率"></a><strong>CPU 利用率</strong></h3><p>sar -u [ &lt;时间间隔&gt; [ &lt;次数&gt; ] ]</p><p><strong>示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">sar -u 1 3   </span><br><span class="line">Linux 2.6.32-642.13.1.el6.x86_64 (upfor106) 2018年04月25日 _x86_64_ (1 CPU)</span><br><span class="line">   </span><br><span class="line">10时33分08秒     CPU     %user     %nice   %system   %iowait    %steal     %idle</span><br><span class="line">10时33分09秒     all      0.00      0.00      0.00      0.00      0.00    100.00</span><br><span class="line">10时33分10秒     all      0.99      0.00      0.99      0.00      0.00     98.02</span><br><span class="line">10时33分11秒     all      0.00      0.00      0.00      0.00      0.00    100.00</span><br><span class="line">平均时间:        all      0.33      0.00      0.33      0.00      0.00     99.33</span><br></pre></td></tr></table></figure><p>命令中 1 3 表示每一秒钟取 1 次值，一共取 3 次。</p><p><strong>输出项说明：</strong></p><ul><li>CPU：all 表示统计信息为所有 CPU 的平均值。</li><li>%user：显示在用户级别(application)运行使用 CPU 总时间的百分比</li><li>%nice：显示在用户级别，用于nice操作，所占用 CPU 总时间的百分比</li><li>%system：在核心级别(kernel)运行所使用 CPU 总时间的百分比%iowait：显示用于等待I&#x2F;O操作占用 CPU 总时间的百分比</li><li>%steal：管理程序(hypervisor)为另一个虚拟进程提供服务而等待虚拟 CPU 的百分比%idle：显示 CPU 空闲时间占用 CPU 总时间的百分比</li></ul><ol><li>若 %iowait 的值过高，表示硬盘存在I&#x2F;O瓶颈</li><li>若 %idle 的值高但系统响应慢时，有可能是 CPU 等待分配内存，此时应加大内存容量</li><li>若 %idle 的值持续低于1，则系统的 CPU 处理能力相对较低，表明系统中最需要解决的资源是 CPU</li></ol><h3 id="索引节点，文件和其他内核表的状态"><a href="#索引节点，文件和其他内核表的状态" class="headerlink" title="索引节点，文件和其他内核表的状态"></a><strong>索引节点，文件和其他内核表的状态</strong></h3><p>sar -v [ &lt;时间间隔&gt; [ &lt;次数&gt; ] ]</p><p><strong>示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">sar -v 1 3</span><br><span class="line"></span><br><span class="line">Linux 2.6.32-696.13.2.el6.x86_64 (upfor163) 2018年04月25日 _x86_64_ (2 CPU)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">10时50分29秒 dentunusd   file-nr  inode-nr    pty-nr</span><br><span class="line">10时50分30秒    920045       864     13910         2</span><br><span class="line">10时50分31秒    920045       864     13910         2</span><br><span class="line">10时50分32秒    920045       896     13910         2</span><br><span class="line">平均时间:       920045       875     13910         2</span><br></pre></td></tr></table></figure><p><strong>输出项说明：</strong></p><ul><li>dentunusd：目录高速缓存中未被使用的条目数量</li><li>file-nr：文件句柄（file handle）的使用数量</li><li>inode-nr：索引节点句柄（inode handle）的使用数量</li><li>pty-nr：使用的 pty 数量</li></ul><h3 id="内存利用率"><a href="#内存利用率" class="headerlink" title="内存利用率"></a><strong>内存利用率</strong></h3><p>sar -r [ &lt;时间间隔&gt; [ &lt;次数&gt; ] ]<br><strong>示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">sar -r 1 3</span><br><span class="line"></span><br><span class="line">Linux 2.6.32-696.13.2.el6.x86_64 (upfor163) 2018年04月25日 _x86_64_ (2 CPU)</span><br><span class="line"></span><br><span class="line">10时53分00秒 kbmemfree kbmemused  %memused kbbuffers  kbcached  kbcommit   %commit</span><br><span class="line">10时53分01秒   2027760   2028732     50.01    145492   1243820   1163900     28.69</span><br><span class="line">10时53分02秒   2027620   2028872     50.02    145492   1243820   1163896     28.69</span><br><span class="line">10时53分03秒   2028100   2028392     50.00    145492   1243820   1163900     28.69</span><br><span class="line">平均时间:      2027827   2028665     50.01    145492   1243820   1163899     28.69</span><br></pre></td></tr></table></figure><p><strong>输出项说明：</strong></p><ul><li>kbmemfree：这个值和 free 命令中的 free 值基本一致，所以它不包括 buffer 和 cache 的空间</li><li>kbmemused：这个值和 free 命令中的 used 值基本一致,所以它包括 buffer 和 cache 的空间</li><li>%memused：这个值是 kbmemused 和内存总量(不包括 swap)的一个百分比</li><li>kbbuffers 和 kbcached：这两个值就是 free 命令中的 buffer 和 cache</li><li>kbcommit：保证当前系统所需要的内存，即为了确保不溢出而需要的内存(RAM + swap)</li><li>%commit：这个值是 kbcommit 与内存总量(包括 swap)的一个百分比</li></ul><h3 id="内存分页状况"><a href="#内存分页状况" class="headerlink" title="内存分页状况"></a><strong>内存分页状况</strong></h3><p>sar -B [ &lt;时间间隔&gt; [ &lt;次数&gt; ] ]<br><strong>示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">sar -B 1 3</span><br><span class="line">Linux 2.6.32-696.13.2.el6.x86_64 (upfor163) 2018年04月25日 _x86_64_ (2 CPU)</span><br><span class="line"></span><br><span class="line">10时55分41秒  pgpgin/s pgpgout/s   fault/s  majflt/s  pgfree/s pgscank/s pgscand/s pgsteal/s    %vmeff</span><br><span class="line">10时55分42秒      0.00      0.00   5723.76      0.00   3356.44      0.00      0.00      0.00      0.00</span><br><span class="line">10时55分43秒      0.00      0.00   1185.00      0.00    312.00      0.00      0.00      0.00      0.00</span><br><span class="line">10时55分44秒      0.00      0.00     27.00      0.00     56.00      0.00      0.00      0.00      0.00</span><br><span class="line">平均时间:         0.00      0.00   2323.26      0.00   1248.50      0.00      0.00      0.00      0.00</span><br></pre></td></tr></table></figure><p><strong>输出项说明：</strong></p><ul><li>pgpgin&#x2F;s：表示每秒从磁盘或SWAP置换到内存的字节数(KB)</li><li>pgpgout&#x2F;s：表示每秒从内存置换到磁盘或SWAP的字节数(KB)</li><li>fault&#x2F;s：每秒钟系统产生的缺页数，即主缺页与次缺页之和(major + minor)</li><li>majflt&#x2F;s：每秒钟产生的主缺页数</li><li>pgfree&#x2F;s：每秒被放入空闲队列中的页个数</li><li>pgscank&#x2F;s：每秒被 kswapd 扫描的页个数</li><li>pgscand&#x2F;s：每秒直接被扫描的页个数</li><li>pgsteal&#x2F;s：每秒钟从 cache 中被清除来满足内存需要的页个数</li><li>%vmeff：每秒清除的页(pgsteal)占总扫描页(pgscank + pgscand)的百分比</li></ul><h3 id="I-O-和传输速率信息状况"><a href="#I-O-和传输速率信息状况" class="headerlink" title="I&#x2F;O 和传输速率信息状况"></a><strong>I&#x2F;O 和传输速率信息状况</strong></h3><p>sar -b [ &lt;时间间隔&gt; [ &lt;次数&gt; ] ]<br><strong>示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">sar -b 1 3</span><br><span class="line">Linux 2.6.32-696.13.2.el6.x86_64 (upfor163) 2018年04月25日 _x86_64_ (2 CPU)</span><br><span class="line"></span><br><span class="line">10时58分15秒       tps      rtps      wtps   bread/s   bwrtn/s</span><br><span class="line">10时58分16秒      7.00      0.00      7.00      0.00     64.00</span><br><span class="line">10时58分17秒      4.04      0.00      4.04      0.00     80.81</span><br><span class="line">10时58分18秒      0.00      0.00      0.00      0.00      0.00</span><br><span class="line">平均时间:         3.67      0.00      3.67      0.00     48.00</span><br></pre></td></tr></table></figure><p><strong>输出项说明：</strong></p><ul><li>tps：每秒钟物理设备的 I&#x2F;O 传输总量</li><li>rtps：每秒钟从物理设备读入的数据总量</li><li>wtps：每秒钟向物理设备写入的数据总量</li><li>bread&#x2F;s：每秒钟从物理设备读入的数据量，单位为：块&#x2F;s</li><li>bwrtn&#x2F;s：每秒钟向物理设备写入的数据量，单位为：块&#x2F;s</li></ul><h3 id="队列长度和平均负载"><a href="#队列长度和平均负载" class="headerlink" title="队列长度和平均负载"></a><strong>队列长度和平均负载</strong></h3><p>sar -q [ &lt;时间间隔&gt; [ &lt;次数&gt; ] ]<br><strong>示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">sar -q 1 3</span><br><span class="line"></span><br><span class="line">Linux 2.6.32-696.13.2.el6.x86_64 (upfor163) 2018年04月25日 _x86_64_ (2 CPU) </span><br><span class="line"></span><br><span class="line">11时00分35秒   runq-sz  plist-sz   ldavg-1   ldavg-5  ldavg-15</span><br><span class="line">11时00分36秒         0       268      0.00      0.00      0.00</span><br><span class="line">11时00分37秒         0       268      0.00      0.00      0.00</span><br><span class="line">11时00分38秒         0       268      0.00      0.00      0.00</span><br><span class="line">平均时间:            0       268      0.00      0.00      0.00</span><br></pre></td></tr></table></figure><p><strong>输出项说明：</strong></p><ul><li>runq-sz：运行队列的长度（等待运行的进程数）</li><li>plist-sz：进程列表中进程（processes）和线程（threads）的数量</li><li>ldavg-1：最后1分钟的系统平均负载（System load average）</li><li>ldavg-5：过去5分钟的系统平均负载</li><li>ldavg-15：过去15分钟的系统平均负载</li></ul><h3 id="系统交换信息"><a href="#系统交换信息" class="headerlink" title="系统交换信息"></a><strong>系统交换信息</strong></h3><p>sar -W [ &lt;时间间隔&gt; [ &lt;次数&gt; ] ]<br><strong>示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">sar -W 1 3</span><br><span class="line">Linux 2.6.32-696.13.2.el6.x86_64 (upfor163) 2018年04月25日 _x86_64_ (2 CPU)</span><br><span class="line"></span><br><span class="line">11时01分45秒  pswpin/s     pswpout/s</span><br><span class="line">11时01分46秒      0.00      0.00</span><br><span class="line">11时01分47秒      0.00      0.00</span><br><span class="line">11时01分48秒      0.00      0.00</span><br><span class="line">平均时间:         0.00      0.00</span><br></pre></td></tr></table></figure><p><strong>输出项说明：</strong></p><ul><li>pswpin&#x2F;s：每秒系统换入的交换页面（swap page）数量</li><li>pswpout&#x2F;s：每秒系统换出的交换页面（swap page）数量</li></ul><h3 id="块设备状况"><a href="#块设备状况" class="headerlink" title="块设备状况"></a><strong>块设备状况</strong></h3><p>sar -d [ &lt;时间间隔&gt; [ &lt;次数&gt; ] ]<br><strong>示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">sar -d 1 3</span><br><span class="line">Linux 2.6.32-696.13.2.el6.x86_64 (upfor163) 2018年04月25日 _x86_64_ (2 CPU)</span><br><span class="line"></span><br><span class="line">11时02分46秒       DEV       tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util</span><br><span class="line">11时02分47秒  dev252-0      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00</span><br><span class="line">11时02分47秒       DEV       tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util</span><br><span class="line">11时02分48秒  dev252-0      6.06      0.00     64.65     10.67      0.00      0.00      0.00      0.00</span><br><span class="line">11时02分48秒       DEV       tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util</span><br><span class="line">11时02分49秒  dev252-0      0.00      0.00      0.00      0.00      0.00      0.00      0.00      0.00</span><br><span class="line">平均时间:       DEV       tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util</span><br><span class="line">平均时间:  dev252-0      2.00      0.00     21.33     10.67      0.00      0.00      0.00      0.00</span><br></pre></td></tr></table></figure><p><strong>输出项说明：</strong></p><ul><li>tps: 每秒从物理磁盘 I&#x2F;O 的次数。多个逻辑请求会被合并为一个 I&#x2F;O 磁盘请求，一次传输的大小是不确定的</li><li>rd_sec&#x2F;s: 每秒读扇区的次数</li><li>wr_sec&#x2F;s: 每秒写扇区的次数</li><li>avgrq-sz: 平均每次设备 I&#x2F;O 操作的数据大小(扇区)</li><li>avgqu-sz: 磁盘请求队列的平均长度</li><li>await: 从请求磁盘操作到系统完成处理，每次请求的平均消耗时间，包括请求队列等待时间，单位是毫秒(1秒&#x3D;1000毫秒)</li><li>svctm: 系统处理每次请求的平均时间,不包括在请求队列中消耗的时间.</li><li>%util: I&#x2F;O请求占CPU的百分比，比率越大，说明越饱和    <ol><li>avgqu-sz 的值较低时，设备的利用率较高    </li><li>当%util的值接近 1% 时，表示设备带宽已经占满</li></ol></li></ul><h3 id="输出统计的数据信息"><a href="#输出统计的数据信息" class="headerlink" title="输出统计的数据信息"></a><strong>输出统计的数据信息</strong></h3><p>sar -o path_file [选项] [ &lt;时间间隔&gt; [ &lt;次数&gt; ] ]<br><strong>示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sar -o sarfile.log -u 1 3</span><br></pre></td></tr></table></figure><p>上述示例命令会将sar -u 1 3采集到的数据以二进制的格式存放到文件sarfile.log中。<br>我们还可以通过命令sadf -d sarfile.log将二进制数据文件转换成数据库可读的格式。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">sadf -d sarfile.log</span><br><span class="line">hostname;interval;timestamp;CPU;%user;%nice;%system;%iowait;%steal;%idle</span><br><span class="line">upfor163;1;2018-04-25 03:15:02 UTC;-1;0.00;0.00;0.50;0.50;0.00;99.00</span><br><span class="line">upfor163;1;2018-04-25 03:15:03 UTC;-1;1.01;0.00;0.00;0.00;0.00;98.99</span><br><span class="line">upfor163;1;2018-04-25 03:15:04 UTC;-1;0.00;0.00;0.00;0.00;0.00;100.00</span><br></pre></td></tr></table></figure><p>也可以将这些数据存储在一个 csv 文档中，然后绘制成图表展示方式，如下所示：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sadf -d sarfile.log | sed &#x27;s/;/,/g&#x27; &gt; sarfile.csv</span><br></pre></td></tr></table></figure><p><img data-src="/assets/sar_csv.jpg" alt="image.jpeg"></p><h3 id="从数据文件读取信息"><a href="#从数据文件读取信息" class="headerlink" title="从数据文件读取信息"></a><strong>从数据文件读取信息</strong></h3><p>sar -f &lt;文件路径&gt;<br><strong>示例：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">sar -f sarfile.log</span><br><span class="line"></span><br><span class="line">Linux 2.6.32-696.13.2.el6.x86_64 (upfor163) 2018年04月25日 _x86_64_ (2 CPU)11时15分01秒     </span><br><span class="line">CPU     %user     %nice   %system   %iowait    %steal     %idle</span><br><span class="line">11时15分02秒     all      0.00      0.00      0.50      0.50      0.00     99.00</span><br><span class="line">11时15分03秒     all      1.01      0.00      0.00      0.00      0.00     98.99</span><br><span class="line">11时15分04秒     all      0.00      0.00      0.00      0.00      0.00    100.00</span><br><span class="line">平均时间:        all      0.33      0.00      0.17      0.17      0.00     99.33</span><br></pre></td></tr></table></figure><p>又将之前存储在二进制文件中的数据给读取并展示出来。</p><h2 id="性能问题排查技巧"><a href="#性能问题排查技巧" class="headerlink" title="性能问题排查技巧"></a><strong>性能问题排查技巧</strong></h2><ul><li><p>怀疑 CPU 存在瓶颈，可用sar -u和sar -q等来查看</p></li><li><p>怀疑内存存在瓶颈，可用sar -B、sar -r和sar -W等来查看</p></li><li><p>怀疑 I&#x2F;O 存在瓶颈，可用sar -b、sar -u和sar -d等来查看</p></li></ul>]]>
    </content>
    <id>https://ilongda.com/2015/docs/tools/monitor_tools/sar/</id>
    <link href="https://ilongda.com/2015/docs/tools/monitor_tools/sar/"/>
    <published>2015-10-15T11:42:57.000Z</published>
    <summary>sar 系统活动报告工具大全：CPU、内存、磁盘 I/O、网络、队列与进程等全方位性能数据采集与解读，更多细节与示例见正文。</summary>
    <title>监控工具 -- sar</title>
    <updated>2026-06-09T08:46:25.965Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具 -- pidstat","description":"pidstat 进程级监控工具笔记：按进程查看 CPU、内存、IO、上下文切换与线程统计的常用参数与示例，更多细节与示例见正文。","image":"https://ilongda.com/assets/pidstat_cmd.png","wordCount":1282,"datePublished":"2015-10-14T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.965Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/tools/monitor_tools/pidstat/"},"url":"https://ilongda.com/2015/docs/tools/monitor_tools/pidstat/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具 -- pidstat","item":"https://ilongda.com/2015/docs/tools/monitor_tools/pidstat/"}]}</script><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p><span class="exturl" data-url="aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC8zOTkxYzBkYmEwOTQ=">转载<i class="fa fa-external-link-alt"></i></span></p><p>pidstat 是sysstat工具的一个命令，用于监控全部或指定进程的cpu、内存、线程、设备IO等系统资源的占用情况。pidstat首次运行时显示自系统启动开始的各项统计信息，之后运行pidstat将显示自上次运行该命令以后的统计信息。用户可以通过指定统计的次数和时间来获得所需的统计信息。</p><h1 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h1><p>pidstat 是sysstat软件套件的一部分，sysstat包含很多监控linux系统状态的工具，它能够从大多数linux发行版的软件源中获得。</p><p>在Debian&#x2F;Ubuntu系统中可以使用下面的命令来安装:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">apt-get install sysstat</span><br></pre></td></tr></table></figure><p>CentOS&#x2F;Fedora&#x2F;RHEL版本的linux中则使用下面的命令：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yum install sysstat</span><br></pre></td></tr></table></figure><h1 id="用法"><a href="#用法" class="headerlink" title="用法"></a>用法</h1><p>pidstat 的用法：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pidstat [ 选项 ] [ &lt;时间间隔&gt; ] [ &lt;次数&gt; ]</span><br></pre></td></tr></table></figure><p>如下图：<br><img data-src="/assets/pidstat_cmd.png" alt="pidstat cmd"></p><p>常用的参数：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">-u：默认的参数，显示各个进程的cpu使用统计</span><br><span class="line">-r：显示各个进程的内存使用统计</span><br><span class="line">-d：显示各个进程的IO使用情况</span><br><span class="line">-p：指定进程号</span><br><span class="line">-w：显示每个进程的上下文切换情况</span><br><span class="line">-t：显示选择任务的线程的统计信息外的额外信息</span><br><span class="line">-T &#123; TASK | CHILD | ALL &#125;</span><br><span class="line">这个选项指定了pidstat监控的。TASK表示报告独立的task，CHILD关键字表示报告进程下所有线程统计信息。ALL表示报告独立的task和task下面的所有线程。</span><br><span class="line">注意：task和子线程的全局的统计信息和pidstat选项无关。这些统计信息不会对应到当前的统计间隔，这些统计信息只有在子线程kill或者完成的时候才会被收集。</span><br><span class="line">-V：版本号</span><br><span class="line">-h：在一行上显示了所有活动，这样其他程序可以容易解析。</span><br><span class="line">-I：在SMP环境，表示任务的CPU使用率/内核数量</span><br><span class="line">-l：显示命令名和所有参数</span><br></pre></td></tr></table></figure><h2 id="查看所有进程的-CPU-使用情况（-u-p-ALL）"><a href="#查看所有进程的-CPU-使用情况（-u-p-ALL）" class="headerlink" title="查看所有进程的 CPU 使用情况（ -u -p ALL）"></a>查看所有进程的 CPU 使用情况（ -u -p ALL）</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pidstat</span><br><span class="line">pidstat -u -p ALL</span><br></pre></td></tr></table></figure><p>pidstat 和 pidstat -u -p ALL 是等效的。<br>pidstat 默认显示了所有进程的cpu使用率。<br><img data-src="/assets/pidstat_cpu.png" alt="pidstat cpu"></p><p>详细说明<br>PID：进程ID</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">%usr：进程在用户空间占用cpu的百分比</span><br><span class="line">%system：进程在内核空间占用cpu的百分比</span><br><span class="line">%guest：进程在虚拟机占用cpu的百分比</span><br><span class="line">%CPU：进程占用cpu的百分比</span><br><span class="line">CPU：处理进程的cpu编号</span><br><span class="line">Command：当前进程对应的命令</span><br></pre></td></tr></table></figure><h2 id="cpu使用情况统计-u"><a href="#cpu使用情况统计-u" class="headerlink" title="cpu使用情况统计(-u)"></a>cpu使用情况统计(-u)</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pidstat -u</span><br></pre></td></tr></table></figure><p>使用-u选项，pidstat将显示各活动进程的cpu使用统计，执行”pidstat -u”与单独执行”pidstat”的效果一样。</p><h2 id="内存使用情况统计-r"><a href="#内存使用情况统计-r" class="headerlink" title="内存使用情况统计(-r)"></a>内存使用情况统计(-r)</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pidstat -r</span><br></pre></td></tr></table></figure><p>使用-r选项，pidstat将显示各活动进程的内存使用统计：<br><img data-src="/assets/pidstat_mem.png" alt="pidstat mem"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">PID：进程标识符</span><br><span class="line">Minflt/s:任务每秒发生的次要错误，不需要从磁盘中加载页</span><br><span class="line">Majflt/s:任务每秒发生的主要错误，需要从磁盘中加载页</span><br><span class="line">VSZ：虚拟地址大小，虚拟内存的使用KB</span><br><span class="line">RSS：常驻集合大小，非交换区五里内存使用KB</span><br><span class="line">Command：task命令名</span><br></pre></td></tr></table></figure><h2 id="显示各个进程的IO使用情况（-d）"><a href="#显示各个进程的IO使用情况（-d）" class="headerlink" title="显示各个进程的IO使用情况（-d）"></a>显示各个进程的IO使用情况（-d）</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pidstat -d</span><br></pre></td></tr></table></figure><p>报告IO统计显示以下信息：<br><img data-src="/assets/pidstat_io.png" alt="pidstat io"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">PID：进程id</span><br><span class="line">kB_rd/s：每秒从磁盘读取的KB</span><br><span class="line">kB_wr/s：每秒写入磁盘KB</span><br><span class="line">kB_ccwr/s：任务取消的写入磁盘的KB。当任务截断脏的pagecache的时候会发生。</span><br><span class="line">COMMAND:task的命令名</span><br></pre></td></tr></table></figure><h2 id="显示每个进程的上下文切换情况（-w）"><a href="#显示每个进程的上下文切换情况（-w）" class="headerlink" title="显示每个进程的上下文切换情况（-w）"></a>显示每个进程的上下文切换情况（-w）</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pidstat -w -p 2831</span><br></pre></td></tr></table></figure><p><img data-src="/assets/pidstat_switch.png" alt="pidstat switch"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">PID:进程id</span><br><span class="line">Cswch/s:每秒主动任务上下文切换数量</span><br><span class="line">Nvcswch/s:每秒被动任务上下文切换数量</span><br><span class="line">Command:命令名</span><br></pre></td></tr></table></figure><h2 id="显示进程内部的线程信息-t"><a href="#显示进程内部的线程信息-t" class="headerlink" title="显示进程内部的线程信息 (-t)"></a>显示进程内部的线程信息 (-t)</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pidstat -t -p 2831</span><br></pre></td></tr></table></figure><p><img data-src="/assets/pidstat_thread.png" alt="pidstat thread"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">TGID:主线程的表示</span><br><span class="line">TID:线程id</span><br><span class="line">%usr：进程在用户空间占用cpu的百分比</span><br><span class="line">%system：进程在内核空间占用cpu的百分比</span><br><span class="line">%guest：进程在虚拟机占用cpu的百分比</span><br><span class="line">%CPU：进程占用cpu的百分比</span><br><span class="line">CPU：处理进程的cpu编号</span><br><span class="line">Command：当前进程对应的命令</span><br></pre></td></tr></table></figure><h2 id="子进程（-T）"><a href="#子进程（-T）" class="headerlink" title="子进程（-T）"></a>子进程（-T）</h2><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">pidstat -T TASK</span><br><span class="line">pidstat -T CHILD</span><br><span class="line">pidstat -T ALL</span><br><span class="line"></span><br><span class="line">TASK表示报告独立的task。</span><br><span class="line">CHILD关键字表示报告进程下所有线程统计信息。</span><br><span class="line">ALL表示报告独立的task和task下面的所有线程。</span><br></pre></td></tr></table></figure><p>注意：task和子线程的全局的统计信息和pidstat选项无关。这些统计信息不会对应到当前的统计间隔，这些统计信息只有在子线程kill或者完成的时候才会被收集。<br><img data-src="/assets/pidstat_child.png" alt="pidstat child"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">PID:进程id</span><br><span class="line">Usr-ms:任务和子线程在用户级别使用的毫秒数。</span><br><span class="line">System-ms:任务和子线程在系统级别使用的毫秒数。</span><br><span class="line">Guest-ms:任务和子线程在虚拟机(running a virtual processor)使用的毫秒数。</span><br><span class="line">Command:命令名</span><br></pre></td></tr></table></figure>]]>
    </content>
    <id>https://ilongda.com/2015/docs/tools/monitor_tools/pidstat/</id>
    <link href="https://ilongda.com/2015/docs/tools/monitor_tools/pidstat/"/>
    <published>2015-10-14T11:42:57.000Z</published>
    <summary>pidstat 进程级监控工具笔记：按进程查看 CPU、内存、IO、上下文切换与线程统计的常用参数与示例，更多细节与示例见正文。</summary>
    <title>监控工具 -- pidstat</title>
    <updated>2026-06-09T08:46:25.965Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具 -- perf","description":"perf 性能剖析工具笔记：基于 Performance Counters 的热点查找，附火焰图分析与阿里承刚系列教程链接","image":"https://ilongda.com/img/my.jpg","wordCount":260,"datePublished":"2015-10-13T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.965Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/tools/monitor_tools/perf/"},"url":"https://ilongda.com/2015/docs/tools/monitor_tools/perf/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具 -- perf","item":"https://ilongda.com/2015/docs/tools/monitor_tools/perf/"}]}</script><h1 id="perf简介"><a href="#perf简介" class="headerlink" title="perf简介"></a>perf简介</h1><p>perf 功能非常强大， 号称瑞士军刀， 对性能很多问题， 都可以尝试用perf 来帮忙解决， 它能够进行函数级与指令级的热点查找。它由一个叫“Performance counters“的内核子系统实现，基于事件采样原理，以性能事件为基础，支持针对处理器相关性能指标与操作系统相关性能指标的性能剖析，可用于性能瓶颈的查找与热点代码的定位。</p><p>强烈推荐先学习 阿里承刚 关于perf 的详细介绍</p><ul><li><p><span class="exturl" data-url="aHR0cDovL2ZpbGVzLmNuYmxvZ3MuY29tL2ppYXl5L0xpbnV4JUU3JTlBJTg0JUU3JUIzJUJCJUU3JUJCJTlGJUU3JUJBJUE3JUU2JTgwJUE3JUU4JTgzJUJEJUU1JTg5JTk2JUU2JTlFJTkwJUU1JUI3JUE1JUU1JTg1JUI3LXBlcmYtMS5wZGY=">Linux的系统级性能剖析工具-perf-1.pdf<i class="fa fa-external-link-alt"></i></span></p></li><li><p><span class="exturl" data-url="aHR0cDovL2ZpbGVzLmNuYmxvZ3MuY29tL2ppYXl5L0xpbnV4JUU3JTlBJTg0JUU3JUIzJUJCJUU3JUJCJTlGJUU3JUJBJUE3JUU2JTgwJUE3JUU4JTgzJUJEJUU1JTg5JTk2JUU2JTlFJTkwJUU1JUI3JUE1JUU1JTg1JUI3LXBlcmYtMi5wZGY=">Linux的系统级性能剖析工具-perf-2.pdf<i class="fa fa-external-link-alt"></i></span></p></li><li><p><span class="exturl" data-url="aHR0cDovL2ZpbGVzLmNuYmxvZ3MuY29tL2ppYXl5L0xpbnV4JUU3JTlBJTg0JUU3JUIzJUJCJUU3JUJCJTlGJUU3JUJBJUE3JUU2JTgwJUE3JUU4JTgzJUJEJUU1JTg5JTk2JUU2JTlFJTkwJUU1JUI3JUE1JUU1JTg1JUI3LXBlcmYtMy5wZGY=">Linux的系统级性能剖析工具-perf-3.pdf<i class="fa fa-external-link-alt"></i></span></p></li><li><p><span class="exturl" data-url="aHR0cDovL2ZpbGVzLmNuYmxvZ3MuY29tL2ppYXl5L1BlcmYlRTUlOUMlQThMaW51eCVFNiU4MCVBNyVFOCU4MyVCRCVFOCVBRiU4NCVFNCVCQyVCMCVFNCVCOCVBRCVFNyU5QSU4NCVFNSVCQSU5NCVFNyU5NCVBOF92My5wZGY=">Perf在Linux性能评估中的应用_v3.pdf<i class="fa fa-external-link-alt"></i></span></p></li></ul><p>另外也有几篇介绍的不错。 </p><ul><li><p><span class="exturl" data-url="aHR0cHM6Ly96aHVhbmxhbi56aGlodS5jb20vcC8xNDE2OTQwNjA/ZnJvbV92b3RlcnNfcGFnZT10cnVl">https://zhuanlan.zhihu.com/p/141694060?from_voters_page=true<i class="fa fa-external-link-alt"></i></span></p></li><li><p><span class="exturl" data-url="aHR0cHM6Ly9waW5nY2FwLmNvbS9ibG9nLWNuL2ZsYW1lLWdyYXBoLw==">https://pingcap.com/blog-cn/flame-graph/<i class="fa fa-external-link-alt"></i></span></p></li><li><p><span class="exturl" data-url="aHR0cDovL25lb3JlbWluZC5jb20vMjAxNy8wOS8lZTQlYmQlYmYlZTclOTQlYTglZTclODElYWIlZTclODQlYjAlZTUlOWIlYmUlZTUlODElOWElZTYlODAlYTclZTglODMlYmQlZTUlODglODYlZTYlOWUlOTAv">http://neoremind.com/2017/09/%e4%bd%bf%e7%94%a8%e7%81%ab%e7%84%b0%e5%9b%be%e5%81%9a%e6%80%a7%e8%83%bd%e5%88%86%e6%9e%90/<i class="fa fa-external-link-alt"></i></span></p></li></ul>]]>
    </content>
    <id>https://ilongda.com/2015/docs/tools/monitor_tools/perf/</id>
    <link href="https://ilongda.com/2015/docs/tools/monitor_tools/perf/"/>
    <published>2015-10-13T11:42:57.000Z</published>
    <summary>perf 性能剖析工具笔记：基于 Performance Counters 的热点查找，附火焰图分析与阿里承刚系列教程链接</summary>
    <title>监控工具 -- perf</title>
    <updated>2026-06-09T08:46:25.965Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具 - 网络监控","description":"Linux 网络监控工具笔记：nethogs、netstat、iperf、ntop、tcprstat 等带宽延迟分析与端口排查命令汇总","image":"https://ilongda.com/assets/sar_network.png","wordCount":838,"datePublished":"2015-10-12T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.965Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/tools/monitor_tools/network/"},"url":"https://ilongda.com/2015/docs/tools/monitor_tools/network/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具 - 网络监控","item":"https://ilongda.com/2015/docs/tools/monitor_tools/network/"}]}</script><h1 id="网络调试工具"><a href="#网络调试工具" class="headerlink" title="网络调试工具"></a>网络调试工具</h1><ul><li>nethogs 按照进程对网络带宽排序</li><li>sar – 监控网卡速度</li><li>iperf 测试网络最大宽带</li><li>ntop 功能比较强大的一个网络监控工具</li><li>tcprstat  检测延迟的一个工具</li><li>netstat 端口察看</li></ul><h2 id="sar-监控网卡速度"><a href="#sar-监控网卡速度" class="headerlink" title="sar 监控网卡速度"></a>sar 监控网卡速度</h2><p>详情可以参考sar 一节</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sar -n DEV  30 5</span><br></pre></td></tr></table></figure><p><img data-src="/assets/sar_network.png" alt="sar network"></p><h2 id="nethogs"><a href="#nethogs" class="headerlink" title="nethogs"></a>nethogs</h2><p>NetHogs是一个小型的net top工具，不像大多数工具那样拖慢每个协议或者是每个子网的速度而是按照进程进行带宽分组。NetHogs不需要依赖载入某个特殊的内核模块。如果发生了网络阻塞你可以启动NetHogs立即看到哪个PID造成的这种状况。这样就很容易找出哪个程序跑飞了然后突然占用你的带宽。</p><p>详情查看 : <span class="exturl" data-url="aHR0cHM6Ly9tYW4ubGludXhkZS5uZXQvbmV0aG9ncw==">NETHOGS<i class="fa fa-external-link-alt"></i></span></p><h2 id="netstat"><a href="#netstat" class="headerlink" title="netstat"></a>netstat</h2><p>netstat 是一个非常强大的工具， 可以显示各种网络相关信息，如网络连接，端口，路由表，接口状态 (Interface Statistics)，masquerade 连接，多播成员 (Multicast Memberships) 等等。</p><p>详情查看 :<span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vZ2dqdWNoZW5nL2FyY2hpdmUvMjAxMi8wMS8wOC8yMzE2NjYxLmh0bWw=">netstat<i class="fa fa-external-link-alt"></i></span></p><h2 id="iperf"><a href="#iperf" class="headerlink" title="iperf"></a>iperf</h2><p>Iperf是美国伊利诺斯大学（University of Illinois）开发的一种开源的网络性能测试工具。可以用来测试网络节点间（也包括回环）TCP或UDP连接的性能，包括带宽、抖动以及丢包率，其中抖动和丢包率适应于UDP测试，而带宽测试适应于TCP和UDP。</p><p>Iperf是一款基于TCP&#x2F;IP和UDP&#x2F;IP的网络性能测试工具，可以用来测量网络带宽和网络质量，提供网络延迟抖动、数据包丢失率、最大传输单元等统计信息。网络管理员可以根据这些信息了解并判断网络性能问题，从而定位网络瓶颈，解决网络故障。</p><p>Iperf 是一款基于命令行模式的网络性能测试工具，是跨平台的，提供横跨Windows、Linux、Mac的全平台支持。iperf 全程使用内存作为发送&#x2F;接收缓冲区，不受磁盘性能的影响，对于机器配置要求很低。不过由于是命令行工具， iperf 不支持输出测试图形。</p><p>Iperf可以测试TCP和UDP带宽质量，具有多种参数和UDP特性，可以用来测试一些网络设备如路由器，防火墙，交换机等的性能。</p><p>详情查看 : <span class="exturl" data-url="aHR0cHM6Ly93d3cubXNjdG8uY29tL29wLzUwNzQwMi5odG1s">iperf<i class="fa fa-external-link-alt"></i></span></p><h2 id="ntop"><a href="#ntop" class="headerlink" title="ntop"></a>ntop</h2><p>Ntop是一种监控网络流量的工具，用NTOP显示网络的使用情况比其他一些网管软件更加直观、详细。NTOP甚至可以列出集群中每个节点计算机的网络带宽利用率。</p><ol><li>自动从网络中识别有用的信息；</li><li>将截获的数据包转换成易于识别的格式；</li><li>对网络环境中通信失败的情况进行分析；</li><li>探测网络环境中的通信瓶颈,记录网络通信的时间和过程。</li></ol><p>详情查看 : <span class="exturl" data-url="aHR0cHM6Ly93d3cuamlhbnNodS5jb20vcC9lZjIyNTJkYmMxNzY=">ntop<i class="fa fa-external-link-alt"></i></span></p><h2 id="tcprstat"><a href="#tcprstat" class="headerlink" title="tcprstat"></a>tcprstat</h2><p>tcprstat 是一个基于 pcap 提取 TCP 应答时间信息的工具，通过监控网络传输来统计分析请求的响应时间。<br>tcprstat是安装在server端，统计分析本地网卡地址请求的响应时间，可以用于临时分析，也可定时任务做信息收集</p><p>推荐:</p><p><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ExMzU1NDUxNTgxMi9hcnRpY2xlL2RldGFpbHMvODk2MDcwNDE/dXRtX21lZGl1bT1kaXN0cmlidXRlLnBjX3JlbGV2YW50Lm5vbmUtdGFzay1ibG9nLUJsb2dDb21tZW5kRnJvbU1hY2hpbmVMZWFyblBhaTItMS5jb250cm9sJmRlcHRoXzEtdXRtX3NvdXJjZT1kaXN0cmlidXRlLnBjX3JlbGV2YW50Lm5vbmUtdGFzay1ibG9nLUJsb2dDb21tZW5kRnJvbU1hY2hpbmVMZWFyblBhaTItMS5jb250cm9s">安装与使用<i class="fa fa-external-link-alt"></i></span></p><p><span class="exturl" data-url="aHR0cHM6Ly9kZXZlbG9wZXIuYWxpeXVuLmNvbS9hcnRpY2xlLzQyMDA1">Aliyun install<i class="fa fa-external-link-alt"></i></span></p>]]>
    </content>
    <id>https://ilongda.com/2015/docs/tools/monitor_tools/network/</id>
    <link href="https://ilongda.com/2015/docs/tools/monitor_tools/network/"/>
    <published>2015-10-12T11:42:57.000Z</published>
    <summary>Linux 网络监控工具笔记：nethogs、netstat、iperf、ntop、tcprstat 等带宽延迟分析与端口排查命令汇总</summary>
    <title>监控工具 - 网络监控</title>
    <updated>2026-06-09T08:46:25.965Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具 - 内存监控","description":"Linux 内存监控工具对比：vmstat、valgrind、jemalloc、perf mem、mtrace 等工具的适用场景与参考链接汇总","image":"https://ilongda.com/img/my.jpg","wordCount":416,"datePublished":"2015-10-11T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.965Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/tools/monitor_tools/memory/"},"url":"https://ilongda.com/2015/docs/tools/monitor_tools/memory/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具 - 内存监控","item":"https://ilongda.com/2015/docs/tools/monitor_tools/memory/"}]}</script><h1 id="内存监控"><a href="#内存监控" class="headerlink" title="内存监控"></a>内存监控</h1><p>based on the document <span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dhdGllbWUvYXJ0aWNsZS9kZXRhaWxzLzUxOTU5NjU0">https://blog.csdn.net/gatieme/article/details/51959654<i class="fa fa-external-link-alt"></i></span></p><table><thead><tr><th align="left"><strong>工具</strong></th><th align="left"><strong>描述</strong></th><th align="left"><strong>推荐</strong></th></tr></thead><tbody><tr><td align="left">vmstat</td><td align="left">vmstat是Virtual Meomory Statistics（虚拟内存统计）的缩写，可对操作系统的虚拟内存、进程、CPU活动进行监控。是对系统的整体情况进行统计，不足之处是无法对某个进程进行深入分析。</td><td align="left"><span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vZnRsMTAxMi9wL3Ztc3RhdC5odG1s">vmstat<i class="fa fa-external-link-alt"></i></span></td></tr><tr><td align="left">valgrind</td><td align="left">一个强大开源的程序检测工具, 可以边缘期间也可以运行期间</td><td align="left"><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FuZHlsYXVyZW4vYXJ0aWNsZS9kZXRhaWxzLzkzMTg5NzQw">推荐1<i class="fa fa-external-link-alt"></i></span>  <span class="exturl" data-url="aHR0cHM6Ly93d3cuY25ibG9ncy5jb20vY29iYmxpdS9wLzQ0MjM3NzUuaHRtbA==">推荐2<i class="fa fa-external-link-alt"></i></span></td></tr><tr><td align="left">jemalloc</td><td align="left">一个非常不错的内存库， 比系统自带的ptmalloc 要优秀很多， 并且自带内存监控功能</td><td align="left"><a href="/knowledge/mysql/source_code_reading/memory/allocator.html">参考本blog 之前jemlloc</a></td></tr><tr><td align="left">perf</td><td align="left">perf 是一个非常强大的监控cpu 指令的工具， 同样也提供内存监控的功能</td><td align="left"><span class="exturl" data-url="aHR0cHM6Ly9waW5nY2FwLmNvbS9ibG9nLWNuL2ZsYW1lLWdyYXBoLw==">perf mem<i class="fa fa-external-link-alt"></i></span></td></tr><tr><td align="left">mtrace</td><td align="left">GNU扩展，用来跟踪malloc，mtrace为内存分配函数(malloc,rellaoc,memalign,free)安装hook函数, 不过略感功能偏弱</td><td align="left"><span class="exturl" data-url="aHR0cHM6Ly96aHVhbmxhbi56aGlodS5jb20vcC84MzU0Nzc2OA==">mtrace<i class="fa fa-external-link-alt"></i></span></td></tr><tr><td align="left">dmalloc</td><td align="left">用于检查C&#x2F;C++内存泄漏的工具，即是检查是否存在程序运行结束还没有释放的内存，以一个运行库发布, 不如jemalloc</td><td align="left"><span class="exturl" data-url="aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3UwMTIyODg4MTUvYXJ0aWNsZS9kZXRhaWxzLzU5NjIyOTk2">dmalloc<i class="fa fa-external-link-alt"></i></span></td></tr><tr><td align="left">memwatch</td><td align="left">和dmalloc一样，它能检测未释放的内存、同一段内存被释放多次、位址存取错误及不当使用未分配之内存区域</td><td align="left"></td></tr><tr><td align="left">mpatrol</td><td align="left">一个跨平台的 C++ 内存泄漏检测器</td><td align="left"><span class="exturl" data-url="aHR0cDovL21wYXRyb2wuc291cmNlZm9yZ2UubmV0Lw==">mpatrol<i class="fa fa-external-link-alt"></i></span></td></tr><tr><td align="left">dbgmem</td><td align="left">也是一个动态库发布的形式，优点类似dmalloc，但是相比之下，可能特点少了一些</td><td align="left">不推荐了</td></tr><tr><td align="left">Electric Fence</td><td align="left">不仅仅能够跟踪malloc()和free(),同时能够检查读访问以及写入，能够准确指出导致错误的指令</td><td align="left">不推荐了</td></tr></tbody></table>]]>
    </content>
    <id>https://ilongda.com/2015/docs/tools/monitor_tools/memory/</id>
    <link href="https://ilongda.com/2015/docs/tools/monitor_tools/memory/"/>
    <published>2015-10-11T11:42:57.000Z</published>
    <summary>Linux 内存监控工具对比：vmstat、valgrind、jemalloc、perf mem、mtrace 等工具的适用场景与参考链接汇总</summary>
    <title>监控工具 - 内存监控</title>
    <updated>2026-06-09T08:46:25.965Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具 - java gc","description":"Java GC 调优实战笔记：Full GC 频繁报警案例分析，结合 jstat/jstack/jmap histo 定位 CMS concurrent mode failure","image":"https://ilongda.com/assets/gc.jpg","wordCount":1925,"datePublished":"2015-10-10T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.964Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/tools/monitor_tools/java_gc/"},"url":"https://ilongda.com/2015/docs/tools/monitor_tools/java_gc/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具 - java gc","item":"https://ilongda.com/2015/docs/tools/monitor_tools/java_gc/"}]}</script><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>2013年的一篇文章，当时介绍的蛮不错的， 保留下来了。</p><h1 id="现象"><a href="#现象" class="headerlink" title="现象"></a>现象</h1><p>系统报警full gc次数过多，每2分钟达到了5～6次，这是不正常的现象<br>在full gc报警时的gc.log如下：<br><img data-src="/assets/gc.jpg" alt="GC.LOG"></p><p>在full gc报警时的jstat如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo -u admin -H /opt/taobao/java/bin/jstat -gcutil `pgrep java` 2000 100</span><br></pre></td></tr></table></figure><p><img data-src="/assets/gc_jstat.jpg" alt="gc jstat"></p><p>此时的cpu如下（基本都是在做gc）：<br><img data-src="/assets/gc_cpu.jpg" alt="gc cpu"></p><p>将应用重启后，问题解决. 但是当后台执行低价航线更新时，过大概十几个小时后，又出现上述情况！</p><h1 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h1><p>当频繁full gc时，jstack打印出堆栈信息如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo -u admin -H /opt/taobao/java/bin/jstack `pgrep java` &gt; #your file path#</span><br></pre></td></tr></table></figure><p><img data-src="/assets/gc_jstat_full.jpg" alt="gc jstat"></p><p>另外在应用频繁full gc时和应用正常时，也执行了如下2种命令：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo -u admin -H /opt/taobao/java/bin/jmap -histo `pgrep` &gt; #your file path#</span><br><span class="line">sudo -u admin -H /opt/taobao/java/bin/jmap -histo:live `pgrep` &gt; #your file path#（live会产生full gc）</span><br></pre></td></tr></table></figure><p>目的是确认以下2种信息：</p><ol><li>是否存在某些引用的不正常，造成对象始终可达而无法回收（Java中的内存泄漏）</li><li>是否真是由于在频繁full gc时同时又有大量请求进入分配内存从而处理不过来，造成concurrent mode failure？</li></ol><p>下图是在应用正常情况下，jmap不加live，产生的histo信息：<br><img data-src="/assets/gc_jmap.jpg" alt="gc jmap"></p><p>下图是在应用正常情况下，jmap加live，产生的histo信息：<br><img data-src="/assets/gc_jmap_live.jpg" alt="gc jmap live"><br>下图是在应用频繁full gc情况下，jmap不加live和加live，产生的histo信息：<br><img data-src="/assets/gc_jmap_live2.jpg" alt="gc jmap live"></p><p>从上述几个图中可以看到：</p><ol><li>在应用正常情况下，图中标红的对象是被回收的，因此不是内存泄漏问题</li><li>在应用频繁full gc时，标红的对象即使加live也是未被回收的，因上就是在频繁full gc时，同时又有大量请求进入分配内存从而处理不过来的问题</li></ol><p>先从解决问题的角度，看怎样造成频繁的full gc？</p><h1 id="CMS-GC"><a href="#CMS-GC" class="headerlink" title="CMS GC"></a>CMS GC</h1><p>从分析CMS GC开始, 先给个CMS GC的概况</p><h2 id="young-gc"><a href="#young-gc" class="headerlink" title="young gc"></a>young gc</h2><p>可以看到，当eden满时，young gc使用的是ParNew收集器</p><p>ParNew: 2230361K-&gt;129028K(2403008K), 0.2363650 secs解释：</p><ol><li>2230361K-&gt;129028K，指回收前后eden+s1(或s2)大小</li><li>2403008K，指可用的young代的大小，即eden+s1（或s2）</li><li>0.2363650 secs，指消耗时间</li></ol><p>2324774K-&gt;223451K(3975872K), 0.2366810 sec解释：</p><ol><li>2335109K-&gt;140198K，指整个堆大小的变化 （heap&#x3D;(young+old)+perm；young&#x3D;eden+s1+s2；s1&#x3D;s2&#x3D;young&#x2F;(survivor ratio+2)）</li><li>0.2366810 sec，指消耗时间 [Times: user&#x3D;0.60 sys&#x3D;0.02, real&#x3D;0.24 secs]解释：指用户时间，系统时间，真实时间<br><img data-src="/assets/gc_young.jpg" alt="gc young gc"></li></ol><h2 id="cms-gc"><a href="#cms-gc" class="headerlink" title="cms gc"></a>cms gc</h2><p>当使用CMS收集器时，当开始进行收集时，old代的收集过程如下所示：</p><ol><li>首先jvm根据-XX:CMSInitiatingOccupancyFraction，-XX:+UseCMSInitiatingOccupancyOnly 来决定什么时间开始垃圾收集</li><li>如果设置了-XX:+UseCMSInitiatingOccupancyOnly，那么只有当old代占用确实达到-XX:CMSInitiatingOccupancyFraction参数所设定的比例时才会触发cms gc</li><li>如果没有设置-XX:+UseCMSInitiatingOccupancyOnly，那么系统会根据统计数据自行决定什么时候触发cms gc；因此有时会遇到设置了80%比例才cms gc，但是50%时就已经触发了，就是因为这个参数没有设置的原因</li><li>当cms gc开始时，首先的阶段是CMS-initial-mark，此阶段是初始标记阶段，是stop the world阶段，因此此阶段标记的对象只是从root集最直接可达的对象CMS-initial-mark：961330K（1572864K），指标记时，old代的已用空间和总空间</li><li>下一个阶段是CMS-concurrent-mark，此阶段是和应用线程并发执行的，所谓并发收集器指的就是这个，主要作用是标记可达的对象. 此阶段会打印2条日志：CMS-concurrent-mark-start，CMS-concurrent-mark</li><li>下一个阶段是CMS-concurrent-preclean，此阶段主要是进行一些预清理，因为标记和应用线程是并发执行的，因此会有些对象的状态在标记后会改变，此阶段正是解决这个问题. 因为之后的Rescan阶段也会stop the world，为了使暂停的时间尽可能的小，也需要preclean阶段先做一部分工作以节省时间. 此阶段会打印2条日志：CMS-concurrent-preclean-start，CMS-concurrent-preclean</li><li>下一阶段是CMS-concurrent-abortable-preclean阶段，加入此阶段的目的是使cms gc更加可控一些，作用也是执行一些预清理，以减少Rescan阶段造成应用暂停的时间. 此阶段涉及几个参数：</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">-XX:CMSMaxAbortablePrecleanTime：当abortable-preclean阶段执行达到这个时间时才会结束</span><br><span class="line">-XX:CMSScheduleRemarkEdenSizeThreshold（默认2m）：控制abortable-preclean阶段什么时候开始执行，即当eden使用达到此值时，才会开始abortable-preclean阶段</span><br><span class="line">-XX:CMSScheduleRemarkEdenPenetratio（默认50%）：控制abortable-preclean阶段什么时候结束执行</span><br></pre></td></tr></table></figure><p>此阶段会打印一些日志如下：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">CMS-concurrent-abortable-preclean-start，CMS-concurrent-abortable-preclean，</span><br><span class="line">CMS：abort preclean due to time XXX</span><br></pre></td></tr></table></figure><ol start="8"><li>再下一个阶段是第二个stop the world阶段了，即Rescan阶段，此阶段暂停应用线程，对对象进行重新扫描并标记</li></ol><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">YG occupancy：964861K（2403008K），指执行时young代的情况</span><br><span class="line">CMS remark：961330K（1572864K），指执行时old代的情况</span><br></pre></td></tr></table></figure><p>此外，还打印出了弱引用处理、类卸载等过程的耗时<br>9. 再下一个阶段是CMS-concurrent-sweep，进行并发的垃圾清理<br>10. 最后是CMS-concurrent-reset，为下一次cms gc重置相关数据结构<br><img data-src="/assets/gc_cms1.jpg" alt="cms gc"></p><h2 id="full-gc："><a href="#full-gc：" class="headerlink" title="full gc："></a>full gc：</h2><p>有2种情况会触发full gc，在full gc时，整个应用会暂停</p><ol><li>concurrent-mode-failure：当cms gc正进行时，此时有新的对象要进行old代，但是old代空间不足造成的</li><li>promotion-failed：当进行young gc时，有部分young代对象仍然可用，但是S1或S2放不下，因此需要放到old代，但此时old代空间无法容纳此<br><img data-src="/assets/gc_full_gc.jpg" alt="full gc"></li></ol><h2 id="频繁full-gc的原因"><a href="#频繁full-gc的原因" class="headerlink" title="频繁full gc的原因"></a>频繁full gc的原因</h2><p>从日志中可以看出有大量的concurrent-mode-failure，因此正是当cms gc进行时，有新的对象要进行old代，但是old代空间不足造成的full gc</p><p>进程的jvm参数如下所示：<br><img data-src="/assets/gc_jvm.jpg" alt="gc jvm parameter"></p><p>影响cms gc时长及触发的参数是以下2个：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">-XX:CMSMaxAbortablePrecleanTime=5000</span><br><span class="line">-XX:CMSInitiatingOccupancyFraction=80</span><br></pre></td></tr></table></figure><p>解决也是针对这两个参数来的</p><p>根本的原因是每次请求消耗的内存量过大</p><h2 id="解决"><a href="#解决" class="headerlink" title="解决"></a>解决</h2><ol><li>针对cms gc的触发阶段，调整-XX:CMSInitiatingOccupancyFraction&#x3D;50，提早触发cms gc，就可以缓解当old代达到80%，cms gc处理不完，从而造成concurrent mode failure引发full gc</li><li>修改-XX:CMSMaxAbortablePrecleanTime&#x3D;500，缩小CMS-concurrent-abortable-preclean阶段的时间</li><li>考虑到cms gc时不会进行compact，因此加入-XX:+UseCMSCompactAtFullCollection（cms gc后会进行内存的compact）和-XX:CMSFullGCsBeforeCompaction&#x3D;4（在full gc4次后会进行compact）参数</li></ol><p>但是运行了一段时间后，只不过时间更长了，又会出现频繁full gc</p><p>计算了一下heap各个代的大小（可以用jmap -heap查看）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">total heap=young+old=4096m</span><br><span class="line">perm:256m</span><br><span class="line">young=s1+s2+eden=2560m</span><br><span class="line">young avail=eden+s1=2133.375+213.3125=2346.6875m</span><br><span class="line">s1=2560/(10+1+1)=213.3125m</span><br><span class="line">s2=s1</span><br><span class="line">eden=2133.375m</span><br><span class="line">old=1536m</span><br></pre></td></tr></table></figure><p>可以看到eden大于old，在极端情况下（young区的所有对象全都要进入到old时，就会触发full gc），因此在应用频繁full gc时，很有可能old代是不够用的，因此想到将old代加大，young代减小</p><p>改成以下： -Xmn1920m<br>新的各代大小：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">total heap=young+old=4096m</span><br><span class="line">perm:256m</span><br><span class="line">young=s1+s2+eden=1920m</span><br><span class="line">young avail=eden+s1=2133.375+213.3125=1760m</span><br><span class="line">s1=1760/(10+1+1)=160m</span><br><span class="line">s2=s1</span><br><span class="line">eden=1600m</span><br><span class="line">old=2176m</span><br></pre></td></tr></table></figure><p>此时的eden小于old，可以缓解一些问题</p><p>改完之后，运行了2天，问题解决，未频繁报full gc</p>]]>
    </content>
    <id>https://ilongda.com/2015/docs/tools/monitor_tools/java_gc/</id>
    <link href="https://ilongda.com/2015/docs/tools/monitor_tools/java_gc/"/>
    <published>2015-10-10T11:42:57.000Z</published>
    <summary>Java GC 调优实战笔记：Full GC 频繁报警案例分析，结合 jstat/jstack/jmap histo 定位 CMS concurrent mode failure</summary>
    <title>监控工具 - java gc</title>
    <updated>2026-06-09T08:46:25.964Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/categories/%E5%B7%A5%E5%85%B7/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <category term="工具" scheme="https://ilongda.com/tags/%E5%B7%A5%E5%85%B7/"/>
    <category term="监控工具" scheme="https://ilongda.com/tags/%E7%9B%91%E6%8E%A7%E5%B7%A5%E5%85%B7/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"监控工具 - java 内存调优","description":"Java 内存监控与调优笔记：推荐 jmap/mat/gperftools 等工具，介绍 heap dump 分析与 tcmalloc HEAPPROFILE 用法","image":"https://ilongda.com/assets/jvm_gperf.png","wordCount":399,"datePublished":"2015-10-10T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.965Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/tools/monitor_tools/java_mem/"},"url":"https://ilongda.com/2015/docs/tools/monitor_tools/java_mem/","inLanguage":"zh-CN","keywords":["工具","监控工具"],"articleSection":["工具","监控工具"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"工具","item":"https://ilongda.com/categories/工具/"},{"@type":"ListItem","position":3,"name":"监控工具 - java 内存调优","item":"https://ilongda.com/2015/docs/tools/monitor_tools/java_mem/"}]}</script><h1 id="概述"><a href="#概述" class="headerlink" title="概述"></a>概述</h1><p>java 内存调优 或内存监控， 推荐先看本系列的另外一篇介绍gc的文章， 在分析gc的过程中，串了很多工具来介绍如何做内存分析。 </p><p>另外java 有一些调试工具不错， 可以推荐一下 jstack&#x2F;jconsole </p><h1 id="工具"><a href="#工具" class="headerlink" title="工具"></a>工具</h1><ul><li>mat   – 离线静态分析工具， 非常好用， 缺点是需要离线dump出来</li><li>jmap  – 常用内存dump 工具</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">jmap -histo pid</span><br><span class="line">jmap -dump:format=b,file=heap.bin &lt;pid&gt;</span><br></pre></td></tr></table></figure><ul><li>gpreftools  –  类似perf 来查看内存</li></ul><h1 id="gpreftools"><a href="#gpreftools" class="headerlink" title="gpreftools"></a>gpreftools</h1><p>为了确认问题，在机器上安装了gpreftools工具进行调试</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>安装过程比较麻烦，先下载最新的preftools包通过oss上传到机器上，重新编译make install；</p><p>需要安装机器上对应版本的gcc和g++:<br>sudo yum install gcc-4.1.2 gcc-c++-4.1.2 -b test；</p><p>函数调用栈输出库libunwind:<br>sudo yum install libunwind.x86_64 -b current；</p><p>在启停脚本参数配置setenv.sh中根据安装的路径加入export LD_PRELOAD&#x3D;&#x2F;home&#x2F;xxx&#x2F;perftools&#x2F;lib&#x2F;libtcmalloc.so以及export HEAPPROFILE&#x3D;&#x2F;tmp&#x2F;test，</p><p>HEAPPROFILE的路径最好写在&#x2F;tmp目录下，否则admin账号没权限写到个人目录里导致类似Failed dumping heap profile to &#x2F;home&#x2F;xxx&#x2F;test.0001.heap的报错</p><p>安装好后重启应用通过&#x2F;usr&#x2F;sbin&#x2F;lsof -n | grep tcmalloc来查看tcmalloc是否加载，且&#x2F;tmp下是否有heap文件。</p><p>一段时间后当再次出现内存泄露的问题时，使用pprof –text &#x2F;opt&#x2F;taobao&#x2F;java&#x2F;bin&#x2F;java test.xxxx.heap查看最新的heap文件。</p><p><img data-src="/assets/jvm_gperf.png" alt="jvm gperf"></p>]]>
    </content>
    <id>https://ilongda.com/2015/docs/tools/monitor_tools/java_mem/</id>
    <link href="https://ilongda.com/2015/docs/tools/monitor_tools/java_mem/"/>
    <published>2015-10-10T11:42:57.000Z</published>
    <summary>Java 内存监控与调优笔记：推荐 jmap/mat/gperftools 等工具，介绍 heap dump 分析与 tcmalloc HEAPPROFILE 用法</summary>
    <title>监控工具 - java 内存调优</title>
    <updated>2026-06-09T08:46:25.965Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="运维" scheme="https://ilongda.com/categories/%E8%BF%90%E7%BB%B4/"/>
    <category term="运维" scheme="https://ilongda.com/tags/%E8%BF%90%E7%BB%B4/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"zookeeper 扩容","description":"Zookeeper 同城多机房扩容实战：将单机房三节点扩展为 221 分布，逐步增机下线旧节点并控制 Leader 变更风险","image":"https://ilongda.com/img/my.jpg","wordCount":1767,"datePublished":"2015-09-10T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.946Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/zookeeper-enlarge/"},"url":"https://ilongda.com/2015/zookeeper-enlarge/","inLanguage":"zh-CN","keywords":["运维"],"articleSection":["运维"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"运维","item":"https://ilongda.com/categories/运维/"},{"@type":"ListItem","position":3,"name":"zookeeper 扩容","item":"https://ilongda.com/2015/zookeeper-enlarge/"}]}</script><h1 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h1><p>因为阿里经常进行机房断网演练， 如果一套zk 只能部署在一个机房时， 当发生断网时， 这套zk是无法为其他机房提供zk 服务， 因此需要将单机房zk 升级到多机房zk， 但因为zookeeper是强同步方式， 所有的请求会在内部进行同步， 如果机器之间延迟比较大时， zookeeper 问题会非常多， 因此，这套解决方案前提条件是同城多机房, 并且时延比较小。</p><p>这套解决方案也适合， zookeeper 升级扩容和zookeeper 机器替换</p><p>在多机房方案中， 常常是3机房， 这个时候，推荐221 的分布模式， 客户端多的机房多部署一台zookeeper</p><span id="more"></span><h1 id="解决的问题"><a href="#解决的问题" class="headerlink" title="解决的问题"></a>解决的问题</h1><p>目前zk 是3台机器， 现在需要再新增3台机器， 并把老的3台机器中一台给下线掉。</p><h1 id="步骤"><a href="#步骤" class="headerlink" title="步骤"></a>步骤</h1><ul><li>扩容新zookeeper</li><li>下线无用的zookeeper</li><li>更新所有zookeeper</li></ul><p>zk的变更其实需要非常小心， 如果发生错误，会导致整个zk停止服务， 会导致大片程序出错。<br>所以， 思路是，逐步增加机器， 尽量减少leader的变更</p><h2 id="扩容新zookeeper"><a href="#扩容新zookeeper" class="headerlink" title="扩容新zookeeper"></a>扩容新zookeeper</h2><p>扩容时， 新增一台机器的配置，仅仅比运行zk的配置多一台机器， 从而保证zk集群的leader不会发生任何变更</p><p>老的zookeeper 配置</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"># The number of milliseconds of each tick</span><br><span class="line">tickTime=2000</span><br><span class="line"># The number of ticks that the initial</span><br><span class="line"># synchronization phase can take</span><br><span class="line">initLimit=10</span><br><span class="line"># The number of ticks that can pass between</span><br><span class="line"># sending a request and getting an acknowledgement</span><br><span class="line">syncLimit=5</span><br><span class="line"># the directory where the snapshot is stored.</span><br><span class="line">maxClientCnxns=300</span><br><span class="line">dataDir=/dev/shm/zk/data</span><br><span class="line">dataLogDir=/dev/shm/zk/logs</span><br><span class="line"></span><br><span class="line"># the port at which the clients will connect</span><br><span class="line">clientPort=2181</span><br><span class="line"># The number of snapshots to retain in dataDir</span><br><span class="line">autopurge.snapRetainCount=5</span><br><span class="line"># Purge task interval in hours</span><br><span class="line"># Set to &quot;0&quot; to disable auto purge feature</span><br><span class="line">autopurge.purgeInterval=1</span><br><span class="line"></span><br><span class="line">#minSessionTimeout=10000</span><br><span class="line">minSessionTimeout=100</span><br><span class="line">maxSessionTimeout=100000</span><br><span class="line"></span><br><span class="line">server.1=zkA.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.2=zkB.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.3=zkC.jstorm.alibaba.com:2888:3888</span><br></pre></td></tr></table></figure><h3 id="扩容D"><a href="#扩容D" class="headerlink" title="扩容D"></a>扩容D</h3><p>新增D 的配置</p><p>扩容后新的配置是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">## 其他配置 同老的配置 ##</span><br><span class="line">server.1=zkA.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.2=zkB.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.3=zkC.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.4=zkD.jstorm.alibaba.com:2888:3888</span><br></pre></td></tr></table></figure><p>有几点需要注意：</p><ul><li>&#x2F;dev&#x2F;shm&#x2F;zk&#x2F;data 目录下，记得创建id</li><li>本例中， zookeeper 的目录是放到共享内存中， 因此需要一个cronjob 每分钟 把新的增量数据文件同步到本地硬盘中，并把本地硬盘中的过时文件删除</li><li>下线机器的id 会被保留，不会覆盖， 避免出现数据紊乱</li><li>增加D 后，需要确保A&#x2F;B&#x2F;C&#x2F;D zk提供服务，并且集群只有一个leader， 如果没有，则需要重做；有很多的方法， 下面提供一种方法</li></ul><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"> ~  echo srvr | nc zkD.jstorm.alibaba.com 2181</span><br><span class="line">Zookeeper version: 3.4.5-1392090, built on 09/30/2012 17:52 GMT</span><br><span class="line">Latency min/avg/max: 0/0/13432</span><br><span class="line">Received: ***</span><br><span class="line">Sent: ***</span><br><span class="line">Connections: ***</span><br><span class="line">Outstanding: 0</span><br><span class="line">Zxid: 0x***</span><br><span class="line">Mode: follower</span><br><span class="line">Node count: ***</span><br></pre></td></tr></table></figure><h3 id="扩容E"><a href="#扩容E" class="headerlink" title="扩容E"></a>扩容E</h3><p>新增E 的配置</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">## 其他配置 同老的配置 ##</span><br><span class="line">server.1=zkA.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.2=zkB.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.3=zkC.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.4=zkD.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.5=zkE.jstorm.alibaba.com:2888:3888</span><br></pre></td></tr></table></figure><p>有几点需要注意：</p><ul><li>&#x2F;dev&#x2F;shm&#x2F;zk&#x2F;data 目录下，记得创建id</li><li>本例中， zookeeper 的目录是放到共享内存中， 因此需要一个cronjob 每分钟 把新的增量数据文件同步到本地硬盘中，并把本地硬盘中的过时文件删除</li><li>下线机器的id 会被保留，不会覆盖， 避免出现数据紊乱</li><li>增加E 后，需要确保A&#x2F;B&#x2F;C&#x2F;D&#x2F;E zk提供服务，集群只有一个leader</li></ul><h3 id="扩容F"><a href="#扩容F" class="headerlink" title="扩容F"></a>扩容F</h3><p>新增F 的配置</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">## 其他配置 同老的配置 ##</span><br><span class="line">server.1=zkA.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.2=zkB.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.3=zkC.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.4=zkD.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.5=zkE.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.6=zkF.jstorm.alibaba.com:2888:3888</span><br></pre></td></tr></table></figure><p>有几点需要注意：</p><ul><li>&#x2F;dev&#x2F;shm&#x2F;zk&#x2F;data 目录下，记得创建id</li><li>本例中， zookeeper 的目录是放到共享内存中， 因此需要一个cronjob 每分钟 把新的增量数据文件同步到本地硬盘中，并把本地硬盘中的过时文件删除</li><li>下线机器的id 会被保留，不会覆盖， 避免出现数据紊乱</li><li>增加E 后，需要确保每台zk提供服务</li></ul><h3 id="更新D-配置"><a href="#更新D-配置" class="headerlink" title="更新D 配置"></a>更新D 配置</h3><p>更新d 的配置后， 重启D 的zookeeper， 并检查所有zk 节点是否正常</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">## 其他配置 同老的配置 ##</span><br><span class="line">server.1=zkA.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.2=zkB.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.3=zkC.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.4=zkD.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.5=zkE.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.6=zkF.jstorm.alibaba.com:2888:3888</span><br></pre></td></tr></table></figure><h3 id="更新E-配置"><a href="#更新E-配置" class="headerlink" title="更新E 配置"></a>更新E 配置</h3><p>更新e 的配置后， 重启e 的zookeeper， 并检查所有zk 节点是否正常</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">## 其他配置 同老的配置 ##</span><br><span class="line">server.1=zkA.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.2=zkB.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.3=zkC.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.4=zkD.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.5=zkE.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.6=zkF.jstorm.alibaba.com:2888:3888</span><br></pre></td></tr></table></figure><h2 id="更新老zookeeper的配置"><a href="#更新老zookeeper的配置" class="headerlink" title="更新老zookeeper的配置"></a>更新老zookeeper的配置</h2><p>步骤：</p><ul><li>检查老zk， 谁是leader， 谁是follower</li><li>更新zookeeper 配置</li><li>重新启动zookeeper</li><li>检查重启的zookeeper是否能提供服务</li></ul><p>注意几点：</p><ul><li>还是必须一台一台的操作， 一台完成后，才能进行下一台</li><li>配置文件 含有6台机器， 而不是5台机器</li><li>变更过程中， 所有zk 的角色不发生任何变更。</li></ul><p>在本例中， 假设leader是c, 则我们先变更a, 成功后，再变更b</p><p>这次的配置文件是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">## 其他配置 同老的配置 ##</span><br><span class="line">server.1=zkA.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.2=zkB.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.3=zkC.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.4=zkD.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.5=zkE.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.6=zkF.jstorm.alibaba.com:2888:3888</span><br></pre></td></tr></table></figure><h2 id="下掉老zk的leader"><a href="#下掉老zk的leader" class="headerlink" title="下掉老zk的leader"></a>下掉老zk的leader</h2><ul><li>杀死老zk的leader</li><li>检查所有zk是否提供服务， 确保所有zk能够提供服务</li></ul><h2 id="更新所有机器的配置"><a href="#更新所有机器的配置" class="headerlink" title="更新所有机器的配置"></a>更新所有机器的配置</h2><p>这次的配置文件是：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">## 其他配置 同老的配置 ##</span><br><span class="line">server.1=zkA.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.2=zkB.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.4=zkD.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.5=zkE.jstorm.alibaba.com:2888:3888</span><br><span class="line">server.6=zkF.jstorm.alibaba.com:2888:3888</span><br></pre></td></tr></table></figure><p>新的配置文件， 会剔除掉一台zk机器， 就是老的zk  leader</p><p>注意几点：</p><ul><li>还是必须一台一台的操作， 一台完成后，才能进行下一台</li><li>配置文件 含有5台机器，新的配置文件同样需要同步到下线的c机器上，以防止c后面被误启动</li><li>变更过程中， leader所在的机器必须是最后变更， 这样可以减少一次leader选举</li></ul>]]>
    </content>
    <id>https://ilongda.com/2015/zookeeper-enlarge/</id>
    <link href="https://ilongda.com/2015/zookeeper-enlarge/"/>
    <published>2015-09-10T11:42:57.000Z</published>
    <summary>Zookeeper 同城多机房扩容实战：将单机房三节点扩展为 221 分布，逐步增机下线旧节点并控制 Leader 变更风险</summary>
    <title>zookeeper 扩容</title>
    <updated>2026-06-09T08:46:25.946Z</updated>
  </entry>
  <entry>
    <author>
      <name>Longda Feng</name>
    </author>
    <category term="论文" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/"/>
    <category term="大数据" scheme="https://ilongda.com/categories/%E8%AE%BA%E6%96%87/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <category term="论文" scheme="https://ilongda.com/tags/%E8%AE%BA%E6%96%87/"/>
    <category term="大数据" scheme="https://ilongda.com/tags/%E5%A4%A7%E6%95%B0%E6%8D%AE/"/>
    <content>
      <![CDATA[<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"深度分析Twitter Heron","description":"Twitter Heron 流计算论文深度笔记：对比 Storm 的调试性、资源隔离与稳定性，及千台规模集群适用场景分析","image":"http://img3.tbcdn.cn/5476e8b07b923/TB1skzsMVXXXXX6aFXXXXXXXXXX","wordCount":4478,"datePublished":"2015-06-04T11:42:57.000Z","dateModified":"2026-06-09T08:46:25.962Z","isAccessibleForFree":true,"author":{"@type":"Person","name":"Longda Feng","url":"https://ilongda.com"},"publisher":{"@type":"Organization","name":"Longda's Interesting World","logo":{"@type":"ImageObject","url":"https://ilongda.com/img/my.jpg"}},"mainEntityOfPage":{"@type":"WebPage","@id":"https://ilongda.com/2015/docs/paper/heron/"},"url":"https://ilongda.com/2015/docs/paper/heron/","inLanguage":"zh-CN","keywords":["论文","大数据"],"articleSection":["论文","大数据"]}</script><script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ilongda.com/"},{"@type":"ListItem","position":2,"name":"论文","item":"https://ilongda.com/categories/论文/"},{"@type":"ListItem","position":3,"name":"深度分析Twitter Heron","item":"https://ilongda.com/2015/docs/paper/heron/"}]}</script><p>2015年6月1号， Twitter 对外宣讲了他们的Heron系统， 从ppt和论文中，看起来完爆storm。昨天，抽空把论文，仔细读了一遍， 把个人笔记和心得分享一下：</p><h1 id="最后总结："><a href="#最后总结：" class="headerlink" title="最后总结："></a>最后总结：</h1><p>Heron更适合超大规模的机器， 超过1000台机器以上的集群。 在稳定性上有更优异的表现， 在性能上，表现一般甚至稍弱一些，在资源使用上，可以和其他编程框架共享集群资源，但topology级别会更浪费一些资源。</p><p>而从应用的角度，应用更偏向于大应用，小应用的话，会多一点点资源浪费， 对于大应用，debug-ability的重要性逐渐提升。 另外对于task的设计， task会走向更重更复杂， 而JStorm的task是向更小更轻量去走。</p><p>未来JStorm可以把自动降级策略引入， 通过实现阿里妈妈的ASM， debug-ability应该远超过storm， 不会逊色于Heron， 甚至更强。</p><span id="more"></span><h1 id="现状："><a href="#现状：" class="headerlink" title="现状："></a>现状：</h1><p>所有的老的生产环境的topology已经运行在Heron上， 每天大概处理几十T的数据， billions of消息</p><h2 id="为什么要重新设计Heron："><a href="#为什么要重新设计Heron：" class="headerlink" title="为什么要重新设计Heron："></a>为什么要重新设计Heron：</h2><p>【题外话】这里完全引用作者吐槽的问题， 不少问题，其实JStorm已经解决</p><ul><li><p>debug-ability 很差， 出现问题，很难发现问题， 多个task运行在一个系统进程中， 很难定位问题。需要一个清晰的逻辑计算单元到物理计算单元的关系</p></li><li><p>需要一种更高级的资源池管理系统</p><ul><li><p>可以和其他编程框架共享资源， 说白了，就是类似yarn&#x2F;mesos， 而在Twitter就是Aurora</p></li><li><p>更简单的弹性扩容和缩容 集群</p></li><li><p>因为不同task，对资源需求是不一样的， 而storm会公平对待每个worker， 因此会存在worker浪费内存问题。当worker内存特别大时， 进行jstack或heap dump时，特别容易引起gc，导致被supervisor干掉</p></li><li><p>经常为了避免性能故障，常常进行超量资源分配， 原本100个core，分配了200个</p></li></ul></li><li><p>认为Storm设计不合理的地方</p><ul><li><p>一个executor 存在2个线程， 一个执行线程， 一个发送线程， 并且一个executor运行多个task， task的调度完全依赖来源的tuple， 很不方便确认哪个task出了问题。</p></li><li><p>因为多种task运行在一个worker中， 无法明确出每种task使用的资源， 也很难定位出问题的task，当出现性能问题或其他行为时， 常用就是重启topology， 重启后就好了，因为task进行了重新调度</p></li><li><p>日志打到同一个文件中，也很难查找问题，尤其是当某个task疯狂的打印日志时</p></li><li><p>当一个task挂掉了，直接会干掉worker，并强迫其他运行好的task被kill掉</p></li><li><p>最大的问题是，当topology某个部分出现问题时， 会影响到topology其他的环节</p></li><li><p>gc引起了大量的问题</p></li><li><p>一条消息至少经过4个线程， 4个队列， 这会触发线程切换和队列竞争问题</p></li><li><p>nimbus功能太多， 调度&#x2F;监控&#x2F;分发jar&#x2F;metric report， 经常会成为系统的bottleneck</p></li><li><p>storm的worker没有做到资源保留和资源隔离， 因此存在一个worker会影响到另外的worker。 而现有的isolation调度会带来资源浪费问题。 Storm on Yarn也没有完全解决这个问题。</p></li><li><p>zookeeper成为系统的瓶颈， 当集群规模增大时。 有些系统为了降低zk心态，新增了tracker，但tracker增加了系统运维难度。</p></li><li><p>nimbus是系统单点</p></li><li><p>缺乏反压机制</p><ul><li><p>当receiver忙不过来时， sender就直接扔弃掉tuple，</p></li><li><p>如果关掉acker机制， 那无法量化drop掉的tuple</p></li><li><p>因为上游worker执行的计算就被扔弃掉。</p></li><li><p>系统会变的难以预测(less predictable.)</p></li></ul></li><li><p>常常出现性能问题， 导致tuple fail， tuple replay， 执行变慢</p><ul><li><p>不良的replay， 任意一个tuple失败了，都会导致整个tuple tree fail， 不良的设计时（比如不重要的tuple失败），会导致tuple轻易被重发</p></li><li><p>当内存很大时，长时间的gc，导致处理延时，甚至被误杀</p></li><li><p>队列竞争</p></li></ul></li></ul></li></ul><h1 id="Heron设计"><a href="#Heron设计" class="headerlink" title="Heron设计"></a>Heron设计</h1><h2 id="设计原则："><a href="#设计原则：" class="headerlink" title="设计原则："></a>设计原则：</h2><ul><li><p>兼容老的storm api</p></li><li><p>实现2种策略， At most once&#x2F;At least once</p></li></ul><h2 id="架构："><a href="#架构：" class="headerlink" title="架构："></a>架构：</h2><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1skzsMVXXXXX6aFXXXXXXXXXX" alt="architecture"></p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1y_zyMVXXXXb.aXXXXXXXXXXX" alt="topology"></p><h2 id="调度器"><a href="#调度器" class="headerlink" title="调度器"></a>调度器</h2><p>Aurora是一个基于mesos的通用service scheduler， Hero基于Aurora 实现了一套Topology Scheduler， 并且这个调度器已经提供了一定的抽象，可以移植到yarn&#x2F;mesos&#x2F;ec2（我的理解应该稍加修改就可以运行在其他通用型调度器上）</p><p>第一个container 运行 Topology Manager（TM）， 其他的container 内部会运行一个Stream manager&#x2F;Metrics Manager 和多个Heron Instance。 这里一个container类似一个docker感念，表示一个资源集合，是Aurora的调度单元， 多个container可以运行在一台机器上， 分配多少container由Aurora根据现有资源情况进行分配， 另外一个container设置了cgroup。 从逻辑或角色上，这里的container相当于jstorm中的worker。</p><h2 id="Topology-Manager"><a href="#Topology-Manager" class="headerlink" title="Topology Manager"></a>Topology Manager</h2><ul><li><p>tm伴随整个topology生命周期， 提供topology状态的唯一contact （类似yarn的app master）</p></li><li><p>可以一主多备， 大家抢占zk 节点， 谁胜出，谁为master， 其他为standby</p></li></ul><h2 id="Stream-manager（SM）"><a href="#Stream-manager（SM）" class="headerlink" title="Stream manager（SM）"></a>Stream manager（SM）</h2><p>最大的改变就是源自Stream manager， Stream manager就相当于一个container的tuple的总线（hub）。 所有的Hero Instance（HI）都连接SM进行send&#x2F;receive</p><p>如果container内部一个HI 发送数据到另外一个HI，走的是本地快速通道。</p><h2 id="Backpressure-反压机制"><a href="#Backpressure-反压机制" class="headerlink" title="Backpressure 反压机制"></a>Backpressure 反压机制</h2><p>当下游处理速度变慢后，通过反压机制，可以通知上游进行减速， 避免数据因buffer被塞满而丢失，并因此带来资源浪费。</p><h3 id="TCP-反压："><a href="#TCP-反压：" class="headerlink" title="TCP 反压："></a>TCP 反压：</h3><p>当一个HI 处理慢了后，则该HI的接收buffer会被填满， 紧接着本地SM的sending buffer被填满， ? 然后会传播到其他的SM和上游HI。</p><p>这个机制很容易实现，但在实际使用中，存在很多问题。因为多个HI 共用SM， 不仅将上游的HI 降速了，也把下游的HI 降速。从而整个topology速度全部下架，并且长时间的降级。</p><h3 id="Spout-反压"><a href="#Spout-反压" class="headerlink" title="Spout 反压"></a>Spout 反压</h3><p>这个机制是结合TCP 反压机制， 一旦SM 发现一个或多个HI 速度变慢，立刻对本地spout进行降级， 停止从这些spout读取数据。并且受影响的SM 会发送一个特殊的start backpressure message 给其他的sm，要求他们对spout进行本地降级。一旦出问题的HI 恢复速度后，本地的SM 会发送 stop backpressure message 解除降级。</p><h3 id="Stage-by-Stage-反压"><a href="#Stage-by-Stage-反压" class="headerlink" title="Stage-by-Stage 反压"></a>Stage-by-Stage 反压</h3><p>这个类似spout反压，但是一级一级向上反压。</p><h3 id="结果"><a href="#结果" class="headerlink" title="结果"></a>结果</h3><p>Heron最后采用的是spout反压， 因为实现比较简单，而且降级响应非常迅速。 并且可以很快定位到那个HI 处理速度慢了。 每个socket channel都绑定了一个buffer， 当buffer 的 queue size超过警戒水位时，触发反压，减少时，接触反压。</p><p>这种机制，不会丢弃tuple，除了机器宕机。</p><p>topology可以设置打开或关闭。</p><h2 id="Heron-Instance"><a href="#Heron-Instance" class="headerlink" title="Heron Instance"></a>Heron Instance</h2><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1g7vOMVXXXXX1XVXXXXXXXXXX" alt="topology"></p><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1I8P3MVXXXXXPXXXXXXXXXXXX" alt="hi"></p><ul><li><p>一个task 一个进程，</p></li><li><p>所有的进程之间通信都是使用protocol buffer</p></li><li><p>一个gateway线程， 一个执行线程。 gateway线程负责和外围通信， sm&#x2F;mm。 执行线程和现有storm的执行线程非常类似。执行线程会收集所有的metrics，然后发送给gateway线程。</p></li><li><p>这个data-in&#x2F;data-out队列会限定大小， 当data-in 队列满了的时候， gateway线程停止从local SM 读取数据。同理如果data-out队列满，gateway会认为local SM不想接受更多的数据。 执行线程就不再emit或执行更多的tuple。</p></li><li><p>data-in&#x2F;data-out队列大小不是固定， 如果是固定时， 当网络颠簸时，会导致内存中大量数据堆积无法发送出去，并触发GC, 并导致进一步的降级。因此是动态调整， 定期调整队列大小。 如果队列的capacity超过阀值时， 对其进行减半。这个操作持续进行指导队列的capacity维持在一个稳定的水位或0。这种方式有利避免GC的影响。 当队列的capcity小于某个阀值时， 会缓慢增长到配置大小或最大capacity值。</p></li></ul><h2 id="Metrics-manager（mm）"><a href="#Metrics-manager（mm）" class="headerlink" title="Metrics manager（mm）"></a>Metrics manager（mm）</h2><p>收集所有的metrics，包括系统的和用户的metrics， 也包含SM的。 mm会发送metrics 给monitor系统(类似ganglia系统)，同样也会给TM.</p><h2 id="流程："><a href="#流程：" class="headerlink" title="流程："></a>流程：</h2><ul><li><p>提交任务， Aurora分配必要的资源和在一些机器上调度container</p></li><li><p>TM 在一个container上运行起来，并注册到ZK</p></li><li><p>每个container的SM 查询ZK 找到TM， 向TM 发送心跳。</p></li><li><p>当所有的SM 连上TM后， TM 执行分配算法， 不同的compoent到不同的container。 这个阶段叫物理执行计划（类似SQL解析和执行过程）。并将执行计划放到ZK。</p></li><li><p>SM 下载执行计划，并开始相互之间进行连接， 与此同时， 启动HI, hi开始发现container，下载他们的执行计划，并开始执行</p></li><li><p>整个topology完成初始化，开始正式的发送和接收数据。</p></li></ul><h3 id="三种failure-case"><a href="#三种failure-case" class="headerlink" title="三种failure case"></a>三种failure case</h3><h4 id="进程挂了"><a href="#进程挂了" class="headerlink" title="进程挂了"></a>进程挂了</h4><ul><li><p>如果TM 挂了， container会重启TM， TM 会从ZK 上重新下载执行计划。如果有一主多备，则备机会被promotion。 所有SM 会切到新的TM</p></li><li><p>如果SM 挂了， container依旧会重启TM, 并从ZK 下载执行计划， 并检查是否有变化。其他的SM 会连到新的SM</p></li><li><p>如果HI 挂了， 重启并下载执行计划，并重新执行。</p></li></ul><h1 id="外围系统"><a href="#外围系统" class="headerlink" title="外围系统"></a>外围系统</h1><p>外围系统就介绍一下Heron Tracker</p><h2 id="Heron-Tracker"><a href="#Heron-Tracker" class="headerlink" title="Heron Tracker"></a>Heron Tracker</h2><p>负责收集topology的信息， 类似一个gateway的角色。 通过watch zk，发现新的TM， 并获取topology的一些原数据。是一种Aurora service， 提供load balance在多个instance之间。</p><p>可以提供REST API。可以获取</p><ul><li><p>逻辑和物理执行计划</p></li><li><p>各种metrics， 系统的和用户的</p></li><li><p>日志link</p></li></ul><h2 id="Heron-UI-VIZ"><a href="#Heron-UI-VIZ" class="headerlink" title="Heron UI&#x2F;VIZ"></a>Heron UI&#x2F;VIZ</h2><p><img data-src="http://img3.tbcdn.cn/5476e8b07b923/TB1k7DYMVXXXXbgXpXXXXXXXXXX" alt="ui"></p><p>UI 提供传统的UI 方式。</p><p>VIZ 提供全新的UI， 可以看到更多的metrics， 曲线和健康检查。比UI 炫酷很多。</p><p>性能报告和测试过程：</p><p>了解整个系统架构和工作流程后， 后面的性能测试报告， 没有看了， 也差不多有个概念了。</p><h1 id="个人思考和总结："><a href="#个人思考和总结：" class="headerlink" title="个人思考和总结："></a>个人思考和总结：</h1><h2 id="相对于JStorm，-Heron把角色剥离的更清晰明了。"><a href="#相对于JStorm，-Heron把角色剥离的更清晰明了。" class="headerlink" title="相对于JStorm， Heron把角色剥离的更清晰明了。"></a>相对于JStorm， Heron把角色剥离的更清晰明了。</h2><ul><li>调度器</li></ul><p>scheduler 负责container的调度，这个调度非常的纯粹，可以直接复用yarn&#x2F;mesos&#x2F;， 现有的TM 其实就是nimbus，唯一一点变化就是这个TM 只负责自己topology的信息， 不是负责所有topology。这个TM 就相当于yarn下的app master， 非常适合目前主流的调度系统。 当集群规模非常大的时候， 并且每个应用都比较大的时候， 这个架构会非避免nimbus成为瓶颈。 不过storm-on-yarn模式下， 可能通过一个nimbus管理一个小的逻辑集群，也可以解决这个问题， 并且当topology 比较小的时候， 可以通过大家公用一个nimbus，节省一些资源。</p><ul><li>container</li></ul><p>这里特别要把container拿出来仔细说一下， 这个container是Auron的一个资源单元。如果将Auron类似JStorm的worker， 你就会发现角色和架构是多么的类似。</p><pre><code>* container和jstorm的worker都可以设置cgroup，达到一定的资源隔离* container内部的SM/MM 其实就类似jstorm worker内部drainer/dispatcher/metricsreport线程。</code></pre><p>但container 相对jstorm 的worker 还有一些其他的优缺点：</p><p>优点：</p><pre><code>* 这个粒度可以控制的更自由， 这个container 可以控制cpu 到更多的核，更多的内存上限。 但jstorm的worker 基本上最多10个核， 而且当内存太大，在core dump和gc的时候压力会比较大。* container还带一定的supervisor的功能，当container内部任何进程挂了， container都会负责把它重启， 因此整个系统的心态逻辑会非常的简单。 ?Auron &lt;–&gt; container, ? ?Container &lt;– &gt; tm/sm/mm/hi. ?整个系统的心跳压力模型会更简单， 心跳压力（对ZK）也更小</code></pre><h2 id="性能："><a href="#性能：" class="headerlink" title="性能："></a>性能：</h2><p>ppt和文档里面说性能有15倍以上的提升， 这个在某些设置下是可以达到这种效果， 但通常情况性能应该比JStorm还要差一点点。</p><p>如何达到这种效果呢，</p><ul><li><p>前提条件是， grouping方式不是选择localOrShuffle或者localFirst</p></li><li><p>就是把container设置的尽可能的大， 最好是独占一台机器。这样SM和SM 之间的通信就会大幅减少， 而一个container内部的HI 通信走内部通道。因此会有更多的HI走内部通道。而jstorm&#x2F;storm， worker比较多的时候， worker和worker之间会创建netty connection， 更多的netty connection会带来更多的内存消耗和线程切换。 尤其是worker数超过200个以上时。</p></li></ul><p>但为什么说通常情况下，性能应该还要比JStorm差一点点呢。</p><p>因为在生产环境， container 是不可能占有这么多资源， 否则Auron的调度太粗粒度，一台机器只跑一个大container， 会导致更严重的资源浪费。正常情况下， 一个container绑定2 ～ 4个核， 这个时候，和一个普通的jstorm worker没有什么区别， 但jstorm worker内部task之间数据传输的效率会远远高于Heron， 因为Heron的HI 之间即使是走进程间通信方式, 也逃脱不了序列化和反序化的动作， 这个动作肯定会耗时， 更不用说IPC 之间的通信效率和进程内的通信效率。</p><h2 id="资源利用率："><a href="#资源利用率：" class="headerlink" title="资源利用率："></a>资源利用率：</h2><p>Heron 可以非常精准的控制资源使用情况， 能够保证， 申请多少资源，就会用多少资源。 在大集群这个级别会节省资源，在topology级别浪费资源。</p><p>如果JStorm-on-yarn这种系统下， 因为每个逻辑集群会超量申请一些资源， 因此资源可能会多有少量浪费。无法做到像Heron一样精准。 如果改造nimbus成为topology level 类似TM（腾讯在jstorm基础上实现了这个功能）， 这个问题就可以很好的解决。在普通standalone的JStorm模式下， jstorm不会浪费资源， 但因为Standalone，导致这些机器不能被其他编程框架使用， 因此也可以说浪费一定的资源。 但这种情况就是 资源隔离性– 资源利用率的一种平衡， 现在这种根据线上运行情况，浪费程度可以接受。</p><p>在topology这个粒度进行比较时， Heron应该会消耗掉更多的资源。 最大的问题在于， Heron中一个task就是一个process， 论文中没有描叙这个process的公共线程， 可以肯定的是， 这个process比如还有大量的公共线程， 比如ZK-client&#x2F;network-thread&#x2F;container-heartbeat-thread， 一个task一个process，这种设计，相对于一个worker跑更多的task而言，肯定浪费了更多的CPU 和内存。</p><p>至于吐槽在Storm和JStorm，超量申请资源问题， 比如一个topology只要100 个cpu core能完成， 申请了600个core， 这个问题，在jstorm中是绝对不存在的， jstorm的cgroup设置是share + limit方式， 也就是上限是600 core，但topology如果用不到600个core， 别的topology可以抢占到cpu core。 在内存方面， jstorm的worker 内存申请量，是按照worker最大内存申请， 但现代操作系统早就做到了， 给你一个上限， 当你用不了这么多的时候， 其他进程可以抢占。</p><h2 id="在稳定性和debug-ability这点上："><a href="#在稳定性和debug-ability这点上：" class="headerlink" title="在稳定性和debug-ability这点上："></a>在稳定性和debug-ability这点上：</h2><p>Heron 优势非常大， 主要就是通过2点:</p><ul><li><p>自动降级策略， 也就是论文说的backpressure， 这个对于大型应用是非常有效的， 也很显著提高稳定性。</p></li><li><p>一个task一个process， 这个结合降级策略，可以非常快速定位到出错的task， 另外因为一个task 一个process， task之间的影响会非常快， 另外也避免了一个进程使用过大的内存，从而触发严重的GC 问题。</p></li></ul><h1 id="最后总结：-1"><a href="#最后总结：-1" class="headerlink" title="最后总结："></a>最后总结：</h1><p>Heron更适合超大规模的机器， 超过1000台机器以上的集群。 在稳定性上有更优异的表现， 在性能上，表现一般甚至稍弱一些，在资源使用上，可以和其他编程框架共享资源，但topology级别会更浪费一些资源。</p><p>另外应用更偏向于大应用，小应用的话，会多一点点资源浪费， 对于大应用，debug-ability的重要性逐渐提升。 另外对于task的设计， task会走向更重更复杂， 而JStorm的task是向更小更轻量去走。</p><p>未来JStorm可以把自动降级策略引入， 通过实现阿里妈妈的ASM， debug-ability应该远超过storm， 不会逊色于Heron， 甚至更强。</p><h1 id="其他流式编程框架"><a href="#其他流式编程框架" class="headerlink" title="其他流式编程框架"></a>其他流式编程框架</h1><p>1.S4 Distributed Stream Computing Platform.?<span class="exturl" data-url="aHR0cDovL2luY3ViYXRvci5hcGFjaGUub3JnL3M0Lw==">http://incubator.apache.org/s4/<i class="fa fa-external-link-alt"></i></span></p><ol start="2"><li><p>Spark Streaming. <span class="exturl" data-url="aHR0cHM6Ly9zcGFyay5hcGFjaGUub3JnL3N0cmVhbWluZy8=">https://spark.apache.org/streaming/<i class="fa fa-external-link-alt"></i></span>?</p></li><li><p>Apache Samza. <span class="exturl" data-url="aHR0cDovL3NhbXphLmluY3ViYXRvci5hcGFjaGUub3JnLw==">http://samza.incubator.apache.org<i class="fa fa-external-link-alt"></i></span></p></li><li><p>Tyler Akidau, Alex Balikov, Kaya Bekiroglu, Slava Chernyak, Josh?Haberman, Reuven Lax, Sam McVeety, Daniel Mills, Paul?Nordstrom, Sam Whittle: MillWheel: Fault-Tolerant Stream?Processing at Internet Scale.?PVLDB 6(11): 1033-1044 (2013)</p></li></ol><p>5.?Mohamed H. Ali, Badrish Chandramouli, Jonathan Goldstein,Roman Schindlauer: The Extensibility Framework in Microsoft?StreamInsight.?ICDE?2011: 1242-1253</p><ol start="6"><li><p>Rajagopal Ananthanarayanan, Venkatesh Basker, Sumit Das, Ashish?Gupta, Haifeng Jiang, Tianhao Qiu, Alexey Reznichenko, Deomid?Ryabkov, Manpreet Singh, Shivakumar Venkataraman: Photon:?Fault-tolerant and Scalable Joining of Continuous Data Streams.?SIGMOD?2013: 577-588</p></li><li><p>DataTorrent.?<span class="exturl" data-url="aHR0cHM6Ly93d3cuZGF0YXRvcnJlbnQuY29tLw==">https://www.datatorrent.com<i class="fa fa-external-link-alt"></i></span></p></li><li><p>Simon Loesing, Martin Hentschel, Tim Kraska, Donald Kossmann:?Stormy: An Elastic and Highly Available Streaming Service in the?Cloud. EDBT&#x2F;ICDT Workshops 2012: 55-60</p></li></ol>]]>
    </content>
    <id>https://ilongda.com/2015/docs/paper/heron/</id>
    <link href="https://ilongda.com/2015/docs/paper/heron/"/>
    <published>2015-06-04T11:42:57.000Z</published>
    <summary>Twitter Heron 流计算论文深度笔记：对比 Storm 的调试性、资源隔离与稳定性，及千台规模集群适用场景分析</summary>
    <title>深度分析Twitter Heron</title>
    <updated>2026-06-09T08:46:25.962Z</updated>
  </entry>
</feed>
