<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>数据库 node of Ruby China Forum</title>
    <link>https://ruby-china.org/</link>
    <description>Recent Topic in 数据库 of Ruby China Forum.</description>
    <item>
      <title>行锁在日常开发中用的真的不多吗？Prisma 居然没有提供行锁的 API</title>
      <description>&lt;p&gt;新项目试了一下 Node 目前被推荐最多的 ORM prisma &lt;a href="https://github.com/prisma/prisma" rel="nofollow" target="_blank"&gt;https://github.com/prisma/prisma&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;然后发现这个框架居然没有提供行锁的任何 API，只能写 raw sql 来实现。&lt;/p&gt;

&lt;p&gt;但在个人认知里，其实行锁用的还比较频繁，特别是现在互联网公司非常喜欢在 mysql 里面存序列化后的 JSON，很多需要增量更新的情况，比如读取全量的 json 字符串，然后把新的字段更新进去，再保存。没有行锁的话，这个操作很容易就互相覆盖了。（虽然这是一种不太好的实践，但说实话在几家大厂都见过大量这种写法）&lt;/p&gt;</description>
      <author>willx</author>
      <pubDate>Fri, 31 May 2024 11:45:50 +0800</pubDate>
      <link>https://ruby-china.org/topics/43718</link>
      <guid>https://ruby-china.org/topics/43718</guid>
    </item>
    <item>
      <title>浅析 PostgreSQL + zhparser 进行中文搜索的分词与排序优化</title>
      <description>&lt;h2 id="背景"&gt;背景&lt;/h2&gt;
&lt;p&gt;之前在做 &lt;a href="https://resources.gitlab.cn/" rel="nofollow" target="_blank" title=""&gt;gitlab resource center&lt;/a&gt; 搜索功能时，初版上线的搜索结果未能令人满意。因此便开始着手对分词和排序进行了改进。&lt;/p&gt;

&lt;p&gt;在第一版搜索功能上线后，当输入关键字&lt;code&gt;安全高效&lt;/code&gt;和&lt;code&gt;安全&lt;/code&gt;时，系统能够正确返回相关文章。然而，在搜索&lt;code&gt;高效&lt;/code&gt;关键字时，却发现返回的文章为空。期望的功能是，只要文章标题、内容或描述中包含关键字&lt;code&gt;高效&lt;/code&gt;，就能够返回相关结果。&lt;/p&gt;
&lt;h2 id="分词"&gt;分词&lt;/h2&gt;
&lt;p&gt;首先我们看看怎么在 PostgreSQL 中启用 &lt;code&gt;zhparser&lt;/code&gt; 插件的：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;zhparser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;SEARCH&lt;/span&gt; &lt;span class="n"&gt;CONFIGURATION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;chinese_zh&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;SEARCH&lt;/span&gt; &lt;span class="n"&gt;CONFIGURATION&lt;/span&gt; &lt;span class="n"&gt;chinese_zh&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PARSER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;zhparser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;SEARCH&lt;/span&gt; &lt;span class="n"&gt;CONFIGURATION&lt;/span&gt; &lt;span class="n"&gt;chinese_zh&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="n"&gt;MAPPING&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;simple&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里的 a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z 均对应了一种词性。&lt;/p&gt;

&lt;p&gt;下面是词性的关系映射表，先列举出来，用于我们后面分析。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"adjective,        形容词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;98&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"differentiation,  区别词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"conjunction,      连词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"adverb,           副词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"exclamation,      感叹词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"position,         方位词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;103&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"root,             词根"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"head,             前连接成分"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;105&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"idiom,            成语"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;106&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"abbreviation,     简称"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;107&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"tail,             后连接成分"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;108&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"tmp,              习用语"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;109&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"numeral,          数词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"noun,             名词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;111&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"onomatopoeia,     拟声词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;112&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"prepositional,    介词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"quantity,         量词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"pronoun,          代词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;115&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"space,            处所词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;116&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"time,             时语素"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;117&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"auxiliary,        助词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;118&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"verb,             动词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;119&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"punctuation,      标点符号"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"unknown,          未知词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;121&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"modal,            语气词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;122&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"status,           状态词"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们直接通过 zhparser 分词策略分词之后的看看他的返回结果&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ts_parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zhparser'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'这种将安全高效整合进研发和运维流程中的安全构建方式'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="n"&gt;tokid&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="n"&gt;token&lt;/span&gt;
&lt;span class="c1"&gt;-------+----------&lt;/span&gt;
   &lt;span class="mi"&gt;114&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;这种&lt;/span&gt;
   &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;将&lt;/span&gt;
   &lt;span class="mi"&gt;110&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;安全高效&lt;/span&gt;
   &lt;span class="mi"&gt;118&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;整&lt;/span&gt;
   &lt;span class="mi"&gt;110&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;合进&lt;/span&gt;
   &lt;span class="mi"&gt;106&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;研发&lt;/span&gt;
    &lt;span class="mi"&gt;99&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;和&lt;/span&gt;
   &lt;span class="mi"&gt;118&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;运&lt;/span&gt;
   &lt;span class="mi"&gt;113&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;维&lt;/span&gt;
   &lt;span class="mi"&gt;110&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;流程&lt;/span&gt;
   &lt;span class="mi"&gt;102&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;中&lt;/span&gt;
   &lt;span class="mi"&gt;117&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;的&lt;/span&gt;
    &lt;span class="mi"&gt;97&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;安全&lt;/span&gt;
   &lt;span class="mi"&gt;118&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;构建&lt;/span&gt;
   &lt;span class="mi"&gt;110&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;方式&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;-- 对应的词性可以在上面的关系映射表里面看&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时 zhparser 会拆分出 &lt;code&gt;安全高效&lt;/code&gt; 和 &lt;code&gt;安全&lt;/code&gt; 这两个词，并没有拆分出我们所需要的 &lt;code&gt;高效&lt;/code&gt; 关键词。&lt;/p&gt;

&lt;p&gt;zhparser 有以下配置，且所有配置默认值均为 false。&lt;/p&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="n"&gt;zhparser&lt;/span&gt;.&lt;span class="n"&gt;punctuation_ignore&lt;/span&gt; = &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="c"&gt;#忽略所有的标点等特殊符号: 
&lt;/span&gt;&lt;span class="n"&gt;zhparser&lt;/span&gt;.&lt;span class="n"&gt;seg_with_duality&lt;/span&gt; = &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="c"&gt;#闲散文字自动以二字分词法聚合: 
&lt;/span&gt;&lt;span class="n"&gt;zhparser&lt;/span&gt;.&lt;span class="n"&gt;dict_in_memory&lt;/span&gt; = &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="c"&gt;#将词典全部加载到内存里: 
&lt;/span&gt;&lt;span class="n"&gt;zhparser&lt;/span&gt;.&lt;span class="n"&gt;multi_short&lt;/span&gt; = &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="c"&gt;#短词复合: 
&lt;/span&gt;&lt;span class="n"&gt;zhparser&lt;/span&gt;.&lt;span class="n"&gt;multi_duality&lt;/span&gt; = &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="c"&gt;#散字二元复合: 
&lt;/span&gt;&lt;span class="n"&gt;zhparser&lt;/span&gt;.&lt;span class="n"&gt;multi_zmain&lt;/span&gt; = &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="c"&gt;#重要单字复合: 
&lt;/span&gt;&lt;span class="n"&gt;zhparser&lt;/span&gt;.&lt;span class="n"&gt;multi_zall&lt;/span&gt; = &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="c"&gt;#全部单字复合: 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;因为 zhparser 插件是一个基于 &lt;a href="https://github.com/hightman/scws" rel="nofollow" target="_blank" title=""&gt;SCWS&lt;/a&gt; 能力开发的 PG 中文分词插件。所以我们可以去 &lt;a href="http://www.xunsearch.com/scws/demo/v48.php" rel="nofollow" target="_blank" title=""&gt;SCWS 在线测试平台&lt;/a&gt; 根据需要做一些调整。最终我们决定将&lt;code&gt;短词复合&lt;/code&gt;配置打开。有以下两种方式去配置（具体怎么应用看对应的云服务商怎么方便处理。）：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;修改 &lt;code&gt;postgresql.conf&lt;/code&gt; 文件配置&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;将 &lt;code&gt;zhparser.multi_short = true&lt;/code&gt; 添加到 &lt;code&gt;postgresql.conf&lt;/code&gt; 文件中，然后进入数据库执行 &lt;code&gt;SELECT pg_reload_conf();&lt;/code&gt; 使 PostgreSQL 重新加载配置文件。&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;通过 sql 命令配置&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;set_config&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zhparser.multi_short'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'true'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;SYSTEM&lt;/span&gt; &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;zhparser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;multi_short&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;pg_reload_conf&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;em&gt;需要注意：执行 sql 必须是原生的 superuser 权限，并且执行 sql 的时候不能在事务中运行&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;将以上配置完毕之后再来看看 zhparser 分词结果是什么&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ts_parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'zhparser'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'这种将安全高效整合进研发和运维流程中的安全构建方式'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="n"&gt;tokid&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="n"&gt;token&lt;/span&gt;
&lt;span class="c1"&gt;-------+----------&lt;/span&gt;
   &lt;span class="mi"&gt;114&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;这种&lt;/span&gt;
   &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;将&lt;/span&gt;
   &lt;span class="mi"&gt;110&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;安全高效&lt;/span&gt;
    &lt;span class="mi"&gt;97&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;安全&lt;/span&gt;
   &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;高效&lt;/span&gt;
   &lt;span class="mi"&gt;118&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;整&lt;/span&gt;
   &lt;span class="mi"&gt;110&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;合进&lt;/span&gt;
   &lt;span class="mi"&gt;106&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;研发&lt;/span&gt;
    &lt;span class="mi"&gt;99&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;和&lt;/span&gt;
   &lt;span class="mi"&gt;118&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;运&lt;/span&gt;
   &lt;span class="mi"&gt;113&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;维&lt;/span&gt;
   &lt;span class="mi"&gt;110&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;流程&lt;/span&gt;
   &lt;span class="mi"&gt;102&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;中&lt;/span&gt;
   &lt;span class="mi"&gt;117&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;的&lt;/span&gt;
    &lt;span class="mi"&gt;97&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;安全&lt;/span&gt;
   &lt;span class="mi"&gt;118&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;构建&lt;/span&gt;
   &lt;span class="mi"&gt;110&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="err"&gt;方式&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;-- 对应的词性可以在上面的关系映射表里面看&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此时&lt;code&gt;高效&lt;/code&gt;关键词已经被拆分出来了，并且它是&lt;code&gt;副词&lt;/code&gt;词性，接下来调整下 &lt;code&gt;chinese_zh&lt;/code&gt; 的策略，只保留我们需要的词性&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 删除分词策略：&lt;/span&gt;

&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;SEARCH&lt;/span&gt; &lt;span class="n"&gt;CONFIGURATION&lt;/span&gt; &lt;span class="n"&gt;chinese_zh&lt;/span&gt; &lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="n"&gt;MAPPING&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;exists&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;z&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 添加分词策略：&lt;/span&gt;
&lt;span class="c1"&gt;-- 添加名词（n）、动词（v）、形容词（a）、成语（i）、叹词（e）、习用语（l）和副词（d）七种分词策略：&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;SEARCH&lt;/span&gt; &lt;span class="n"&gt;CONFIGURATION&lt;/span&gt; &lt;span class="n"&gt;chinese_zh&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="n"&gt;MAPPING&lt;/span&gt; &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;simple&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后观察 &lt;code&gt;chinese_zh&lt;/code&gt; 的分词结果&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="s1"&gt;'这种将安全高效整合进研发和运维流程中的安全构建方式'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                                &lt;span class="n"&gt;to_tsvector&lt;/span&gt;
&lt;span class="c1"&gt;---------------------------------------------------------------------------------------------&lt;/span&gt;
 &lt;span class="s1"&gt;'合进'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="s1"&gt;'安全'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="s1"&gt;'安全高效'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="s1"&gt;'将'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="s1"&gt;'整'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="s1"&gt;'方式'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt; &lt;span class="s1"&gt;'构建'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="s1"&gt;'流程'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="s1"&gt;'运'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="s1"&gt;'高效'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;-- 每个单词后面跟随了对应的位置信息&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此我们分词调整策略基本告一段落，主要做两个事情&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;修改 zhparser 的配置&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;修改自定义 &lt;code&gt;chinese_zh&lt;/code&gt; 分词策略所保留的词性&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;除此之外 zhparser 还支持自定义词库，所有的自定义词都放在了 &lt;code&gt;zhprs_custom_word&lt;/code&gt; 表里面，默认添加的自定义词的词性是 &lt;code&gt;(120, x, "unknown, 未知词")&lt;/code&gt;，可能用到的 sql 包含但不局限于如下：&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- 添加自定义词&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;zhparser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zhprs_custom_word&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'资金压力'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;-- 自定义词库也支持停止词功能，例如我们不希望词语'这是'单独作为一个分词，同样可以在自定义词库中插入对应的词语和控制符停止特定分词：&lt;/span&gt;
&lt;span class="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;zhparser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zhprs_custom_word&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'这是'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'!'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- 添加/删除自定义分词之后需要执行以下命令才能使词库生效&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;sync_zhprs_custom_word&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;-- 查询已存在的自定义词库&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;zhparser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zhprs_custom_word&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="排序"&gt;排序&lt;/h2&gt;
&lt;p&gt;PostgreSQL 有权重赋值的方法 &lt;a href="https://www.postgresql.org/docs/current/textsearch-controls.html" rel="nofollow" target="_blank" title=""&gt;&lt;code&gt;setweight&lt;/code&gt;&lt;/a&gt; 当然在 &lt;a href="https://github.com/Casecommons/pg_search" rel="nofollow" target="_blank" title=""&gt;pg_search&lt;/a&gt; 中也集成了该接口，便于直接使用。&lt;/p&gt;

&lt;p&gt;权重设置分别可以设定为 &lt;code&gt;A&lt;/code&gt; &lt;code&gt;B&lt;/code&gt; &lt;code&gt;C&lt;/code&gt; &lt;code&gt;D&lt;/code&gt;，默认值为 A&lt;/p&gt;

&lt;p&gt;假设权重用数字来评判的话（满分为 1 分）他们之间的梯度关系如下所示：&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;权重&lt;/th&gt;
&lt;th&gt;对应系数&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;0.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;0.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;0.1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;--- 设置权重为 A&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;ts_rank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setweight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'这种将安全高效整合进研发和运维流程中的安全构建方式'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'高效'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;ts_rank&lt;/span&gt;
&lt;span class="c1"&gt;-----------&lt;/span&gt;
 &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6079271&lt;/span&gt;

&lt;span class="c1"&gt;--- 设置权重为 B&lt;/span&gt;

&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;ts_rank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setweight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'这种将安全高效整合进研发和运维流程中的安全构建方式'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'B'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'高效'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;ts_rank&lt;/span&gt;
&lt;span class="c1"&gt;------------&lt;/span&gt;
 &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;24317084&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;--- 设置权重为 C&lt;/span&gt;

&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;ts_rank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setweight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'这种将安全高效整合进研发和运维流程中的安全构建方式'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'C'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'高效'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;ts_rank&lt;/span&gt;
&lt;span class="c1"&gt;------------&lt;/span&gt;
 &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;12158542&lt;/span&gt;

&lt;span class="c1"&gt;--- 设置权重为 D&lt;/span&gt;

&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;ts_rank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setweight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'这种将安全高效整合进研发和运维流程中的安全构建方式'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'D'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'高效'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;ts_rank&lt;/span&gt;
&lt;span class="c1"&gt;------------&lt;/span&gt;
 &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;06079271&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样的话我们便可以将文章的标题、描述、内容的权重分别设置为 A, B, C，以此算出一个综合的分数来做排序。但这种计算出来的分数只会和&lt;code&gt;词频&lt;/code&gt;相关。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- A 代表权重，0 代表使用的关注度配置&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;ts_rank&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;setweight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsvector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'高效'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_tsquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'chinese_zh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'高效'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;ts_rank&lt;/span&gt;
&lt;span class="c1"&gt;-----------&lt;/span&gt;
 &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6079271&lt;/span&gt;
&lt;span class="c1"&gt;--  即使文档长度很小，最终计算出来的分数依然是 0.6079271&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;由于较长的文档包含查询词的几率更高，因此可能还需要考虑文档的长度，例如具有五个搜索词实例的一百字文档可能比具有五个搜索词实例的千字文档更相关。&lt;/p&gt;

&lt;p&gt;所以在此基础上我们需要引入 PostgreSQL 的 &lt;a href="https://www.postgresql.org/docs/current/textsearch-controls.html#TEXTSEARCH-RANKING" rel="nofollow" target="_blank" title=""&gt;normalization&lt;/a&gt; 参数&lt;/p&gt;

&lt;p&gt;具体怎么配置需要根据业务去调整，我们主要根据文档的长度做了些调整。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#   normalization 查询结果关注度:&lt;/span&gt;
&lt;span class="c1"&gt;#   0（缺省）表示跟长度大小没有关系&lt;/span&gt;
&lt;span class="c1"&gt;#   1 表示关注度（rank）除以文档长度的对数+1&lt;/span&gt;
&lt;span class="c1"&gt;#   2 表示关注度除以文档的长度&lt;/span&gt;
&lt;span class="c1"&gt;#   4 表示关注度除以范围内的平均谐波距离，只能使用ts_rank_cd实现。&lt;/span&gt;
&lt;span class="c1"&gt;#   8 表示关注度除以文档中唯一分词的数量&lt;/span&gt;
&lt;span class="c1"&gt;#   16 表示关注度除以唯一分词数量的对数+1&lt;/span&gt;
&lt;span class="c1"&gt;#   32 表示关注度除以本身+1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;测试一下长文本（由于文本太长这里就不贴出来了）和短文本之间的使用不同的 &lt;code&gt;normalization&lt;/code&gt;之后分数的差距。&lt;/p&gt;
&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;文本长度&lt;/th&gt;
&lt;th&gt;normalization&lt;/th&gt;
&lt;th&gt;分数&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3887&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.6079271&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1087&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0.6079271&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3887&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.057773568&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1087&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.06928112&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3887&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0.00041355583&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1087&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0.0013911375&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;相对来说 normalization 为 1 时，分数变化随着文章长度越长，变化曲率越平滑。&lt;/p&gt;

&lt;p&gt;至此我们排序策略基本告一段落，主要做两个事情&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;为文章的标题、描述、内容分别设置不同的权重&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;设置对应的 normalization 值&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;当然 &lt;a href="https://github.com/Casecommons/pg_search" rel="nofollow" target="_blank" title=""&gt;pg_search&lt;/a&gt; 上面还有更多的开箱即用的搜索的配置，这里就不再一一阐述了&lt;/p&gt;
&lt;h2 id="参考"&gt;参考&lt;/h2&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hightman/scws" rel="nofollow" target="_blank" title=""&gt;SCWS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/amutu/zhparser/tree/master" rel="nofollow" target="_blank" title=""&gt;zhparser&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ruby-china.org/topics/38153" title=""&gt;Postgres Fulltext Search (一)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ruby-china.org/topics/42047" title=""&gt;Postgres Full Text Search with Docker Compose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;【实操系列】如何通过云原生数据仓库实现“一站式全文检索”业务（链接触发到敏感词，原文可能需要自行搜索一下）&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;</description>
      <author>SuMingXuan</author>
      <pubDate>Fri, 10 Nov 2023 20:49:44 +0800</pubDate>
      <link>https://ruby-china.org/topics/43468</link>
      <guid>https://ruby-china.org/topics/43468</guid>
    </item>
    <item>
      <title>PostgreSQL 数据库存放路径初窥探</title>
      <description>&lt;p&gt;用 PostgreSQL 数据库已经有一段时间了，始终对他的目录结构不是特别了解。这篇文章简单总结一下这段时间的发现。就当作是学习笔记了。原文链接： &lt;a href="https://step-by-step.tech/posts/path-of-databases-in-pg" rel="nofollow" target="_blank"&gt;https://step-by-step.tech/posts/path-of-databases-in-pg&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="新的目录，从0开始探索"&gt;新的目录，从 0 开始探索&lt;/h2&gt;
&lt;p&gt;PostgreSQL 服务有很多种启动方式，为了获得一个洁净的环境，我们需要重新启动一个 PG 服务。最简单的方式是使用&lt;code&gt;pg_ctl&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;mkdir &lt;/span&gt;pg_study

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pg_ctl initdb &lt;span class="nt"&gt;-D&lt;/span&gt; pg_study // 初始化数据库服务目录

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; vim pg_study/postgresql.conf // 把端口号改成5678，并保存（假设5432端口已经被占用不跟已有的服务冲突）

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pg_ctl &lt;span class="nt"&gt;-D&lt;/span&gt; pg_study  start // 启动服务
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里简单采用 socket 无密码的方式来登陆，需要用 5678 端口连接这个新的数据库服务&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; psql &lt;span class="nt"&gt;-U&lt;/span&gt; lan &lt;span class="nt"&gt;-d&lt;/span&gt; postgres &lt;span class="nt"&gt;-p&lt;/span&gt; 5678

&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;#&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="数据库放在哪了？"&gt;数据库放在哪了？&lt;/h2&gt;
&lt;p&gt;当你窥探数据库文件夹的时候&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-hS&lt;/span&gt; pg_study

postgresql.conf      pg_wal               pg_notify
pg_hba.conf          pg_subtrans          pg_replslot
global               pg_xact              pg_serial
pg_ident.conf        postmaster.pid       pg_snapshots
base                 postgresql.auto.conf pg_stat
pg_logical           postmaster.opts      pg_tblspc
pg_stat_tmp          pg_commit_ts         pg_twophase
pg_multixact         pg_dynshmem          PG_VERSION
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;会看到一堆不太看得懂的东西，除了一堆文件目录之外就是类似于&lt;code&gt;postgresql.conf&lt;/code&gt;， &lt;code&gt;pg_hba.conf&lt;/code&gt;这种配置文件，还有进程文件&lt;code&gt;postmaster.pid&lt;/code&gt;。他们各自代表什么暂且不管（笔者也不是很清楚），我现在想知道数据库放在哪。答案就是&lt;code&gt;pg_study/base&lt;/code&gt;目录下。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lhS&lt;/span&gt; pg_study/base
total 0
drwx------  297 lan  staff   9.3K Sep 10 21:08 14023
drwx------  296 lan  staff   9.3K Sep 10 21:01 1
drwx------  296 lan  staff   9.3K Sep 10 21:01 14022
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;只有一堆不明觉厉的以数字为名的目录，数字分别代表什么一点头绪都没有。这个时候可以利用数据库的&lt;strong&gt;内省&lt;/strong&gt;机制。&lt;strong&gt;从数据库里面查询数据库本身的资料。&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# select oid, datname from pg_database ;&lt;/span&gt;

&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# select oid, datname from pg_database ;&lt;/span&gt;
  oid  |  datname
&lt;span class="nt"&gt;-------&lt;/span&gt;+-----------
 14023 | postgres
     1 | template1
 14022 | template0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可见总共只有 3 个数据库，每个数据库都有对应的&lt;code&gt;oid&lt;/code&gt;这个&lt;code&gt;oid&lt;/code&gt;其实就是&lt;code&gt;pg_study/base&lt;/code&gt;目录下对应的目录名。我们可以多创建一个新的数据库看看是不是会多一个对应的文件夹&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# CREATE DATABASE study;&lt;/span&gt;
CREATE DATABASE

&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# SELECT oid, datname FROM pg_database ;&lt;/span&gt;
  oid  |  datname
&lt;span class="nt"&gt;-------&lt;/span&gt;+-----------
 14023 | postgres
 16384 | study
     1 | template1
 14022 | template0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可见多了一个&lt;code&gt;oid&lt;/code&gt;为&lt;code&gt;16384&lt;/code&gt;的数据库，在去看看 base 目录下是不是这么回事&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-lhS&lt;/span&gt; pg_study/base
total 0
drwx------  297 lan  staff   9.3K Sep 10 21:08 14023
drwx------  296 lan  staff   9.3K Sep 10 21:01 1
drwx------  296 lan  staff   9.3K Sep 10 21:01 14022
drwx------  296 lan  staff   9.3K Sep 10 21:20 16384
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不出所料，果然多了一个&lt;code&gt;16384&lt;/code&gt;的文件夹。&lt;/p&gt;
&lt;h2 id="不想放在base里面？"&gt;不想放在 base 里面？&lt;/h2&gt;
&lt;p&gt;虽然这种需求不怎么常见，但有没有可能把我们的数据库放在&lt;code&gt;base&lt;/code&gt;之外的目录里呢？这种时候需要利用 PostgreSQL 表空间 (Tablespace) 这个概念了。假设我们想要把数据库放在目录&lt;code&gt;/var/tmp/hello&lt;/code&gt;里面，则针对这个目录创建一个表空间&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# CREATE TABLESPACE newspace LOCATION '/var/tmp/hello';&lt;/span&gt;
CREATE TABLESPACE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后在创建数据库的时候指定这个表空间即可&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# CREATE DATABASE database_in_newspace TABLESPACE newspace ;&lt;/span&gt;
CREATE DATABASE

&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# SELECT oid, datname FROM pg_database WHERE datname ~ 'database_in_newspace';&lt;/span&gt;
 16453 | database_in_newspace
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;新数据库的&lt;code&gt;oid&lt;/code&gt;是&lt;code&gt;16453&lt;/code&gt;，再去查看一下&lt;code&gt;/var/tmp/hello&lt;/code&gt;目录下是否有名为&lt;code&gt;16453&lt;/code&gt;这个文件夹&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; /var/tmp/hello
PG_14_202107181

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; /var/tmp/hello/PG_14_202107181
16453
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;倒是不出我们所料，只不过外面还套了一层&lt;code&gt;PG_14_202107181&lt;/code&gt;。这串主要是根据&lt;code&gt;PG_{{VERSION}}_{{CATALOG VERSION NUMBER}}&lt;/code&gt;。最后一串玩意可以这样去获取&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pg_controldata pg_study | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'Catalog'&lt;/span&gt;
Catalog version number:               202107181
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="数据表怎么存放呢？"&gt;数据表怎么存放呢？&lt;/h2&gt;
&lt;p&gt;一般来说，只要不指定表空间，数据表一般都会默认存放在对应的数据库目录里面的。我们重新创建一个数据库，并在该数据库里面创建一个数据表试试看？&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# CREATE DATABASE where_is_table;&lt;/span&gt;
CREATE DATABASE

&lt;span class="nv"&gt;postgres&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# \c where_is_table&lt;/span&gt;
You are now connected to database &lt;span class="s2"&gt;"where_is_table"&lt;/span&gt; as user &lt;span class="s2"&gt;"lan"&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="nv"&gt;where_is_table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# CREATE TABLE mytable(id integer);&lt;/span&gt;
CREATE TABLE

&lt;span class="nv"&gt;where_is_table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# SELECT oid, datname FROM pg_database WHERE datname = 'where_is_table';&lt;/span&gt;

 16455 | where_is_table
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数据库的&lt;code&gt;oid&lt;/code&gt;是&lt;code&gt;16455&lt;/code&gt;，而在&lt;code&gt;pg_study/base/16455&lt;/code&gt;目录下其实有很多文件&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;ls &lt;/span&gt;pg_study/base/16455 | &lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt;
     296
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;哪个才是数据表呢？我们可以通过查询&lt;code&gt;pg_catalog.pg_class&lt;/code&gt;数据表来找到对应记录。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;where_is_table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# select relname, relfilenode from pg_catalog.pg_class where relname = 'mytable';&lt;/span&gt;
 relname | relfilenode
&lt;span class="nt"&gt;---------&lt;/span&gt;+-------------
 mytable |       16456
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到&lt;code&gt;relfilenode&lt;/code&gt;的值为&lt;code&gt;16456&lt;/code&gt;，这便是对应数据表的文件名，再去&lt;code&gt;pg_study/base/16455&lt;/code&gt;搜索下看看&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; pg_study/base/16455 | &lt;span class="nb"&gt;grep &lt;/span&gt;16456
&lt;span class="nt"&gt;-rw-------&lt;/span&gt;    1 lan  staff       0 Sep 11 18:23 16456
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;该文件是存在的，并且是一个普通文件，并不是目录。只是现在表里面没有任何数据，所以表文件的大小是 0。往里面插入一点数据看看&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;where_is_table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="c"&gt;# INSERT INTO  mytable values (1);&lt;/span&gt;
INSERT 0 1
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; pg_study/base/16455 | &lt;span class="nb"&gt;grep &lt;/span&gt;16456
&lt;span class="nt"&gt;-rw-r--r--&lt;/span&gt;    1 lan  staff    8192 Sep 12 18:03 16456
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;有数据了，文件大了 8KB 左右（这跟 PG 的存储规则有关）。数据表存放路径大概可以用以下公式来概括&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# 没有表空间的情况&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;root_path_of_database_service&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/base/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;oid_of_database&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;relfilenode_of_table&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# 有表空间的情况&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;location_of_tablespace&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/PG_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;catalog_version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;oid_of_database&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;relfilenode_of_table&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;小贴士：为何在&lt;code&gt;pg_catalog.pg_class&lt;/code&gt;里面查找表信息是通过&lt;code&gt;relname&lt;/code&gt;以及&lt;code&gt;relfilenode&lt;/code&gt;，而不是普遍认知的&lt;code&gt;tablename&lt;/code&gt;或者&lt;code&gt;tablefilenode&lt;/code&gt;？因为跟很多常用的数据库不同，在 PostgreSQL 里面把数据表都称作&lt;strong&gt;Relation&lt;/strong&gt;。不知道您是否注意到，当我们运行命令&lt;code&gt;\d&lt;/code&gt;来查看数据库内表信息的时候它会显示&lt;strong&gt;List of relations&lt;/strong&gt;而不是&lt;strong&gt;List of tables&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;这篇文章是一篇简单的学习笔记，简单窥探 PostgreSQL 里面数据库，数据表是如何存放的，同时也展示了 PostgreSQL 里面的一些&lt;strong&gt;内省&lt;/strong&gt;查询。&lt;/p&gt;</description>
      <author>lanzhiheng</author>
      <pubDate>Tue, 13 Sep 2022 09:09:22 +0800</pubDate>
      <link>https://ruby-china.org/topics/42647</link>
      <guid>https://ruby-china.org/topics/42647</guid>
    </item>
    <item>
      <title>PostgreSQL 的执行计划，我想怎么看是否有回表</title>
      <description>&lt;h3 id="测试环境"&gt;测试环境&lt;/h3&gt;&lt;table class="table table-bordered table-striped"&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;th&gt;环境&lt;/th&gt;
&lt;th&gt;版本&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Postgres&lt;/td&gt;
&lt;td&gt;v14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DataGrip&lt;/td&gt;
&lt;td&gt;2022.1.5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;h3 id="测试脚本"&gt;测试脚本&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://chengsukai.oss-cn-hangzhou.aliyuncs.com/blog/d102_seat_center.sql" rel="nofollow" target="_blank"&gt;https://chengsukai.oss-cn-hangzhou.aliyuncs.com/blog/d102_seat_center.sql&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="测试结果"&gt;测试结果&lt;/h3&gt;
&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/chengsukai/405d7c16-d876-4be2-b4ca-c2311c46276f.png!large" title="" alt=""&gt;&lt;/p&gt;</description>
      <author>chengsukai</author>
      <pubDate>Tue, 26 Jul 2022 10:04:30 +0800</pubDate>
      <link>https://ruby-china.org/topics/42550</link>
      <guid>https://ruby-china.org/topics/42550</guid>
    </item>
    <item>
      <title>runc hang 导致 Kubernetes 节点 NotReady</title>
      <description>&lt;p&gt;Kubernetes 1.19.3&lt;/p&gt;

&lt;p&gt;OS: CentOS 7.9.2009&lt;/p&gt;

&lt;p&gt;Kernel: 5.4.94-1.el7.elrepo.x86_64&lt;/p&gt;

&lt;p&gt;Docker: 20.10.6&lt;/p&gt;

&lt;p&gt;先说结论，runc v1.0.0-rc93 有 bug，会导致 docker hang 住。&lt;/p&gt;
&lt;h2 id="发现问题"&gt;发现问题&lt;/h2&gt;
&lt;p&gt;线上告警提示集群中存在 2-3 个 K8s 节点处于 NotReady 的状态，并且 NotReady 状态一直持续。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;kubectl describe node，有 NotReady 相关事件。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/579d08f7-a41f-43e1-a7eb-91d8d30a5230.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;登录问题机器后，查看节点负载情况，一切正常。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;查看 kubelet 日志，发现 PLEG 时间过长，导致节点被标记为 NotReady。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/0cbd22ce-52dc-42fb-8b4f-0e8df16c3ccf.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;docker ps 正常。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;执行 ps 查看进程，发现存在几个 runc init 的进程。runc 是 containerd 启动容器时调用的 OCI Runtime 程序。初步怀疑是 docker hang 住了。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/1aa39196-0006-45fb-9986-725f6c30765b.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;要解决这个问题可以通过两种方法，首先来看一下 A 方案。&lt;/p&gt;
&lt;h2 id="解决方案 A"&gt;解决方案 A&lt;/h2&gt;
&lt;p&gt;针对 docker hang 住这样的现象，通过搜索资料后发现了以下两篇文章里也遇到了相似的问题：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;docker hang 问题排查 [&lt;a href="https://www.likakuli.com/posts/docker-hang/" rel="nofollow" target="_blank"&gt;https://www.likakuli.com/posts/docker-hang/&lt;/a&gt;]&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker hung 住问题解析系列 (一)：pipe 容量不够 [&lt;a href="https://juejin.cn/post/6891559762320703495" rel="nofollow" target="_blank"&gt;https://juejin.cn/post/6891559762320703495&lt;/a&gt;]&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这两篇文章都提到了是由于 pipe 容量不够导致 runc init 往 pipe 写入卡住了，将 /proc/sys/fs/pipe-user-pages-soft 的限制放开，就能解决问题。&lt;/p&gt;

&lt;p&gt;于是，查看问题主机上 /proc/sys/fs/pipe-user-pages-soft 设置的是 16384。所以将它放大 10 倍 echo 163840 &amp;gt; /proc/sys/fs/pipe-user-pages-soft，然而 kubelet 还是没有恢复正常，pleg 报错日志还在持续，runc init 程序也没有退出。&lt;/p&gt;

&lt;p&gt;考虑到 runc init 是 kubelet 调用 CRI 接口创建的，可能需要将 runc init 退出才能使 kubelet 退出。而根据文章中的说明，只需要将对应的 pipe 中的内容读取掉，runc init 就能退出。因为读取 pipe 的内容可以利用「UNIX/Linux 一切皆文件」的原则，通过 lsof -p 查看 runc init 打开的句柄信息，获取写入类型的 pipe 对应的编号（可能存在多个），依次执行 cat /proc/$pid/fd/$id 的方式，读取 pipe 中的内容。尝试了几个后，runc init 果然退出了。&lt;/p&gt;

&lt;p&gt;再次检查，节点状态切换成 Ready，pleg 报错日志也消失了，观察一天也没有出现节点 NotReady 的情况，问题（临时）解决。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;对解决方案 A 疑问&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;虽然问题解决了，但是仔细读 /proc/sys/fs/pipe-user-pages-soft 参数的说明文档，不难发现这个参数跟本次问题的根本原因不太对得上。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/ecb11032-f77c-4803-9a8a-b312c32abf8f.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;pipe-user-pages-soft 含义是对没有 CAP_SYS_RESOURCE CAP_SYS_ADMIN 权限的用户使用 pipe 容量大小做出限制，默认最多只能使用 1024 个 pipe，一个 pipe 容量大小为 16k。&lt;/p&gt;

&lt;p&gt;那这里就有了疑问：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;dockerd/containerd/kubelet 等组件均通过 root 用户运行，并且 runc init 处于容器初始化阶段，理论上不会将 1024 个 pipe 消耗掉。因此，pipe-user-pages-soft 不会对 docker hang 住这个问题产生影响，但是实际参数放大后问题就消失了，解释不通。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;pipe 容量是固定，用户在创建 pipe 时无法声明容量。从线上来看，pipe 的确被建出来了，容量是固定的话，不应该因为用户使用 pipe 总量超过 pipe-user-pages-soft 限制，而导致无法写入的问题。是不是新创建的 pipe 容量变小了，导致原先可以写入的数据，本次无法写入了？&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;目前对 pipe-user-pages-soft 放大了 10 倍，放大 2 倍够不够，哪个值是最合适的值？&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;探索&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;定位问题最直接的方法，就是阅读源码。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;先查看下 Linux 内核跟 pipe-user-pages-soft 相关的代码。线上内核版本为 5.4.94-1，切换到对应的版本进行检索。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static bool too_many_pipe_buffers_soft(unsigned long user_bufs)
{
        unsigned long soft_limit = READ_ONCE(pipe_user_pages_soft);

        return soft_limit &amp;amp;&amp;amp; user_bufs &amp;gt; soft_limit;
}

struct pipe_inode_info *alloc_pipe_info(void)
{
  ...
  unsigned long pipe_bufs = PIPE_DEF_BUFFERS;  // #define PIPE_DEF_BUFFERS        16
  ...

        if (too_many_pipe_buffers_soft(user_bufs) &amp;amp;&amp;amp; is_unprivileged_user()) {
                user_bufs = account_pipe_buffers(user, pipe_bufs, 2);
                pipe_bufs = 2;
        }

        if (too_many_pipe_buffers_hard(user_bufs) &amp;amp;&amp;amp; is_unprivileged_user())
                goto out_revert_acct;

        pipe-&amp;gt;bufs = kcalloc(pipe_bufs, sizeof(struct pipe_buffer),
                             GFP_KERNEL_ACCOUNT);
  ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在创建 pipe 时，内核会通过 too_many_pipe_buffers_soft 检查是否超过当前用户可使用 pipe 容量大小。如果发现已经超过，则将容量大小从 16 个 PAGE_SIZE 调整成 2 个 PAGE_SIZE。通过机器上执行 getconf PAGESIZE 可以获取到 PAGESIZE 是 4096 字节，也就是说正常情况下 pipe 大小为 164096 字节，但是由于超过限制，pipe 大小被调整成 24096 字节，这就有可能出现数据无法一次性写入 pipe 的问题，基本可以验证问题 2 的猜想。&lt;/p&gt;

&lt;p&gt;至此，pipe-user-pages-soft 相关的逻辑也理顺了，相对还是比较好理解的。&lt;/p&gt;

&lt;p&gt;那么，问题就回到了「为什么容器 root 用户 pipe 容量会超过限制」。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;百分百复现&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;找到问题根本原因的第一步，往往是在线下环境复现问题。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;由于线上环境已经都通过方案 A 做了紧急修复，因此，已经无法在线上分析问题了，需要找到一种必现的手段。&lt;/p&gt;

&lt;p&gt;功夫不负有心人，在 issue 中找到了相同的问题，并且可以通过以下方法复现。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/containerd/containerd/issues/5261" rel="nofollow" target="_blank"&gt;https://github.com/containerd/containerd/issues/5261&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/e2d4b2b1-a194-425a-b5b7-41c910a2046d.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo 1 &amp;gt; /proc/sys/fs/pipe-user-pages-soft
while true; do docker run -itd --security-opt=no-new-privileges nginx; done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行以上命令之后，立刻就出现 runc init 卡住的情况，跟线上的现象是一致的。通过 lsof -p 查看 runc init 打开的文件句柄情况：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/d3518a22-7fcf-42b8-ad86-dc68a6228e36.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;可以看到 fd4、fd5、fd6 都是 pipe 类型，其中，fd4 跟 fd6 编号都是 415841，是同一个 pipe。那么，如何来获取 pipe 大小来实际验证下「疑问 2」中的猜想呢？Linux 下没有现成的工具可以获取 pipe 大小，但是内核开放了系统调用 fcntl（fd, F_GETPIPE_SZ）可以获取到，代码如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;unistd.h&amp;gt;
#include &amp;lt;errno.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;
// Must use Linux specific fcntl header.
#include &amp;lt;/usr/include/linux/fcntl.h&amp;gt;

int main(int argc, char *argv[]) {
    int fd = open(argv[1], O_RDONLY);
    if (fd &amp;lt; 0) {
        perror("open failed");
        return 1;
    }

    long pipe_size = (long)fcntl(fd, F_GETPIPE_SZ);
    if (pipe_size == -1) {
        perror("get pipe size failed.");
    }
    printf("pipe size: %ld\\n", pipe_size);

    close(fd);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编译好之后，查看 pipe 大小情况如下：&lt;/p&gt;

&lt;p&gt;重点看下 fd4 跟 fd6，两个句柄对应的是同一个 pipe，获取到的容量大小是 8192 = 2 * PAGESIZE。所以的确是因为 pipe 超过软限制导致 pipe 容量被调整成了 2 * PAGESIZE。&lt;/p&gt;

&lt;p&gt;使用 A 方案解决问题后，我们来看一下 B 方案。&lt;/p&gt;
&lt;h2 id="解决方案 B"&gt;解决方案 B&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/opencontainers/runc/pull/2871" rel="nofollow" target="_blank"&gt;https://github.com/opencontainers/runc/pull/2871&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/2244c355-2908-4446-93bf-ab7d6873adf6.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;该 bug 是在 runc v1.0.0-rc93 中引入的，并且在 v1.0.0-rc94 中通过上面的 PR 修复。那么，线上应该如何做修复呢？是不是需要把 docker 所有组件都升级呢？&lt;/p&gt;

&lt;p&gt;如果把 dockerd/containerd/runc 等组件都升级的话，就需要将业务切走然后才能升级，整个过程相对比较复杂，并且风险较高。而且在本次问题中，出问题的只有 runc，并且只有新创建的容器受到影响。因此顺理成章考虑是否可以单独升级 runc？&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/d1b9d455-73e0-4212-bdbf-7ae6c4d6bd4f.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;因为在 Kubernetes v1.19 版本中还没有弃用 dockershim，因此运行容器整个调用链为：kubelet → dockerd → containerd → containerd-shim → runc → container。不同于 dockerd/containerd 是后台运行的服务端，containerd-shim 调用 runc，实际是调用了 runc 二进制来启动容器。因此，我们只需要升级 runc，对于新创建的容器，就会使用新版本的 runc 来运行容器。&lt;/p&gt;

&lt;p&gt;在测试环境验证了下，的确不会出现 runc init 卡住的情况了。最终，逐步将线上 runc 升级成 v1.1.1，并将 /proc/sys/fs/pipe-user-pages-soft 调整回原默认值。runc hang 住的问题圆满解决。&lt;/p&gt;
&lt;h2 id="分析&amp;amp;总结"&gt;分析&amp;amp;总结&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;PR 做了什么修复？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Bug 的缘由。当容器开启 no-new-privileges 后，runc 会需要去卸载一段已经加载的 bpf 代码，然后重新加载 patch 后的 bpf 代码。在 bpf 的设计中，需要先获取已经加载的 bpf 代码，然后才能利用这段代码调用卸载接口。在获取 bpf 代码，内核开放了 seccomp_export_bpf 函数，runc 采用了 pipe 作为 fd 句柄传参来获取代码，由于 seccomp_export_bpf 函数是同步阻塞的，内核会将代码写入到 fd 句柄中，因此，如果 pipe 大小太小的话，就会出现 pipe 数据写满后无法写入 bpf 代码导致卡住的情况。&lt;/p&gt;

&lt;p&gt;PR 中的解决方案。启动一个 goroutine 来及时读取 pipe 中的内容，而不是等数据写入完成后再读取。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;为什么超过限制？&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;容器的 root 用户 UID 为 0，而宿主机的 root 用户 UID 也是 0。在内核统计 pipe 使用量时，认为是同一用户，没有做区分。所以，当 runc init 申请 pipe 时，内核判断当前用户没有特权，就查询 UID 为 0 的用户 pipe 使用量，由于内核统计的是所有 UID 为 0 用户（包括容器内）pipe 使用量的总和，所以已经超过了 /proc/sys/fs/pipe-user-pages-soft 中的限制。而实际容器 root 用户 pipe 使用量并没有超过限制。这就解释了前面提到的疑问 2。&lt;/p&gt;

&lt;p&gt;所以我们最后做个总结，本次故障的原因是，操作系统对 pipe-user-pages-soft 有软限制，但是由于容器 root 用户的 UID 与宿主机一致都是 0，内核统计 pipe 使用量时没有做区分，导致当 UID 为 0 的用户 pipe 使用量超过软限制后，新分配的 pipe 容量会变小。而 runc 1.0.0-rc93 正好会因为 pipe 容量太小，导致数据无法完整写入，写入阻塞，一直同步等待，进而 runc init 卡住，kubelet pleg 状态异常，节点 NotReady。&lt;/p&gt;

&lt;p&gt;修复方案，runc 通过 goroutine 及时读取 pipe 内容，防止写入阻塞。&lt;/p&gt;

&lt;p&gt;参考资料&lt;/p&gt;

&lt;p&gt;&lt;a href="https://iximiuz.com/en/posts/container-learning-path/" rel="nofollow" target="_blank"&gt;https://iximiuz.com/en/posts/container-learning-path/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://medium.com/@mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf" rel="nofollow" target="_blank"&gt;https://medium.com/@mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://man7.org/linux/man-pages/man7/pipe.7.html" rel="nofollow" target="_blank"&gt;https://man7.org/linux/man-pages/man7/pipe.7.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gist.github.com/cyfdecyf/1ee981611050202d670c" rel="nofollow" target="_blank"&gt;https://gist.github.com/cyfdecyf/1ee981611050202d670c&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/containerd/containerd/issues/5261" rel="nofollow" target="_blank"&gt;https://github.com/containerd/containerd/issues/5261&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/opencontainers/runc/pull/2871" rel="nofollow" target="_blank"&gt;https://github.com/opencontainers/runc/pull/2871&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="推荐阅读"&gt;推荐阅读&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.upyun.com/tech/article/701/%E9%9D%A2%E8%AF%95%E5%AE%98%E9%97%AE%EF%BC%8CRedis%20%E6%98%AF%E5%8D%95%E7%BA%BF%E7%A8%8B%E8%BF%98%E6%98%AF%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%88%91%E6%87%B5%E4%BA%86.html" rel="nofollow" target="_blank" title=""&gt;面试官问，Redis 是单线程还是多线程我懵了&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.upyun.com/tech/article/719/%E3%80%90%E5%AE%9E%E6%93%8D%E5%B9%B2%E8%B4%A7%E3%80%91%E5%81%9A%E5%A5%BD%E8%BF%99%2016%20%E9%A1%B9%E4%BC%98%E5%8C%96%EF%BC%8C%E4%BD%A0%E7%9A%84%20Linux%20%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F%E7%84%95%E7%84%B6%E4%B8%80%E6%96%B0.html" rel="nofollow" target="_blank" title=""&gt;【实操干货】做好这 16 项优化，你的 Linux 操作系统焕然一新&lt;/a&gt;&lt;/p&gt;</description>
      <author>upyun</author>
      <pubDate>Mon, 04 Jul 2022 11:30:30 +0800</pubDate>
      <link>https://ruby-china.org/topics/42497</link>
      <guid>https://ruby-china.org/topics/42497</guid>
    </item>
    <item>
      <title>什么是走索引？</title>
      <description>&lt;p&gt;索引是一种利用某种规则的数据结构与实际数据的关系加快数据查找的功能。我们的数据库中存储有大量的内容，而索引能够通过数据节点，根据特定的规则和算法快速查找到节点对应的实际文件的位置。简单来说索引就像书的目录，能够帮助我们准确定位到书籍具体的内容。&lt;/p&gt;

&lt;p&gt;最近在学习索引的时候遇到了一个问题，下面我们通过重现的方式来看一下。&lt;/p&gt;

&lt;p&gt;首先建立一个如下测试表：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE `simple_table` (
  `id` int NOT NULL AUTO_INCREMENT,
  `c1` datetime DEFAULT NULL,
  `c2` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `c2__idx` (`c2`),
  KEY `fun_c1_idx` ((cast(`c1` as date)))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fun_c1_idx: 是 mysql8 开始支持的函数索引&lt;/p&gt;

&lt;p&gt;然后往这个表里随机插入 1000 条数据。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select * from simple_table where date(c2) = '2022-01-01';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到上面的这条 SQL 语句不能走索引。因为索引树中存储的是列的实际值和主键值，所以对条件字段做函数操作是会让索引失效的。简单来说就是，如果拿‘2022-01-01’去匹配，将无法定位到索引树中的值。因此正确选择是放弃走索引，选择全表扫描。&lt;/p&gt;

&lt;p&gt;我们再看下一条 SQL。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select id,c2 from simple_table where date(c2) = '2022-01-01';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与第一条不同，这条 SQL 只返回了部分列，而且这些列都在索引中了。然后我们用 explain 分析一下这条 SQL 的执行计划，判断它能否走索引：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/770b411e-e92a-40b5-8488-1b1016ae2d13.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;上图可以明显看到 key 值为&lt;code&gt;c2__idx&lt;/code&gt;，即走了索引。&lt;/p&gt;

&lt;p&gt;这里就很奇怪，不是说对条件字段做函数操作是会让索引失效吗，为什么这里又走了索引？&lt;/p&gt;

&lt;p&gt;这就是我当时在学习时遇到的问题，后来我发现是因为我没有搞清楚“走索引”的意思。大家都知道索引能加快查询，但是索引能加快查询的原因你知道么？答案是减少了查询的次数。&lt;/p&gt;

&lt;p&gt;现在我们回到上面的 SQL，可以看到虽然 key 值为&lt;code&gt;c2__idx&lt;/code&gt;，但是 rows 值为 1000。也就是扫描了扫描全表，即 &lt;code&gt;c2__idx&lt;/code&gt;的所有记录。但是由于&lt;code&gt;c2__idx&lt;/code&gt;已经包含了所有需要查询的列，优化器才选择了走这个索引。&lt;/p&gt;

&lt;p&gt;最后再来思考一个问题，使用了索引是否一定快？这个问题我们通过一个具体例子看一下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select * from simple_table;
select * from simple_table where id &amp;gt; 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;不需要 explain 分析直接肉眼观察就能看到第一条 SQL 没有走索引，第二条 SQL 使用了主键索引。可以看到没有使用索引的速度快一些，这是因为虽然使用了索引，但是还是从主键索引的最左边的叶节点开始向右扫描整个索引树，进行了全表扫描，这让索引失去了意义。&lt;/p&gt;

&lt;p&gt;总结一下：查询是否使用索引，只是表示一个 SQL 语句的执行过程；而是否为慢查询，是由它执行的时间决定的，也就是说是否使用了索引和是否是慢查询两者之间没有必然的联系。我们在使用索引时，不应只关注是否起作用，而应该关心索引是否减少了查询扫描的数据行数，扫描行数减少效率才会得到提升。对于一个大表，不止要创建索引，还要考虑索引过滤性，过滤性好，执行速度才会快。&lt;/p&gt;
&lt;h3 id="推荐阅读"&gt;推荐阅读&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.upyun.com/tech/article/715/Base64%20%E7%BC%96%E7%A0%81%E7%9F%A5%E8%AF%86%EF%BC%8C%E4%B8%80%E6%96%87%E6%89%93%E5%B0%BD%EF%BC%81.html" rel="nofollow" target="_blank" title=""&gt;Base64 编码知识，一文打尽！&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.upyun.com/tech/article/717/Golang%20%E5%B8%B8%E8%A7%81%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B9%8B%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F.html" rel="nofollow" target="_blank" title=""&gt;Golang 常见设计模式之单例模式&lt;/a&gt;&lt;/p&gt;</description>
      <author>upyun</author>
      <pubDate>Wed, 15 Jun 2022 11:09:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/42458</link>
      <guid>https://ruby-china.org/topics/42458</guid>
    </item>
    <item>
      <title>Change Buffer 只适用于非唯一索引页？错</title>
      <description>&lt;p&gt;最近在网上看到一些文章里说：“change buffer 只适用于非唯一索引页。”其实这个观点是错的，先来看看官方文档对 change buffer 的介绍：&lt;/p&gt;

&lt;p&gt;文档地址：&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/innodb-change-buffer.html" rel="nofollow" target="_blank"&gt;https://dev.mysql.com/doc/refman/8.0/en/innodb-change-buffer.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The change buffer is a special data structure that caches changes to secondary index pages when those pages are not in the buffer pool.&lt;/p&gt;

&lt;p&gt;这里的意思是，缓存那些不在 buffer pool 中的二级索引页，并不是指非唯一的二级索引。那具体使用 change buffer 的条件是什么？其实具体使用条件主要汇集在以下五点：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;用户设置选项 innodb_change_buffering。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;在 mysql 的索引结构中，只有叶子结点才存储数据。因此有叶子节点才考虑是否使用 ibuf。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;如上面文档显示的一样，change buffer 只能缓存二级索引页，所以对于聚集索引，不可以缓存操作。聚簇索引页是由 Innodb 引擎将数据页加载到 Buffer Pool 中（这个查找过程是顺序 I/O），然后进行数据记录插入或者更新、删除。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;因为唯一二级索引（unique key）的索引记录具有唯一性，因此无法缓存插入和更新操作，但可以缓存删除操作；&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;表上没有 flush 操作，例如执行 flush table for export 时，不允许对表进行 ibuf 缓存（通过 dict_table_t::quiesce 进行标识）&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;接下来我们结合源码和文档来看看具体操作。&lt;/p&gt;

&lt;p&gt;源码地址：GitHub - mysql/mysql-server: MySQL Server, the world's most popular open source database, and MySQL&lt;/p&gt;

&lt;p&gt;先看第一点设置选项尾 innodb_change_buffering，它能够针对三种类型的操作 INSERT、DELETE-MARK、DELETE 进行缓存，三者对应 dml 语句关系如下：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;INSERT 操作：插入二级索引。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;先进行 DELETE-MARK 操作，再进行 INSERT 操作：更新二级索引。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;DELETE-MARK 操作：删除二级索引&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 代码路径：storage/innobase/include/ibuf0ibuf.h

/* Possible operations buffered in the insert/whatever buffer. See
ibuf_insert(). DO NOT CHANGE THE VALUES OF THESE, THEY ARE STORED ON DISK. */
typedef enum {
  IBUF_OP_INSERT = 0,
  IBUF_OP_DELETE_MARK = 1,
  IBUF_OP_DELETE = 2,

  /* Number of different operation types. */
  IBUF_OP_COUNT = 3
} ibuf_op_t;

/** Combinations of operations that can be buffered.
@see innodb_change_buffering_names */
enum ibuf_use_t {
  IBUF_USE_NONE = 0,
  IBUF_USE_INSERT,             /* insert */
  IBUF_USE_DELETE_MARK,        /* delete */
  IBUF_USE_INSERT_DELETE_MARK, /* insert+delete */
  IBUF_USE_DELETE,             /* delete+purge */
  IBUF_USE_ALL                 /* insert+delete+purge */
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;此外 innodb_change_buffering 还可以通过设置其他选项来进行相应的缓存操作：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;all：默认值，默认开启 buffer inserts、delete-marking operations、purges。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;none：不开启 change buffer。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;inserts：只是开启 buffer insert 操作。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;deletes：只是开 delete-marking 操作。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;changes：开启 buffer insert 操作和 delete-marking 操作。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;purges：对只是在后台执行的物理删除操作开启 buffer 功能。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;第二点还需要大家进行判断条件即可，所以就不进行扩展讲解了，我们来细说一下第三点。&lt;/p&gt;

&lt;p&gt;第三点的具体参考函数为 ibuf_should_try，它满足 ibuf 缓存条件后，会使用两种模式去尝试获取数据页。&lt;/p&gt;

&lt;p&gt;这里说明一下，在 MySQL5.5 之前的版本中，由于只支持缓存 insert 操作，所以最初叫做 insert buffer，只是后来的版本中支持了更多的操作类型缓存，才改叫 change buffer，但是代码中与 change buffer 相关的 函数或变量还是以 ibuf 前缀开头。&lt;/p&gt;

&lt;p&gt;下面是函数的具体实现，地址在：&lt;code&gt;storage/innobase/include/ibuf0ibuf.ic&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/** A basic partial test if an insert to the insert buffer could be possible and
 recommended. */
static inline ibool ibuf_should_try(
    dict_index_t *index,     /*!&amp;lt; in: index where to insert */
    ulint ignore_sec_unique) /*!&amp;lt; in: if != 0, we should
                             ignore UNIQUE constraint on
                             a secondary index when we
                             decide */
{
  return (innodb_change_buffering != IBUF_USE_NONE &amp;amp;&amp;amp; ibuf-&amp;gt;max_size != 0 &amp;amp;&amp;amp;
          index-&amp;gt;space != dict_sys_t::s_dict_space_id &amp;amp;&amp;amp;
          !index-&amp;gt;is_clustered() &amp;amp;&amp;amp; !dict_index_is_spatial(index) &amp;amp;&amp;amp;
          !dict_index_has_desc(index) &amp;amp;&amp;amp;
          index-&amp;gt;table-&amp;gt;quiesce == QUIESCE_NONE &amp;amp;&amp;amp;
          (ignore_sec_unique || !dict_index_is_unique(index)) &amp;amp;&amp;amp;
          srv_force_recovery &amp;lt; SRV_FORCE_NO_IBUF_MERGE);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面加粗和标红的地方就是对唯一二级索引的判断的地方，意思是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;当 ignore_sec_unique 这个变量为 0 时，如果修改的是唯一二级索引记录，就不能使用。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;当 ignore_sec_unique 这个变量为 1 时，如果修改的是唯一二级索引记录，还可以试着使用一下。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ignore_sec_unique 的取值在：&lt;code&gt;storage/innobase/btr/btr0cur.cc&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (btr_op != BTR_NO_OP &amp;amp;&amp;amp;
    ibuf_should_try(index, btr_op != BTR_INSERT_OP)) {
  /* Try to buffer the operation if the leaf
  page is not in the buffer pool. */

  fetch = btr_op == BTR_DELETE_OP ? Page_fetch::IF_IN_POOL_OR_WATCH
                                  : Page_fetch::IF_IN_POOL;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中红框中的 btr_op 指的是本次修改的具体操作是什么，也就是：&lt;/p&gt;

&lt;p&gt;当此次具体的修改操作是 INSERT 操作时，ignore_sec_unique 为 0，也就是当修改的是唯一二级索引记录时，不可以使用 ibuf。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;当此次具体的修改操作不是 INSERT 操作时，ignore_sec_unique 为 1，也就是当修改的是唯一二级索引记录时，可以试着使用 ibuf。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;可以看到当 ibuf_should_try 函数返回 1 时，也就是可以试着用一下 ibuf，那么就把读取 buffer pool 中页面的模式改一下。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最后总结一下，看完了本文相信大家都能比较明确的意识到，网上说只有非唯一索引才能使用 change buffer 的说法，毫无疑问是错的。只要满足了其它 4 个条件，对唯一索引进行的删除操作完全可以使用 change buffer 优化。&lt;/p&gt;
&lt;h3 id="推荐阅读"&gt;推荐阅读&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.upyun.com/tech/article/705/javaScript%20%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86%E6%9C%BA%E5%88%B6.html" rel="nofollow" target="_blank" title=""&gt;javaScript 内存管理机制&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.upyun.com/tech/article/707/130%20%E8%A1%8C%E4%BB%A3%E7%A0%81%E6%90%9E%E5%AE%9A%E6%A0%B8%E9%85%B8%E7%BB%9F%E8%AE%A1%EF%BC%8C%E7%A8%8B%E5%BA%8F%E5%91%98%E5%9C%A8%E6%8A%97%E7%96%AB%E6%9C%9F%E9%97%B4%E7%9A%84%E5%A4%A7%E8%83%BD%E9%87%8F.html" rel="nofollow" target="_blank" title=""&gt;130 行代码搞定核酸统计，程序员在抗疫期间的大能量&lt;/a&gt;&lt;/p&gt;</description>
      <author>upyun</author>
      <pubDate>Thu, 26 May 2022 10:08:45 +0800</pubDate>
      <link>https://ruby-china.org/topics/42419</link>
      <guid>https://ruby-china.org/topics/42419</guid>
    </item>
    <item>
      <title>闲来无事，用 Ruby 撸了个 LSM-Tree</title>
      <description>&lt;h2 id="Introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;最近对 LSM-Tree 比较好奇，为了更好的理解，所以就用 Ruby 写了一个 toy project &lt;a href="https://github.com/yfractal/lsm-tree" rel="nofollow" target="_blank"&gt;https://github.com/yfractal/lsm-tree&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="Why?"&gt;Why?&lt;/h2&gt;
&lt;p&gt;Log Structured Merge Tress (LSM-trees) 被很多的数据库所使用，比如 BigTable, LevelDB, RocksDB, Apach Cassandra, HBase 等，是一个非常重要的数据结构。&lt;/p&gt;

&lt;p&gt;数据库会有各种各样的适用场景，比如经常会听到 Cassandra 适合高写入。再比如 RocksDB 可能出现卡死的情况 [2]。&lt;/p&gt;

&lt;p&gt;这些都是由实现所决定的。当了解了实现，就可以更好的使用和理解问题背后的原因。&lt;/p&gt;

&lt;p&gt;所以我用 Ruby 写了一个非常简单的实现 [1]。&lt;/p&gt;
&lt;h2 id="Current State &amp;amp; Future Plan"&gt;Current State &amp;amp; Future Plan&lt;/h2&gt;
&lt;p&gt;目前实现了 Tired compacting Log-Structured Merge Tree。可以用来了解 LSM-Tree 的数据结构，以及如何做 compaction。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/yfractal/69a13696-b2df-4e95-8439-76d4b9deb049.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;如果时间允许的话，后续会实现 crash recovery 以及 range query。&lt;/p&gt;

&lt;p&gt;优化方面，也有很多东西做尝试，比如使用 mmap 做文件到磁盘的映射，尝试如何用 Ruby 做并发/并行，参数调优，优化 Bloom Filter 的使用 [4]。&lt;/p&gt;
&lt;h2 id="Other Learning Resource"&gt;Other Learning Resource&lt;/h2&gt;
&lt;p&gt;LSM trees (Log Structured Merge Trees) - Detailed video[5] 可以用来快速了解 LSM-Tree 如何工作。&lt;/p&gt;

&lt;p&gt;cs265-lsm-tree[3] 使用 C++ 实现，里面的文档对实现有详细的描述。&lt;/p&gt;

&lt;p&gt;如果想对 LSM-Tree 有更深入的理解，也可以参考这两篇 [6][7] 论文。&lt;/p&gt;
&lt;h2 id="References"&gt;References&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/yfractal/lsm-tree" rel="nofollow" target="_blank"&gt;https://github.com/yfractal/lsm-tree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;RocksDB compaction caused lievelock
  &lt;a href="https://twitter.com/otbzi/status/1315616340082790400" rel="nofollow" target="_blank"&gt;https://twitter.com/otbzi/status/1315616340082790400&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jackdent/cs265-lsm-tree" rel="nofollow" target="_blank"&gt;https://github.com/jackdent/cs265-lsm-tree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;NIV DAYAN. Optimal Bloom Filters and Adaptive Merging For LSM-Trees. 2018&lt;/li&gt;
&lt;li&gt;LSM trees (Log Structured Merge Trees) - Detailed video
  &lt;code&gt;https://www.youtube.com/watch?v=oUNjDHYFES8&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Siying Dong. Optimizing Space Amplification in RocksDB. 2017&lt;/li&gt;
&lt;li&gt;Patrick O'Neil. The Log-Structured Merge-Tree (LSM-Tree). 1996&lt;/li&gt;
&lt;/ol&gt;</description>
      <author>yfractal</author>
      <pubDate>Sun, 01 May 2022 16:18:40 +0800</pubDate>
      <link>https://ruby-china.org/topics/42363</link>
      <guid>https://ruby-china.org/topics/42363</guid>
    </item>
    <item>
      <title>not in 踩坑记录</title>
      <description>&lt;p&gt;在做一些类似黑名单的功能时，会使用 not in 来过滤。比如：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;//&lt;/span&gt; &lt;span class="err"&gt;或者&lt;/span&gt;

&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;blacklist&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是我们经常忽略一个前提条件，这两个查询能够满足需求的前提是：&lt;code&gt;in&lt;/code&gt;后面的返回值列表不包含&lt;code&gt;NULL&lt;/code&gt;；&lt;/p&gt;

&lt;p&gt;那么现在来看一下各种情况&lt;code&gt;not in&lt;/code&gt;的返回值：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; 
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;"1 not in (2,3)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;"1 not in (1,3)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;"1 not in (1, null)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;"1 not in (2, null)"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

 &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;----------------+----------------+--------------------+--------------------&lt;/span&gt;
 &lt;span class="n"&gt;t&lt;/span&gt;              &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;              &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;                  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前三个都符合预期，下面来看第四种情况：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
 &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="k"&gt;column&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
&lt;span class="c1"&gt;----------&lt;/span&gt;
 &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查了一下资料，返回&lt;code&gt;NULL&lt;/code&gt;的原因是：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;1 not in (2, null)&lt;/code&gt;被翻译成了 &lt;code&gt;1 != 2 and 1 != null&lt;/code&gt; =&amp;gt; &lt;code&gt;true and null&lt;/code&gt; =&amp;gt; &lt;code&gt;null&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;所以，在 not in 相关查询里一定要注意 in 后面是不是有可能包含 NULL 值，否则结果可能不符合预期。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt; 
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;blacklist&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ref：&lt;a href="https://ossdaily.com/hooopo/not-in-cai-keng-ji-lu-hpa" rel="nofollow" target="_blank"&gt;https://ossdaily.com/hooopo/not-in-cai-keng-ji-lu-hpa&lt;/a&gt;&lt;/p&gt;</description>
      <author>hooopo</author>
      <pubDate>Mon, 14 Feb 2022 09:02:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/42134</link>
      <guid>https://ruby-china.org/topics/42134</guid>
    </item>
    <item>
      <title>面试官问，Redis 是单线程还是多线程？我懵了</title>
      <description>&lt;p&gt;我们平时看到介绍 Redis 的文章，都会说 Redis 是单线程的。但是我们学习的时候，比如 Redis 的 bgsave 命令，它的作用是在后台异步保存当前数据库的数据到磁盘，那既然是异步了，肯定是由别的线程去完成的，这怎么还能说 Redis 是单线程的呢？&lt;/p&gt;

&lt;p&gt;其实通常说的 Redis 是单线程，主要是指 Redis 对外提供键值存储服务的主要流程，即网络 IO 和键值对读写是由⼀个线程来完成的。除此外 Redis 的其他功能，比如持久化、异步删除、集群数据同步等，是由额外的线程执⾏的。在这一点上 Node 也是一样的，一般提到 Node 也是单线程的，但其实 Node 只有一个主线程是单线程，其他异步任务则由其他线程完成。这样做的原因是防止有同步代码阻塞，导致主线程被占用后影响后续的程序代码执行。&lt;/p&gt;

&lt;p&gt;因此，严格地说 Redis 并不是单线程。但是我们⼀般把 Redis 称为单线程高性能，这样显得 Redis 更强一些。&lt;/p&gt;
&lt;h2 id="Redis 为什么用单线程"&gt;Redis 为什么用单线程&lt;/h2&gt;
&lt;p&gt;Redis 为什么用单线程？在回答这个问题前，先来看大家都很熟悉的数据库 MySQL，它使用的就是多线程。MySQL 不会每有一个连接就创建一个线程，因为线程过多会带来额外的开销，其中包括创建销毁线程的开销、调度线程的开销等，同时也会降低计算机的整体性能。这个正是多线程会遇到的难点。&lt;/p&gt;

&lt;p&gt;此外多线程系统中通常会存在被多线程同时访问的共享资源，比如一个共享的数据结构，当有多个进程要修改这个共享资源时，为了保证共享资源的正确性，就需要有额外的机制进行保证，而这个额外的机制，也会带来额外的开销。还是以 MySQL 举例，MySQL 引入了锁机制来解决这个问题。&lt;/p&gt;

&lt;p&gt;从上面不难看出，多线程开发中并发访问控制是⼀个难点，需要精细的设计才能处理。如果只是简单地处理，比如简单地采⽤⼀个粗粒度互斥锁，只会出现不理想的结果。即便增加了线程，系统吞吐率也不会随着线程的增加而增加，因为大部分线程还在等待获取访问共享资源的互斥锁。而且，大部分采用多线程开发引入的同步原语保护共享资源的并发访问，也会降低系统代码的易调试性和可维护性。&lt;/p&gt;

&lt;p&gt;而正是以上这些问题，才让 Redis 采⽤了单线程模式。&lt;/p&gt;

&lt;p&gt;看到这里大家可能有点疑惑，前面说了 Redis 不是单线程，现在我们也说了 Redis 的键值对读写操作使用采用了单线程模式，那么它的其他线程是是什么样的呢？&lt;/p&gt;
&lt;h2 id="主进程的其它线程"&gt;主进程的其它线程&lt;/h2&gt;
&lt;p&gt;Redis 3.0 版本后，主进程中除了主线程处理网络 IO 和命令操作外，还有 3 个辅助 BIO 线程。这 3 个 BIO 线程分别负责处理，文件关闭、AOF 缓冲数据刷新到磁盘，以及清理对象这三个任务队列，从而避免这些任务对主 IO 线程的影响。&lt;/p&gt;

&lt;p&gt;Redis 在启动时，会同时启动这三个 BIO 线程，但是 BIO 线程只有在需要执行相关类型后台任务时才会唤醒，其他时间会休眠等待任务。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/7bc34e8c-86f9-4d88-9cea-84cedf7bb518.png!large" title="" alt=""&gt;&lt;/p&gt;
&lt;h2 id="多进程"&gt;多进程&lt;/h2&gt;
&lt;p&gt;除了主进程，在以下场景如果需要进行重负荷任务的处理，Redis 会 fork 一个子进程来处理：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;收到 bgrewriteaof 命令：&lt;/strong&gt; Redis fork 一个子进程，然后子进程往临时 AOF 文件中写入重建数据库状态的所有命令。写入完毕后，子进程会通知父进程把新增的写操作追加到临时 AOF 文件。最后将临时文件替换旧的 AOF 文件，并重命名。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;收到 bgsave 命令：&lt;/strong&gt; Redis 构建子进程，子进程将内存中的所有数据通过快照做一次持久化落地，写入到 RDB 中。&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;当需要进行全量复制：&lt;/strong&gt; master 启动一个子进程，子进程将数据库快照保存到 RDB 文件。在写完 RDB 快照文件后，master 会把 RDB 发给 slave，同时将后续新的写指令都同步给 slave。&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="Redis6.0 多线程"&gt;Redis6.0 多线程&lt;/h2&gt;
&lt;p&gt;多线程是 Redis6.0 推出的一个新特性。正如上面所说 Redis 是核心线程负责网络 IO，命令处理以及写数据到缓冲，而随着网络硬件的性能提升，单个主线程处理⽹络请求的速度跟不上底层⽹络硬件的速度，导致网络 IO 的处理成为了 Redis 的性能瓶颈。&lt;/p&gt;

&lt;p&gt;而 Redis6.0 就是从单线程处理网络请求到多线程处理，通过多个 IO 线程并⾏处理网络操作提升实例的整体处理性能。需要注意的是对于读写命令，Redis 仍然使⽤单线程来处理，这是因为继续使⽤单线程执行命令操作，就不⽤为了保证 Lua 脚本、事务的原⼦性，额外开发多线程互斥机制了。&lt;/p&gt;

&lt;p&gt;需要注意的是在 Redis6.0 中，多线程机制默认是关闭的，需要在 redis.conf 中完成以下两个设置才能启用多线程。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;设置 io-thread-do-reads 配置项为 yes，表示启用多线程。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;io-threads-do-reads yes
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;设置线程个数。⼀般来说，&lt;strong&gt;线程个数要小于 Redis 实例所在机器的 CPU 核数，&lt;/strong&gt; 例如，对于⼀个 8 核的机器来说，Redis 官⽅建议配置 6 个 IO 线程。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;io-threads 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;多线程流程&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;来具体看一下在 Redis6.0 中，主线程和 IO 线程是如何协作完成请求处理的。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/upyun/ee4e83fb-3238-45c2-b00e-0e43d22dcb42.png!large" title="" alt="整体流程示意图"&gt;&lt;/p&gt;

&lt;p&gt;全部流程分为以下 4 阶段：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;阶段一：服务端和客⼾端建立 Socket 连接，并分配处理线程&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;当有客⼾端请求和实例建立 Socket 连接时，主线程会创建和客户端的连接，并把 Socket 放入全局等待队列中。然后主线程通过轮询方法把 Socket 连接分配给 IO 线程。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;阶段二：IO 线程读取并解析请求&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;主线程把 Socket 分配给 IO 线程后，会进⼊阻塞状态等待 IO 线程完成客户端请求读取和解析。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;阶段三：主线程执⾏请求操作&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;IO 线程解析完请求后，主线程以单线程的⽅式执⾏这些命令操作。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;阶段四：IO 线程回写 Socket 和主线程清空全局队&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;主线程执行完请求操作后，会把需要返回的结果写入缓冲区。然后，主线程会阻塞等待 IO 线程把这些结果回写到 Socket 中，并返回给客户端。等到 IO 线程回写 Socket 完毕，主线程会清空全局队列，等待客户端的后续请求。&lt;/p&gt;
&lt;h2 id="总结"&gt;总结&lt;/h2&gt;
&lt;p&gt;看完了这篇文章，相信大家对 Redis 是单线程的说法已经有了大致概念。我们说它是单线程，主要是因为在以前的版本中网络 IO 和键值对读写是由⼀个线程来完成的。而之所以说 Redis 是多线程，则是因为 Redis6.0 以后的版本里，网络 IO 的部分变为了多线程处理。而且除了主线程，还有 3 个辅助 BIO 线程，分别是 fsync 线程、close 线程、清理回收线程。当然不能忘记的是，想要体验多线程机制，就得通过修改配置文件开启多线程功能。&lt;/p&gt;
&lt;h3 id="推荐阅读"&gt;推荐阅读&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.upyun.com/tech/article/684/%E5%8E%9F%E5%88%9B%E5%86%85%E5%AE%B9%E5%B1%A1%E5%B1%A1%E8%A2%AB%E7%9B%97%EF%BC%9F%E4%BB%8E%E6%BA%90%E5%A4%B4%E5%AF%B9%E8%B5%84%E6%BA%90%E7%9B%97%E7%94%A8%E8%AF%B4NO.html" rel="nofollow" target="_blank" title=""&gt;原创内容屡屡被盗？从源头对资源盗用说 NO&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.upyun.com/tech/article/685/%E4%B8%A5%E9%87%8D%E5%8D%B1%E5%AE%B3%E8%AD%A6%E5%91%8A%EF%BC%81Log4j%20%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E8%A2%AB%E5%85%AC%E5%BC%80%EF%BC%81.html" rel="nofollow" target="_blank" title=""&gt;严重危害警告！Log4j 执行漏洞被公开！&lt;/a&gt;&lt;/p&gt;</description>
      <author>upyun</author>
      <pubDate>Thu, 13 Jan 2022 11:01:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/42075</link>
      <guid>https://ruby-china.org/topics/42075</guid>
    </item>
    <item>
      <title>新东西，不可变数据库 immudb</title>
      <description>&lt;p&gt;这个可能对 CQRS 和 event sourcing 那个有用吧。&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.immudb.io/master/" rel="nofollow" target="_blank" title=""&gt;link&lt;/a&gt;&lt;/p&gt;</description>
      <author>chenge</author>
      <pubDate>Thu, 16 Dec 2021 08:24:45 +0800</pubDate>
      <link>https://ruby-china.org/topics/41988</link>
      <guid>https://ruby-china.org/topics/41988</guid>
    </item>
    <item>
      <title>Can't Connect to MySQL Server on IP Address (10061) 错误的解决方案</title>
      <description>&lt;p&gt;&lt;img src="https://kalacloud.com/static/ec10753d046e32ab397d2bda4155b339/ef245/head.jpg" title="" alt="Can't Connect to MySQL Server on IP Address (10061) 错误的解决方案"&gt;&lt;/p&gt;

&lt;p&gt;如果你打算从&lt;a href="https://kalacloud.com/blog/how-to-allow-remote-access-to-mysql/" rel="nofollow" target="_blank" title=""&gt;远程连接 MySQL 服务器&lt;/a&gt;的话，有可能会碰到 10061 错误，这个错误特别常见，通常的错误提示是「Driver Error, Can’t connect to MySQL server on‘YOUR_IP_ADDRESS’ (10061)」&lt;/p&gt;

&lt;p&gt;导致 10061 这个错误的情况有两种&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  登录账号远程访问权限问题&lt;/li&gt;
&lt;li&gt;  MySQL 配置文件设置问题&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;本教程将详细讲解，如何针对这两种情况进行配置，以修正 10061 错误。&lt;/p&gt;

&lt;p&gt;特别提示：关于如何打开 MySQL 远程访问功能，可看这篇《&lt;a href="https://kalacloud.com/blog/how-to-allow-remote-access-to-mysql/" rel="nofollow" target="_blank" title=""&gt;如何远程连接 MySQL 数据库，阿里云腾讯云外网连接教程&lt;/a&gt;》，如果想开启服务器可查看这份教程，本篇教程只讲开启后，为什么会出现 10061 错误。另外推荐一下卡拉云，只要你能写 SQL，不会任何前端也可以用&lt;a href="https://kalacloud.com/" rel="nofollow" target="_blank" title=""&gt;卡拉云&lt;/a&gt;快速搭建属于自己的后台管理系统，详见本文文末&lt;/p&gt;
&lt;h2 id="一. 授权登录 MySQL 服务器的账号远程访问权限"&gt;一。授权登录 MySQL 服务器的账号远程访问权限&lt;/h2&gt;
&lt;p&gt;如果账号没有远程访问权限或 host 配置错误，会导致 10061 错误。我们可以新建一个账号用于远程登录，也可以修改已有账号的 host 配置，使它可以远程访问。&lt;/p&gt;
&lt;h3 id="1. 新建用于远程登录的 MySQL 账号"&gt;1. 新建用于远程登录的 MySQL 账号&lt;/h3&gt;
&lt;p&gt;MySQL 用户账号是否可以远程登录，取决于账号中的&amp;nbsp;&lt;strong&gt;host&lt;/strong&gt;&amp;nbsp;配置。host 指定该账号在哪些主机上可以登录，如果是本地用户可用&amp;nbsp;&lt;strong&gt;localhost，&lt;/strong&gt; 如果是远程用户，需要指定远程计算机的 IP，如果想任意主机均可登录，那么可以使用通配符&amp;nbsp;%&lt;/p&gt;

&lt;p&gt;本教程使用通配符 % 来作为账号 host 的设置，你可以根据自己的情况将 % 改为指定主机 IP，这样可以是 MySQL 远程登录更加安全。&lt;/p&gt;

&lt;p&gt;首先登录 MySQL Server&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql -u root -p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后新建一个用于远程登录的 MySQL 账号，这里的「password」换成你的密码，如果 MySQL 设置为严格密码的话，需要「数字 + 英文大小写 + 符号」&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE USER 'kalacloud.com-remote'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着，根据自己的需要，给你用于远程访问的账号赋予权限。下面的例子是给账号全局权限，包括创建（&lt;code&gt;CREATE&lt;/code&gt;）、修改（&lt;code&gt;ALTER&lt;/code&gt;）、删除（&lt;code&gt;DROP&lt;/code&gt;）数据库、表、用户，任意表的插入（&lt;code&gt;INSERT&lt;/code&gt;）、更新（&lt;code&gt;UPDATE&lt;/code&gt;）、删除（&lt;code&gt;DELETE&lt;/code&gt;）操作权限。可以使用&amp;nbsp;&lt;code&gt;SELECT&lt;/code&gt;&amp;nbsp;查询数据，使用&amp;nbsp;&lt;code&gt;REFERENCES&lt;/code&gt;&amp;nbsp;建立外键关系权限，以及使用&amp;nbsp;&lt;code&gt;RELOAD&lt;/code&gt;&amp;nbsp;权限执行&amp;nbsp;&lt;code&gt;FLUSH&lt;/code&gt;&amp;nbsp;操作的权限。&lt;/p&gt;

&lt;p&gt;当然，你也可以根据自己都需求，对账号权限进行调整。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GRANT CREATE, ALTER, DROP, INSERT, UPDATE, DELETE, SELECT, REFERENCES, RELOAD on *.* TO 'kalacloud.com-remote'@'%' WITH GRANT OPTION;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后，运行 FLUSH PRIVILEGES 命令，刷新 MySQL 的系统权限相关表，更新缓存。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FLUSH PRIVILEGES;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;扩展阅读：&lt;a href="https://kalacloud.com/blog/best-mysql-gui-tools/" rel="nofollow" target="_blank" title=""&gt;最好用的 10 款 MySQL / MariaDB 管理工具横向测评&lt;/a&gt;&amp;nbsp;- 免费和付费到底怎么选？&lt;/p&gt;
&lt;h3 id="2. 将已有账号更改为可远程登录的账号"&gt;2. 将已有账号更改为可远程登录的账号&lt;/h3&gt;
&lt;p&gt;如果你不想新建账号，只是想沿用已经有的 MySQL 登录账号，那么我们也可以直接把它改为可远程登录的账号。&lt;/p&gt;

&lt;p&gt;首先，我们&lt;strong&gt;查看 MySQL 所有账号&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT DISTINCT CONCAT('User: ''',user,'''@''',host,''';') AS query FROM mysql.user;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/ccb3b9785d374e6f46bf20c1b0baf19f/c1b63/01-SELECT-DISTINCT-CONCAT.png" title="" alt="SELECT-DISTINCT-CONCAT"&gt;&lt;/p&gt;

&lt;p&gt;接下来，我们把&amp;nbsp;&lt;code&gt;'kalacloud.com'@'localhost'&lt;/code&gt;&amp;nbsp;这个账号改为可远程登录的账号。我们把这个账号 host 改为任意主机（%）或者是固定主机 ip。可以使用 RENAME USER 命令来实现：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RENAME USER 'kalacloud.com'@'localhost' TO 'kalacloud.com'@'%';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行后，再查看 MySQL 所有账号列表&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT DISTINCT CONCAT('User: ''',user,'''@''',host,''';') AS query FROM mysql.user;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/e6ce7b2ea4ec46507556e888b4316fcf/c1b63/02-kalacloud-users-mysql.png" title="" alt="kalacloud-users-mysql"&gt;&lt;/p&gt;

&lt;p&gt;我们可以看到&amp;nbsp;&lt;code&gt;'kalacloud.com'@'localhost'&lt;/code&gt;&amp;nbsp;已经变为&amp;nbsp;&lt;code&gt;'kalacloud.com'@'%'&lt;/code&gt;&amp;nbsp;了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;特别提示&lt;/strong&gt;：如果你使用的是 MySQL 8.0，使用这个账号远程登录可能会出现&lt;code&gt;caching_sha2_password&lt;/code&gt;&amp;nbsp;报错，这是因为 MySQL 8.0 默认认证方式改为&amp;nbsp;&lt;code&gt;SHA2&lt;/code&gt;&amp;nbsp;了，如果不支持&amp;nbsp;&lt;code&gt;SHA2&lt;/code&gt;&amp;nbsp;插件认证方式，那么就会报错，比如使用 Workbench 和 Squel Pro 登录时，如下图。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MySQL said: Authentication plugin 'caching_sha2_password' cannot be loaded: dlopen(/usr/local/lib/plugin/caching_sha2_password.so, 2): image not found
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/ece301011f40bdbe1a5fb718096b3d42/e5166/03-sequel-pro.jpg" title="" alt="sequel-pro"&gt;&lt;/p&gt;

&lt;p&gt;我们可以把使用&amp;nbsp;&lt;code&gt;caching_sha2_plugin&lt;/code&gt;&amp;nbsp;认证的&lt;code&gt;'kalacloud'@'%'&lt;/code&gt;账号改为使用密码认证。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ALTER USER 'kalacloud.com'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后运行&amp;nbsp;&lt;code&gt;FLUSH PRIVILEGES&lt;/code&gt;命令 刷新 MySQL 系统权限相关表，更新缓存&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FLUSH PRIVILEGES;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此，MySQL 远程登录账号部分就配置完成了，如果还是报 10061 错误，那么你还应该检查 MySQL 配置文件是否有问题。&lt;/p&gt;

&lt;p&gt;扩展阅读：&lt;a href="https://kalacloud.com/blog/mysql-workbench-tutorial/" rel="nofollow" target="_blank" title=""&gt;MySQL Workbench 中文使用指南&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="二. 改 MySQL 配置文件"&gt;二。改 MySQL 配置文件&lt;/h2&gt;
&lt;p&gt;在默认情况下，MySQL 数据库仅监听本地连接。如果想让外网远程连接到数据库，我们需要修改配置文件，让 MySQL 可以监听远程固定 ip 或者监听所有远程 ip。&lt;/p&gt;

&lt;p&gt;首先打开&amp;nbsp;&lt;code&gt;mysqld.cnf&lt;/code&gt;&amp;nbsp;配置文件。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;找到 bind - address 这一行，如下图所示。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/d44f35877e139986a0d7d50efb26a099/6ed34/04-mysqld.jpg" title="" alt="mysqld"&gt;&lt;/p&gt;

&lt;p&gt;默认情况下，bind - address 的值为 127.0.0.1，所以只能监听本地连接。我们需要将这个值改为远程连接 ip 可访问，可使用通配符 ip 地址&amp;nbsp;&lt;code&gt;*&lt;/code&gt;，&amp;nbsp;&lt;code&gt;::&lt;/code&gt;，&amp;nbsp;&lt;code&gt;0.0.0.0&lt;/code&gt;&amp;nbsp;，当然也可以是单独的固定 ip，这样就仅允许指定 ip 连接，更加安全。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/4b89be8dbd5e34ca7883d013eaa1de9f/6ed34/05-mysqld-bind-address.jpg" title="" alt="mysqld-bind-address"&gt;&lt;/p&gt;

&lt;p&gt;提示：在某些 MySQL 版本的配置文件中，没有 bind - address 这一行，这种情况下，在合适的位置加上就可以了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;提示：&lt;/strong&gt; 在某些 MySQL 版本的配置文件中，没有 bind - address 这一行，这种情况下，在合适的位置加上就可以了。&lt;/p&gt;

&lt;p&gt;更改后，保存并退出编辑器（使用 CTRL+X 保存并退出 nano 编辑器。）&lt;/p&gt;

&lt;p&gt;然后重启 MySQL 服务，使刚刚编辑的&amp;nbsp;&lt;code&gt;mysqld.cnf&lt;/code&gt;&amp;nbsp;文件生效：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl restart mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;扩展阅读：&lt;a href="https://kalacloud.com/blog/mysql-remove-duplicate-records/" rel="nofollow" target="_blank" title=""&gt;如何在 MySQL / MariaDB 中查找和删除重复记录？&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="三. 总结"&gt;三。总结&lt;/h2&gt;
&lt;p&gt;到这里，只要你跟着教程走，一定已经解决了 MySQL 10061 错误。&lt;/p&gt;

&lt;p&gt;MySQL 除了远程连接这类适合使用终端命令操作外，大多数对 MySQL / MariaDB 数据导入导出操作还是为了数据展示、分析、协同共享等产品和运营层面的应用场景。&lt;/p&gt;

&lt;p&gt;比如后端工程师接到产品需求，协助导出某类数据等场景，如果这类需求频繁出现，推荐使用我开发的卡拉云。卡拉云是新一代低代码开发工具，免安装部署，可一键接入包括 MySQL 在内的常见数据库及 API。&lt;/p&gt;

&lt;p&gt;卡拉云不仅可以像命令行一样灵活，还可根据自己的工作流，定制开发。无需繁琐的前端开发，只需要简单拖拽，即可快速搭建企业内部工具。&lt;strong&gt;数月的开发工作量，使用卡拉云后可缩减至数天，&lt;a href="https://kalacloud.com/" rel="nofollow" target="_blank" title=""&gt;欢迎免费试用卡拉云&lt;/a&gt;。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/18822b2a23183deb7d11dd484a7f65aa/71c1d/97-kalacloud-sql.png" title="" alt="卡拉云可快速接入的常见数据库及 API"&gt;&lt;/p&gt;

&lt;p&gt;卡拉云可快速接入的常见数据库及 API&lt;/p&gt;

&lt;p&gt;卡拉云可根据公司工作流需求，轻松搭建数据看板，并且可分享给组内的小伙伴共享数据&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/5400a60956e16d655e0297c5d6e5a8d2/98-kalacloud-gif.gif" title="" alt="仅需拖拽一键生成前端代码"&gt;&lt;/p&gt;

&lt;p&gt;仅需拖拽一键生成前端代码，简单一行代码即可映射数据到指定组件中。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/34625d3adaea4ed250ff3f05b863e47c/cca35/99-kalacloud-sql-index.png" title="" alt="卡拉云快速搭建企业内部工具"&gt;&lt;/p&gt;

&lt;p&gt;卡拉云可直接添加导出按钮，导出适用于各类分析软件的数据格式，方便快捷。&lt;a href="https://kalacloud.com/?utm_medium=register" rel="nofollow" target="_blank" title=""&gt;立即开通卡拉云&lt;/a&gt;，搭建属于自己的后台管理工具。&lt;/p&gt;

&lt;p&gt;如果觉得我的文章对你有帮助，还请点个赞再走。欢迎评论区一起交流。 &lt;/p&gt;</description>
      <author>HiJiangChuan</author>
      <pubDate>Fri, 19 Nov 2021 19:10:58 +0800</pubDate>
      <link>https://ruby-china.org/topics/41899</link>
      <guid>https://ruby-china.org/topics/41899</guid>
    </item>
    <item>
      <title>如何使用 MySQL 慢查询日志进行性能优化 - Profiling、mysqldumpslow 实例详解</title>
      <description>&lt;p&gt;&lt;img src="https://kalacloud.com/static/890db2f8674d38638465902fd2273979/ef245/head.jpg" title="" alt="使用 MySQL 慢查询日志进行性能优化"&gt;&lt;/p&gt;

&lt;p&gt;当我们开始关注数据库整体性能优化时，我们需要一套 MySQL 查询分析工具。特别是在开发中大型项目时，往往有数百个查询分布在代码库中的各个角落，并实时对数据库进行大量访问和查询。如果没有一套趁手的分析方法和工具，就很难发现在执行过程中代码的效率瓶颈，我们需要通过这套工具去定位 SQL 语句在执行中缓慢的问题和原因。&lt;/p&gt;

&lt;p&gt;本教程带领大家学习和实践 MySQL Server 内置的查询分析工具 —— 慢查询日志、&lt;code&gt;mysqldumpslow&lt;/code&gt;、&lt;code&gt;Profiling&lt;/code&gt;，详细讲解如何使用他们提升代码执行效率。如果你想根据自己的工作流开发一套数据库查询管理工具，推荐使用卡拉云。只要你会写 SQL，无需会前端也可以轻松搭建属于自己的后台查询工具，详见本文文末。&lt;/p&gt;
&lt;h2 id="一. 有关 MySQL 慢查询日志"&gt;一。有关 MySQL 慢查询日志&lt;/h2&gt;&lt;h3 id="1.慢查询日志是什么？"&gt;1.慢查询日志是什么？&lt;/h3&gt;
&lt;p&gt;MySQL 慢查询日志是用来记录 MySQL 在执行命令中，响应时间超过预设阈值的 SQL 语句。&lt;/p&gt;

&lt;p&gt;记录这些执行缓慢的 SQL 语句是优化 MySQL 数据库效率的第一步。&lt;/p&gt;

&lt;p&gt;默认情况下，慢查询日志功能是关闭的，需要我们手动打开。当然，如果不是调优需求的话，一般也不建议长期启动这个功能，因为开启慢查询多少会对数据库的性能带来一些影响。慢查询日志支持将记录写入文件，当然也可以直接写入数据库的表中。&lt;/p&gt;
&lt;h3 id="2.配置并打开慢查询日志"&gt;2.配置并打开慢查询日志&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;（1）在 MySQL Server 中临时开启慢查询功能&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;在 MySQL Server 中，默认情况慢查询功能是关闭的，我们可以通过查看此功能的状态&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show variables like 'slow_query_log'; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/00eee2e5cee9c8afcd9b51d53d1b5342/350de/01-slow-query-log.png" title="" alt="slow_query_log"&gt;&lt;/p&gt;

&lt;p&gt;如上图所示，慢查询日志（slow_query_log）的状态为关闭。&lt;/p&gt;

&lt;p&gt;我们可以使用以下命令开启并配置慢查询日志功能，&lt;strong&gt;在 mysql 中执行以下命令&lt;/strong&gt;：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/kalacloud-slow.log';
SET GLOBAL log_queries_not_using_indexes = 'ON';
SET SESSION long_query_time = 1;
SET SESSION min_examined_row_limit = 100;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;SET GLOBAL slow_query_log&lt;/code&gt;&amp;nbsp;：全局开启慢查询功能。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SET GLOBAL slow_query_log_file&lt;/code&gt;&amp;nbsp;：指定慢查询日志存储文件的地址和文件名。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SET GLOBAL log_queries_not_using_indexes&lt;/code&gt;：无论是否超时，未被索引的记录也会记录下来。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SET SESSION long_query_time&lt;/code&gt;：慢查询阈值（秒），SQL 执行超过这个阈值将被记录在日志中。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SET SESSION min_examined_row_limit&lt;/code&gt;：慢查询仅记录扫描行数大于此参数的 SQL。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;特别注意：&lt;/strong&gt;在实践中常常会碰到无论慢查询阈值调到多小，日志就是不被记录。这个问题很有可能是&amp;nbsp;&lt;code&gt;min_examined_row_limit&lt;/code&gt;&amp;nbsp;行数过大，导致没有被记录。&lt;code&gt;min_examined_row_limit&lt;/code&gt;&amp;nbsp;在配置中常被忽略，这里要特别注意。&lt;/p&gt;

&lt;p&gt;接着我们来执行查询语句，看看配置。（在 MySQL Server 中执行）&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show variables like 'slow_query_log%';
show variables like 'log_queries_not_using_indexes';
show variables like 'long_query_time';
show variables like 'min_examined_row_limit';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/40193134129de28aa2cb7e366f3e1ed4/5f652/02-show-variables-like.png" title="" alt="show-variables-like"&gt;&lt;/p&gt;

&lt;p&gt;以上修改 MySQL 慢查询配置的方法是用在&lt;strong&gt;临时监测数据库运行状态&lt;/strong&gt;的场景下，当 MySQL Server 重启时，以上修改全部失效并恢复原状。&lt;/p&gt;

&lt;p&gt;扩展阅读：&lt;a href="https://kalacloud.com/blog/how-to-manage-and-use-mysql-database-triggers/" rel="nofollow" target="_blank" title=""&gt;六类 MySQL 触发器使用教程及应用场景实战案例&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;（2）将慢查询设置写入 MySQL 配置文件，永久生效&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;虽然我们可以在命令行中对慢查询进行动态设置，但动态设置会随着重启服务而失效。如果想长期开启慢查询功能，需要把慢查询的设置写入 MySQL 配置文件中，这样无论是重启服务器，还是重启 MySQL，慢查询的设置都会保持不变。&lt;/p&gt;

&lt;p&gt;MySQL conf 配置文件通常在&amp;nbsp;&lt;code&gt;/etc&lt;/code&gt;&amp;nbsp;或&amp;nbsp;&lt;code&gt;/usr&lt;/code&gt;&amp;nbsp;中。我们可以使用&amp;nbsp;&lt;code&gt;find&lt;/code&gt;&amp;nbsp;命令找到配置文件具体的存放位置。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo find /etc -name my.cnf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/3953e9d1658e1cb75f5cb77d3ec276eb/99f37/03-sudo-find.png" title="" alt="sudo-find"&gt;&lt;/p&gt;

&lt;p&gt;找到位置后，使用&amp;nbsp;&lt;code&gt;nano&lt;/code&gt;&amp;nbsp;编辑&amp;nbsp;&lt;code&gt;my.cnf&lt;/code&gt;&amp;nbsp;将慢查询设置写入配置文件。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo nano /etc/mysql/my.cnf
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[mysqld]

slow-query-log = 1
slow-query-log-file = /var/log/mysql/localhost-slow.log
long_query_time = 1
log-queries-not-using-indexes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用&amp;nbsp;&lt;code&gt;nano&lt;/code&gt;&amp;nbsp;打开配置文件，把上面的的代码写在&amp;nbsp;&lt;code&gt;[mysqld]&lt;/code&gt;&amp;nbsp;的下面即可。&amp;nbsp;&lt;code&gt;ctrl+X&lt;/code&gt;&amp;nbsp;保存退出。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo systemctl restart mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;重启 MySQL Server 服务，使刚刚修改的配置文件生效。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;特别注意：&lt;/strong&gt;直接在命令行中设置的慢查询动态变量与直接写入 my.cnf 配置文件的语法有所不同。&lt;/p&gt;

&lt;p&gt;扩展阅读：10 种&amp;nbsp;&lt;a href="https://kalacloud.com/blog/best-mysql-gui-tools/" rel="nofollow" target="_blank" title=""&gt;MySQL 管理工具&lt;/a&gt;&amp;nbsp;横向测评 - 免费和付费到底怎么选？&lt;/p&gt;

&lt;p&gt;举例：动态变量是&lt;code&gt;slow_query_log&lt;/code&gt;，写入配置文件是&lt;code&gt;slow-query-log&lt;/code&gt;。这里要特别注意。&lt;/p&gt;

&lt;p&gt;更多 MySQL 8.0 动态变量语法可查看&amp;nbsp;&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/dynamic-system-variables.html" rel="nofollow" target="_blank" title=""&gt;MySQL 官方文档&lt;/a&gt;。&lt;/p&gt;
&lt;h2 id="二. 使用慢查询功能记录日志"&gt;二。使用慢查询功能记录日志&lt;/h2&gt;
&lt;p&gt;到这里我们已经配置好慢查询功能所需要的一切。下面咱们写一个示例，在这个示例中我们来一起学习如何查看和分析慢查询日志。&lt;/p&gt;

&lt;p&gt;你可以打开两个连接到服务器的命令行窗口，一个用来写 MySQL 代码，另一个用来查看日志。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注意：以下教程中，有些代码是在命令行中执行，有些是在 MySQL Server 中执行，请注意分辨。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;登录 MySQL Server，创建一个数据库，写入一组示例数据。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE DATABASE kalacloud_demo;
USE kalacloud_demo;
CREATE TABLE users ( id TINYINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255) );
INSERT INTO users (name) VALUES ('Jack Ma'),('Lei Jun'),('Wang Xing'),('Pony Ma'),('Zhang YiMing'),('Ding Lei'),('Robin Li'),('Xu Yong'),('Huang Zheng'),('Richard Liu');
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;为了保证大家与教程配置保持一致，咱们一起使用动态变量，再设置一边慢查询参数。&lt;/p&gt;

&lt;p&gt;在 MySQL Server 中执行以下 SQL 代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET GLOBAL slow_query_log = 1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/kalacloud-slow.log';
SET GLOBAL log_queries_not_using_indexes = 1;
SET long_query_time = 10;
SET min_examined_row_limit = 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在我们有了一个表中有数据的示例数据库。慢查询功能也已经打开，我们特意把时间阈值（long_query_time）设置为 10 并且把最小行（min_examined_row_limit）设置为 0。&lt;/p&gt;

&lt;p&gt;接着我们来运行一段代码测试一下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USE kalacloud_demo;
SELECT * FROM users WHERE id = 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;使用主键索引对表进行&amp;nbsp;&lt;code&gt;select&lt;/code&gt;&amp;nbsp;查询，这种查询速度非常快，又使用了索引。因此慢查询日志中不会有任何记录。&lt;/p&gt;

&lt;p&gt;我们打开慢查询日志，验证一下是否有记录，在命令行中执行以下命令：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo cat /var/log/mysql/kalacloud-slow.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到&lt;code&gt;kalacloud-slow.log&lt;/code&gt;还没有任何记录。
&lt;img src="https://kalacloud.com/static/415badf1b06c670bdfffe14ff8ccdd56/5fc9a/04-kalacloud-slow-log.png" title="" alt="kalacloud-slow-log"&gt;&lt;/p&gt;

&lt;p&gt;接着我们在 MySQL Server 中执行以下代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM users WHERE name = 'Wang Xing';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这段查询代码使用非索引列（name）来进行查询，所以慢查询日志在会记录下这个查询。&lt;/p&gt;

&lt;p&gt;我们打开日志查看记录变化：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo cat /var/log/mysql/kalacloud-slow.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/9731bcba62083712ead0b5c4d12fc322/5fc9a/05-cat.png" title="" alt="通过 cat 查看 log"&gt;&lt;/p&gt;

&lt;p&gt;我们可以看到这个非索引查询，已经被记录在慢查询日志中了。&lt;/p&gt;

&lt;p&gt;再举个例子。我们提高最小检查行（min_examined_row_limit）的检查行数设置为 100，然后再执行查询。&lt;/p&gt;

&lt;p&gt;在 MySQL Server 中执行以下代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET min_examined_row_limit = 100;
SELECT * FROM users WHERE name = 'Zhang YiMing';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行后，再打开&amp;nbsp;&lt;code&gt;kalacloud-slow.log&lt;/code&gt;&amp;nbsp;，可以看到条小于&amp;nbsp;&lt;code&gt;100&lt;/code&gt;&amp;nbsp;行的查询，没有被记录到日志中。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;特别注意&lt;/strong&gt;：如果慢查询日志中，没有记录任何数据，可以检查以下内容。&lt;/p&gt;

&lt;p&gt;（1）创建日志的目录权限问题，是否有对应的权限。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd /var/log
mkdir mysql
chmod 755 mysql
chown mysql:mysql mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（2）另一个可能是查询变量配置问题，把&amp;nbsp;&lt;code&gt;my.conf&lt;/code&gt;&amp;nbsp;文件内有关慢查询的配置清干净，然后重启服务，重新配置。看看是不是这里出的问题。&lt;/p&gt;

&lt;p&gt;扩展阅读：&lt;a href="https://kalacloud.com/blog/how-to-save-mysql-mariadb-query-output-to-a-file/" rel="nofollow" target="_blank" title=""&gt;如何将 MySQL 的查询结果保存到文件&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="三. 慢查询日志记录参数详解"&gt;三。慢查询日志记录参数详解&lt;/h2&gt;
&lt;p&gt;接着我们来讲解慢查询日志应该如何分析&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/be6eaa33dcd8d251aa7050a378379582/5fc9a/06-cat-mysql.png" title="" alt="慢查询日志分析"&gt;&lt;/p&gt;

&lt;p&gt;日志中信息的说明：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;Time&lt;/code&gt;&amp;nbsp;：被日志记录的代码在服务器上的运行时间。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;User@Host&lt;/code&gt;：谁执行的这段代码。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Query_time&lt;/code&gt;：这段代码运行时长。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Lock_time&lt;/code&gt;：执行这段代码时，锁定了多久。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Rows_sent&lt;/code&gt;：慢查询返回的记录。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;Rows_examined&lt;/code&gt;：慢查询扫描过的行数。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这些被记录的信息非常有意义，所有超过阈值的代码都会被记录在日志中，我们可以通过这些信息找到 MySQL 查询时效率不佳的代码，有助于我们优化 MySQL 性能。&lt;/p&gt;

&lt;p&gt;扩展阅读：&lt;a href="https://kalacloud.com/blog/find-all-tables-with-specific-column-names-in-mysql/" rel="nofollow" target="_blank" title=""&gt;如何在 MySQL 里查询数据库中带有某个字段的所有表名&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="四. 使用 mysqldumpslow 工具对慢查询日志进行分析"&gt;四。使用 mysqldumpslow 工具对慢查询日志进行分析&lt;/h2&gt;
&lt;p&gt;实际工作中，慢查询日志可不像上文描述的那样，仅仅有几行记录。现实中慢查询日志会记录大量慢查询信息，写入也非常频繁。日志记录的内容会越来越长，分析数据也变的困难。好在 MySQL 内置了&amp;nbsp;&lt;code&gt;mysqldumpslow&lt;/code&gt;&amp;nbsp;工具，它可以把相同的 SQL 归为一类，并统计出归类项的执行次数和每次执行的耗时等一系列对应的情况。&lt;/p&gt;

&lt;p&gt;我们先来执行几行代码让慢查询日志记录下来，然后再用&amp;nbsp;&lt;code&gt;mysqldumpslow&lt;/code&gt;&amp;nbsp;进行分析。&lt;/p&gt;

&lt;p&gt;上文我们把&lt;code&gt;min_examined_row_limit&lt;/code&gt;&amp;nbsp;设置为 100，在这里，我们要将它改为 0，慢查询才能有记录。在 MySQL Server 中执行以下代码：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET min_examined_row_limit = 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着我们执行几条查询命令：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT * FROM users WHERE name = 'Wang Xing';
SELECT * FROM users WHERE name = 'Huang Zheng';
SELECT * FROM users WHERE name = 'Zhang YiMing';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;根据前文的慢查询设置，这三条记录都将被记录在日志中。&lt;/p&gt;

&lt;p&gt;现在，&lt;strong&gt;我们切换到命令行的窗口中&lt;/strong&gt;，执行&amp;nbsp;&lt;code&gt;mysqldumpslow&lt;/code&gt;&amp;nbsp;命令：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo mysqldumpslow -s at /var/log/mysql/kalacloud-slow.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回的数据：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/7d12184d72cf0935032f15ce0a97b277/b8bf8/07-mysqldumpslow.png" title="" alt="mysqldumpslow"&gt;&lt;/p&gt;

&lt;p&gt;我们可以看到，返回的数据中，已经把三条类似的 SQL 语句记录抽象成一条记录&lt;code&gt;SELECT * FROM users WHERE name = 'S'&lt;/code&gt;&amp;nbsp;并且针对这条记录列出了对应的总量和平均量的记录。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;常见的&lt;/strong&gt;&amp;nbsp;&lt;code&gt;mysqldumpslow&lt;/code&gt;&amp;nbsp;&lt;strong&gt;命令&lt;/strong&gt;&amp;nbsp;平时大家也可以根据自己的常用需求来总结，存好这些脚本备用。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;mysqldumpslow -s at -t 10 kalacloud-slow.log&lt;/code&gt;：平均执行时长最长的前 10 条 SQL 代码。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;mysqldumpslow -s al -t 10 kalacloud-slow.log&lt;/code&gt;：平均锁定时间最长的前 10 条 SQL 代码。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;mysqldumpslow -s c -t 10 kalacloud-slow.log&lt;/code&gt;：执行次数最多的前 10 条 SQL 代码。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;mysqldumpslow -a -g 'user' kalacloud-slow.log&lt;/code&gt;：显示所有&amp;nbsp;&lt;code&gt;user&lt;/code&gt;&amp;nbsp;表相关的 SQL 代码的具体值&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;mysqldumpslow -a kalacloud-slow.log&lt;/code&gt;：直接显示 SQL 代码的情况。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;mysqldumpslow&lt;/code&gt;&amp;nbsp;的参数命令&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Usage: mysqldumpslow [ OPTS... ] [ LOGS... ]

Parse and summarize the MySQL slow query log. Options are

  --verbose    verbose
  --debug      debug
  --help       write this text to standard output
  -v           verbose
  -d           debug
  -s ORDER     what to sort by (al, at, ar, c, l, r, t), 'at' is default
                al: average lock time
                ar: average rows sent
                at: average query time
                 c: count
                 l: lock time
                 r: rows sent
                 t: query time
  -r           reverse the sort order (largest last instead of first)
  -t NUM       just show the top n queries
  -a           don't abstract all numbers to N and strings to 'S'
  -n NUM       abstract numbers with at least n digits within names
  -g PATTERN   grep: only consider stmts that include this string
  -h HOSTNAME  hostname of db server for *-slow.log filename (can be wildcard),
               default is '*', i.e. match all
  -i NAME      name of server instance (if using mysql.server startup script)
  -l           don't subtract lock time from total time
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;常用的参数讲解：&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-s&lt;/code&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  al：平均锁定时间&lt;/li&gt;
&lt;li&gt;  at：平均查询时间&amp;nbsp;[默认]&lt;/li&gt;
&lt;li&gt;  ar：平均返回记录时间&lt;/li&gt;
&lt;li&gt;  c：count 总执行次数&lt;/li&gt;
&lt;li&gt;  l：锁定时间&lt;/li&gt;
&lt;li&gt;  r：返回记录&lt;/li&gt;
&lt;li&gt;  t：查询时间&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;-t&lt;/code&gt;：返回前 N 条的数据&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-g&lt;/code&gt;：可写正则表达，类似于 grep 命令，过滤出需要的信息。如，只查询 X 表的慢查询记录。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-r&lt;/code&gt;：rows sent 总返回行数。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mysqldumpslow&lt;/code&gt;&amp;nbsp;日志查询工具好用就好用在它特别灵活，又可以合并同类项式的分析慢查询日志。我们在日常工作的使用中，就能够体会&amp;nbsp;&lt;code&gt;mysqldumpslow&lt;/code&gt;&amp;nbsp;的好用之处。&lt;/p&gt;

&lt;p&gt;另外&amp;nbsp;&lt;code&gt;mysqldumpslow&lt;/code&gt;&amp;nbsp;的使用参数也可在&amp;nbsp;&lt;a href="https://dev.mysql.com/doc/refman/8.0/en/mysqldumpslow.html" rel="nofollow" target="_blank" title=""&gt;MySQL 8.0 使用手册&lt;/a&gt;&amp;nbsp;中找到。&lt;/p&gt;

&lt;p&gt;扩展阅读：&lt;a href="https://kalacloud.com/blog/how-to-get-the-sizes-of-the-tables-of-a-mysql-database/" rel="nofollow" target="_blank" title=""&gt;如何查看 MySQL 数据库、表、索引容量大小&lt;/a&gt;？找到占用空间最大的表&lt;/p&gt;
&lt;h2 id="五. Profilling - MySQL 性能分析工具"&gt;五。Profilling - MySQL 性能分析工具&lt;/h2&gt;
&lt;p&gt;为了更精准的定位一条 SQL 语句的性能问题，我们需要拆分这条语句运行时到底在什么地方消耗了多少资源。我们可以使用 Profilling 工具来进行这类细致的分析。我们可通过 Profilling 工具获取一条 SQL 语句在执行过程中对各种资源消耗的细节。&lt;/p&gt;

&lt;p&gt;进入 MySQL Server 后，执行以下代码，启动 Profilling&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SET SESSION profiling = 1; 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;检查 profiling 的状态&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT @@profiling;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;返回数据：0 表示未开启，1 表示已开启。
&lt;img src="https://kalacloud.com/static/92145f415c86b1b124d3cf4f9dca4542/d48f1/08-profiling.png" title="" alt="profiling"&gt;&lt;/p&gt;

&lt;p&gt;执行需要定位问题的 SQL 语句。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;USE kalacloud_demo;
SELECT * FROM users WHERE name = 'Jack Ma';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;查看 SQL 语句状态。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW PROFILES;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;打开 profiling 后，&lt;code&gt;SHOW PROFILES;&lt;/code&gt;&amp;nbsp;会显示一个将&amp;nbsp;&lt;code&gt;Query_ID&lt;/code&gt;&amp;nbsp;链接到 SQL 语句的表。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/473bd36358238e8ba1b548995cb08ec5/36eca/09-show-profiles.png" title="" alt="show-profiles"&gt;
&lt;code&gt;Query_ID&lt;/code&gt;：SQL 语句的 ID 编号。
&lt;code&gt;Duration&lt;/code&gt;：SQL 语句执行时长。
&lt;code&gt;Query&lt;/code&gt;：具体的 SQL 语句。&lt;/p&gt;

&lt;p&gt;执行以下 SQL 代码，将&amp;nbsp;&lt;code&gt;[# Query_ID]&lt;/code&gt;&amp;nbsp;替换为我们要分析的 SQL 代码&lt;code&gt;Query_ID&lt;/code&gt;的编号。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW PROFILE CPU, BLOCK IO FOR QUERY [# Query_ID];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;即&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHOW PROFILE CPU, BLOCK IO FOR QUERY 4;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/cdde8f424bc279ec82311770be4cf461/c1b63/10-show-profiles-all.png" title="" alt="show-profiles-all"&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Status&lt;/code&gt;&amp;nbsp;是执行查询过程中的具体步骤，&lt;code&gt;Duration&lt;/code&gt;&amp;nbsp;是完成该步骤所需的时间（以秒为单位）。&lt;/p&gt;

&lt;p&gt;我们可以根据这些细节来具体分析，如何优化对应的 SQL 代码。&lt;/p&gt;
&lt;h2 id="六. 慢查询教程总结"&gt;六。慢查询教程总结&lt;/h2&gt;
&lt;p&gt;慢查询是让我们看到数据库真实运行状态的工具，对服务器和数据库性能优化有着指导性的意义。无论是生产环境、开发、QA，都可以谨慎的打开慢查询来记录性能日志。&lt;/p&gt;

&lt;p&gt;我们可以先把动态变量&lt;code&gt;long_query_time&lt;/code&gt;&amp;nbsp;设置的大一些，观察一下，然后在进行微调。有了慢查询日志，我们就有了优化性能的方向和目标，再使用&amp;nbsp;&lt;code&gt;mysqldumpslow&lt;/code&gt;&amp;nbsp;和&amp;nbsp;&lt;code&gt;profiling&lt;/code&gt;&amp;nbsp;进行宏观和微观的日志分析。找到低效 SQL 语句的细节，进行微调，最终使我们的系统可以获得最佳执行性能。&lt;/p&gt;

&lt;p&gt;至此，MySQL 慢查询日志我们就讲解完了，如果你周期性的查看 log 日志，可以使用卡拉云搭一个日志看板，自己不仅查看、分析数据方便，还可以一键分享给组内的小伙伴共享数据。&lt;/p&gt;

&lt;p&gt;卡拉云是新一代低代码开发工具，免安装部署，可一键接入包括 MySQL 在内的常见数据库及 API。不仅可以像命令行一样灵活，还可根据自己的工作流，定制开发。无需繁琐的前端开发，只需要简单拖拽，即可快速搭建企业内部工具。&lt;strong&gt;数月的开发工作量，使用卡拉云后可缩减至数天&lt;/strong&gt;，欢迎使用我开发的&lt;a href="https://kalacloud.com/?utm_medium=register" rel="nofollow" target="_blank" title=""&gt;卡拉云&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/18822b2a23183deb7d11dd484a7f65aa/71c1d/97-kalacloud-sql.png" title="" alt="卡拉云可快速接入的常见数据库及 API"&gt;&lt;/p&gt;

&lt;p&gt;卡拉云可快速接入的常见数据库及 API&lt;/p&gt;

&lt;p&gt;卡拉云可根据公司工作流需求，轻松搭建数据看板，并且可分享给组内的小伙伴共享数据&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/5400a60956e16d655e0297c5d6e5a8d2/98-kalacloud-gif.gif" title="" alt="快速搭建企业内部工具"&gt;&lt;/p&gt;

&lt;p&gt;仅需拖拽一键生成前端代码，简单一行代码即可映射数据到指定组件中。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/34625d3adaea4ed250ff3f05b863e47c/cca35/99-kalacloud-sql-index.png" title="" alt="卡拉云搭建数据库看板"&gt;&lt;/p&gt;

&lt;p&gt;卡拉云可直接添加导出按钮，导出适用于各类分析软件的数据格式，方便快捷。立即开通&lt;a href="https://kalacloud.com/?utm_medium=register" rel="nofollow" target="_blank" title=""&gt;卡拉云&lt;/a&gt;，快速搭建属于你自己的后台管理系统。&lt;/p&gt;

&lt;p&gt;有关 MySQL 教程，可继续拓展学习：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://kalacloud.com/blog/how-to-allow-remote-access-to-mysql/" rel="nofollow" target="_blank" title=""&gt;如何远程连接 MySQL 数据库，阿里云腾讯云外网连接教程&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://kalacloud.com/blog/how-to-import-and-export-databases-excel-csv-in-mysql-or-mariadb-from-terminal/" rel="nofollow" target="_blank" title=""&gt;如何在 MySQL / MariaDB 中导入导出数据，导入导出数据库文件、Excel、CSV&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://kalacloud.com/blog/how-to-migrate-a-mysql-database-between-two-servers-aliyun-tencentyun/" rel="nofollow" target="_blank" title=""&gt;如何在两台服务器之间迁移 MySQL 数据库 阿里云腾讯云迁移案例&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://kalacloud.com/blog/how-to-use-the-mysql-blob-data-type-to-store-images-with-php-or-kalacloud/" rel="nofollow" target="_blank" title=""&gt;MySQL 中如何实现 BLOB 数据类型的存取，BLOB 有哪些应用场景？&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://kalacloud.com/blog/mysql-workbench-tutorial/" rel="nofollow" target="_blank" title=""&gt;如何使用 MySQL Workbench 操作 MySQL / MariaDB 数据库中文指南&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>HiJiangChuan</author>
      <pubDate>Thu, 18 Nov 2021 00:16:59 +0800</pubDate>
      <link>https://ruby-china.org/topics/41891</link>
      <guid>https://ruby-china.org/topics/41891</guid>
    </item>
    <item>
      <title>MySQL / MariaDB 触发器的创建、使用、查看、删除教程及应用场景实战案例</title>
      <description>&lt;p&gt;&lt;img src="https://kalacloud.com/static/0fd7f49674f5905a18cfa04d8c0c8b6a/ef245/head.jpg" title="" alt="MySQL 触发器"&gt;&lt;/p&gt;

&lt;p&gt;本文首发：&lt;a href="https://kalacloud.com/blog/how-to-manage-and-use-mysql-database-triggers/" rel="nofollow" target="_blank" title=""&gt;https://kalacloud.com/blog/how-to-manage-and-use-mysql-database-triggers&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;触发器（Trigger）是 MySQL 中非常实用的一个功能，它可以在操作者对表进行「增删改」之前（或之后）被触发，自动执行一段事先写好的 SQL 代码。&lt;/p&gt;

&lt;p&gt;本教程带领大家在实践中学习，你将学到触发器在实际应用场景中的重要应用。&lt;/p&gt;

&lt;p&gt;在这个教程中，你是「卡拉云银行」的程序员，你正在搭建一套银行客户管理系统。在这套系统中，你需要设置在&lt;code&gt;INSERT&lt;/code&gt; 表之前检测操作者是否输入错误数据、在 &lt;code&gt;UPDATE&lt;/code&gt; 时，记录操作者的行为 log，以及在&lt;code&gt;DELETE&lt;/code&gt; 时，判断删除的信息是否符合删除规则。这三类操作都可以使用 MySQL 触发器来实现。&lt;/p&gt;

&lt;p&gt;如果你正在数据库的基础上搭建一套数据库管理工具或企业内部工具，推荐你试试我开发的&lt;a href="https://kalacloud.com" rel="nofollow" target="_blank" title=""&gt;卡拉云&lt;/a&gt;，详情见后文。&lt;/p&gt;

&lt;p&gt;本教程将带你一起实践的案例&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BEFORE INSERT&lt;/code&gt; ：在插入数据前，检测插入数据是否符合业务逻辑，如不符合返回错误信息。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AFTER INSERT&lt;/code&gt; ：在表 A 创建新账户后，将创建成功信息自动写入表 B 中。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BEFORE UPDATE&lt;/code&gt; ：在更新数据前，检测更新数据是否符合业务逻辑，如不符合返回错误信息。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AFTER INSERT&lt;/code&gt; ：在更新数据后，将操作行为记录在 log 中&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BEFORE DELETE&lt;/code&gt; ：在删除数据前，检查是否有关联数据，如有，停止删除操作。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AFTER DELETE&lt;/code&gt; ：删除表 A 信息后，自动删除表 B 中与表 A 相关联的信息。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="先决条件"&gt;先决条件&lt;/h2&gt;
&lt;p&gt;在开始之前，请确保您具备以下条件：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;一台配置好的 Ubuntu 服务器，root 账号。&lt;/li&gt;
&lt;li&gt;服务器上配置好 MySQL Server（配置 MySQL 请看&lt;a href="https://kalacloud.com/blog/how-to-install-linux-apache-mysql-php-lamp-stack/" rel="nofollow" target="_blank" title=""&gt;MySQL 安装&lt;/a&gt;及&lt;a href="https://kalacloud.com/blog/how-to-allow-remote-access-to-mysql/" rel="nofollow" target="_blank" title=""&gt;连接 MySQL 教程&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;MySQL root 账号&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="创建示例数据库"&gt;创建示例数据库&lt;/h2&gt;
&lt;p&gt;我们先创建一个干净的示例数据库，方便大家可以跟随本教程一起实践。我们会在这个数据库中演示 MySQL 触发器的多种工作方式。&lt;/p&gt;

&lt;p&gt;首先，以 root 身份登录到你的 MySQL 服务器：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;出现提示时，请输入你 MySQL root 账号的密码，然后点击 &lt;code&gt;ENTER&lt;/code&gt; 继续。看到 &lt;code&gt;mysql&amp;gt;&lt;/code&gt; 提示后，运行以下命令，创建 &lt;code&gt;demo_kalacloud&lt;/code&gt; 数据库：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt; &lt;span class="n"&gt;demo_kalacloud&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Query OK, 1 row affected (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来，切换到新建的 &lt;code&gt;demo_kalacloud&lt;/code&gt; 数据库：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;demo_kalacloud&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Database changed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着创建一个 &lt;code&gt;customers&lt;/code&gt; 表。我们使用这个表记录银行客户的信息。这个表包括 &lt;code&gt;customer_id&lt;/code&gt;，&lt;code&gt;customer_name&lt;/code&gt;，和&lt;code&gt;level&lt;/code&gt;。咱们先把客户分为两个级别：&lt;code&gt;BASIC&lt;/code&gt;和&lt;code&gt;VIP&lt;/code&gt;。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
&lt;span class="n"&gt;customer_name&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
&lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;INNODB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Query OK, 0 rows affected (0.01 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着，我们向 &lt;code&gt;customers&lt;/code&gt; 表中添加一些客户记录。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;
&lt;span class="k"&gt;Insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Jack Ma'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'BASIC'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;Insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Robin Li'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'BASIC'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;Insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Pony Ma'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'VIP'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;分别运行三个 &lt;code&gt;INSERT&lt;/code&gt; 命令后，命令行输出成功信息。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Query OK, 1 row affected (0.01 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们使用 &lt;code&gt;SELECT&lt;/code&gt; 检查一下三条信息是否已经写入表中：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/884ce259e7ad472b46da683b123e22cf/e2d3b/02-select-customers-tables.png" title="" alt="显示 customers 表"&gt;&lt;/p&gt;

&lt;p&gt;下面我们创建另一个表&lt;code&gt;customer_status&lt;/code&gt;，用于保存 &lt;code&gt;customers&lt;/code&gt; 表中客户的备注信息。&lt;/p&gt;

&lt;p&gt;这个表包含 &lt;code&gt;customer_id&lt;/code&gt; 和 &lt;code&gt;status_notes&lt;/code&gt; 字段：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;customer_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status_notes&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;INNODB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后，我们再创建一个 &lt;code&gt;sales&lt;/code&gt; 表，这个表与 &lt;code&gt;customer_id&lt;/code&gt; 关联。保存与客户有关的销售数据。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales_amount&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;INNODB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Query OK, 0 rows affected (0.01 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最后一步，我们再建一个 &lt;code&gt;audit_log&lt;/code&gt; 表，用来记录操作员操作「卡拉云银行」客户管理系统时的操作行为。方便管理员在发生问题时，有 log 可查。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;audit_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;log_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales_id&lt;/span&gt; &lt;span class="nb"&gt;BIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;previous_amount&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_amount&lt;/span&gt; &lt;span class="nb"&gt;DOUBLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_by&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;updated_on&lt;/span&gt; &lt;span class="nb"&gt;DATETIME&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;INNODB&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Query OK, 0 rows affected (0.02 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;至此，你作为「卡拉云银行」的程序员，已经把客户管理系统的&lt;code&gt;demo_kalacloud&lt;/code&gt; 数据库和四张表建立完成。接下来，我们将对这个管理系统的关键节点增加对应的触发器。&lt;/p&gt;

&lt;p&gt;扩展阅读：《&lt;a href="https://kalacloud.com/blog/how-to-use-mysql-slow-query-log-profiling-mysqldumpslow/" rel="nofollow" target="_blank" title=""&gt;如何使用 MySQL 慢查询日志进行性能优化 - Profiling、mysqldumpslow 实例详解&lt;/a&gt;》&lt;/p&gt;
&lt;h2 id="1.BEFORE INSERT触发器使用方法"&gt;1.&lt;code&gt;BEFORE INSERT&lt;/code&gt;触发器使用方法&lt;/h2&gt;
&lt;p&gt;作为严谨的银行客户管理系统，对任何写入系统的数据都应该提前检测，以防止错误的信息被写进去。&lt;/p&gt;

&lt;p&gt;在写入前检测数据这个功能，我们可以使用&lt;code&gt;BEFORE INSERT&lt;/code&gt; 触发器来实现。&lt;/p&gt;

&lt;p&gt;在操作者对 &lt;code&gt;sales&lt;/code&gt; 表中的&lt;code&gt;sales_amount&lt;/code&gt; 字段进行写操作时，系统将在写入（&lt;code&gt;INSERT&lt;/code&gt;）前检查数据是否符合规范。&lt;/p&gt;

&lt;p&gt;我们先来看一下，&lt;strong&gt;创建触发器的基本语法&lt;/strong&gt;。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;触发器的名字&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;触发器执行时机&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;触发器监测的对象&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;表名&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;触发器主体代码&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;触发器的结构包括：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DELIMITER //&lt;/code&gt;：MySQL 默认分隔符是&lt;code&gt;;&lt;/code&gt; 但在触发器中，我们使用 &lt;code&gt;//&lt;/code&gt; 表示触发器的开始与结束。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[触发器的名字]&lt;/code&gt;：这里填写触发器的名字&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[触发器执行时机]&lt;/code&gt;：这里设置触发器是在关键动作执行之前触发，还是执行之后触发。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[触发器监测的对象]&lt;/code&gt;：触发器可以监测 &lt;code&gt;INSERT&lt;/code&gt;、&lt;code&gt;UPDATE&lt;/code&gt;、&lt;code&gt;DELETE&lt;/code&gt; 的操作，当监测的命令对触发器关联的表进行操作时，触发器就被激活了。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[表名]&lt;/code&gt;：将这个触发器与数据库中的表进行关联，触发器定义在表上，也附着在表上，如果这个表被删除了，那么这个触发器也随之被删除。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FOR EACH ROW&lt;/code&gt;：这句表示只要满足触发器触发条件，触发器都会被执行，也就是说带上这个参数后，触发器将监测每一行对关联表操作的代码，一旦符合条件，触发器就会被触发。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;[触发器主体代码]&lt;/code&gt;：这里是当满足触发条件后，被触发执行的代码主体。这里可以是一句 SQL 语句，也可以是多行命令。如果是多行命令，那么这些命令要写在 &lt;code&gt;BEGIN...END&lt;/code&gt; 之间。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;注：&lt;/strong&gt;在创建触发器主体时，还可以使用&lt;code&gt;OLD&lt;/code&gt;和&lt;code&gt;NEW&lt;/code&gt; 来获取 SQL 执行&lt;code&gt;INSERT&lt;/code&gt;，&lt;code&gt;UPDATE&lt;/code&gt;和&lt;code&gt;DELETE&lt;/code&gt; 操作前后的写入数据。这里没看明白没关系，我们将会在接下来的实践中，展开讲解。&lt;/p&gt;

&lt;p&gt;讲到这里，大家看了一大堆云里雾里的概念，如果没看懂，也别担心。接下来进入实践环节，只要跟着贴代码看返回结果，很快你就能够通透理解触发器了。&lt;/p&gt;

&lt;p&gt;现在，我们来创建第一个触发器，&lt;code&gt;BEFORE INSERT&lt;/code&gt; （在执行 &lt;code&gt;insert&lt;/code&gt; 之前，执行触发器）。这个触发器用于监测操作者在写入 &lt;code&gt;sales&lt;/code&gt; 表中的 &lt;code&gt;sales_amount&lt;/code&gt; 值时，这个值是否大于 &lt;code&gt;10000&lt;/code&gt; ，如果大于，那么返回错误信息进行报错。&lt;/p&gt;

&lt;p&gt;登录 MySQL Server 后，我们创建一个触发器：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;validate_sales_amount&lt;/span&gt;
&lt;span class="k"&gt;BEFORE&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
&lt;span class="n"&gt;SIGNAL&lt;/span&gt; &lt;span class="k"&gt;SQLSTATE&lt;/span&gt; &lt;span class="s1"&gt;'45000'&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;MESSAGE_TEXT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"你输入的销售总额超过 10000 元。"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面这段代码中，我们使用&lt;code&gt;IF...THEN...END IF&lt;/code&gt; 来创建一个监测 &lt;code&gt;INSERT&lt;/code&gt; 语句写入的值是否在限定的范围内的触发器。&lt;/p&gt;

&lt;p&gt;这个触发器的功能时监测 &lt;code&gt;INSERT&lt;/code&gt; 在写入&lt;code&gt;sales_amount&lt;/code&gt; 值时，这个新增的（&lt;code&gt;NEW&lt;/code&gt;）值是否符合条件（ &lt;code&gt;&amp;gt; 10000&lt;/code&gt;）。&lt;/p&gt;

&lt;p&gt;当操作员录入一个超过 10000 的数字，会返回如下错误信息：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = '你输入的销售总额超过 10000 元。';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们来试试看，看看触发器是否已启用。&lt;/p&gt;

&lt;p&gt;我们向 &lt;code&gt;sales_amount&lt;/code&gt; 中插入一条 &lt;code&gt;11000&lt;/code&gt; 的值。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'11000'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/dee0dc83297e4164d3f60676d922e277/ca3c3/03-error-1644.png" title="" alt="ERROR 1644 触发器不允许写入"&gt;&lt;/p&gt;

&lt;p&gt;命令行返回错误信息，这就是我们刚刚创建触发器时，填入的错误信息。与我们的设置一致。&lt;/p&gt;

&lt;p&gt;下面我们 &lt;code&gt;insert&lt;/code&gt; 一个值小于 &lt;code&gt;10000&lt;/code&gt; 的数字：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt;  &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'7700'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入值为 &lt;code&gt;7700&lt;/code&gt; 小于设定的 &lt;code&gt;10000&lt;/code&gt;  ，&lt;code&gt;insert&lt;/code&gt; 命令执行成功。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Query OK, 1 row affected (0.01 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们调出 &lt;code&gt;sales&lt;/code&gt; 表，看看是否插入成功：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输出确认数据在表中：&lt;/p&gt;

&lt;p&gt;![确认数据在表中]](&lt;a href="https://kalacloud.com/static/6a4d3093e730b33651a25d716fb6c765/29beb/04-insert-into.png" rel="nofollow" target="_blank"&gt;https://kalacloud.com/static/6a4d3093e730b33651a25d716fb6c765/29beb/04-insert-into.png&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;通过这张表，我们可以看到，7700 已经插入到表中。&lt;/p&gt;

&lt;p&gt;刚刚我们演示了在执行 &lt;code&gt;insert&lt;/code&gt; 命令前，检测某个值是否符合设定，接着我们来看在执行 &lt;code&gt;insert&lt;/code&gt; 之后，使用触发器将不同的值保存到不同的表中。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;扩展阅读：&lt;/strong&gt;《&lt;a href="https://kalacloud.com/blog/how-to-migrate-a-mysql-database-between-two-servers-aliyun-tencentyun/" rel="nofollow" target="_blank" title=""&gt;如何在两台服务器之间迁移 MySQL / MariaDB 数据库 阿里云腾讯云迁移案例&lt;/a&gt;》&lt;/p&gt;
&lt;h2 id="2.AFTER INSERT触发器使用方法"&gt;2.&lt;code&gt;AFTER INSERT&lt;/code&gt;触发器使用方法&lt;/h2&gt;
&lt;p&gt;接着我们讲解 &lt;code&gt;AFTER INSERT&lt;/code&gt; ，触发器在监测到我们成功执行了 &lt;code&gt;INSERT&lt;/code&gt; 命令后，再执行触发器中设置好的代码。&lt;/p&gt;

&lt;p&gt;例如：在银行账户系统中，当我们新建一个账户后，我们将创建成功信息写入对应的 &lt;code&gt;customer_status&lt;/code&gt; 表中。&lt;/p&gt;

&lt;p&gt;在这个案例中，你作为「卡拉云银行」的程序员，现在要创建一个&lt;code&gt;AFTER INSERT&lt;/code&gt;触发器，在创建新客户账户后，将成功信息写入&lt;code&gt;customer_status&lt;/code&gt; 表中&lt;/p&gt;

&lt;p&gt;要创建&lt;code&gt;AFTER INSERT&lt;/code&gt;触发器，请输入以下命令：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;customer_status_records&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;Insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;customer_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status_notes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'账户创建成功'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Query OK, 0 rows affected (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个触发器在操作者向 &lt;code&gt;customers&lt;/code&gt; 表中 &lt;code&gt;INSERT&lt;/code&gt; 新客户信息后，再向 &lt;code&gt;customer_status&lt;/code&gt; 表对应的行中写入成功信息。&lt;/p&gt;

&lt;p&gt;现在我们 &lt;code&gt;INSERT&lt;/code&gt; 一条信息，看看触发器是否已启用：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Xing Wang'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'VIP'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Query OK, 1 row affected (0.01 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;记录 &lt;code&gt;INSERT&lt;/code&gt; 成功，接着我们来检查&lt;code&gt;customer_status&lt;/code&gt;表中是否写入了对应的成功数据。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;customer_status&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/fbe9ada62cf8fe333997165f762993a4/79166/05-insert-into-01.png" title="" alt="检查是否成功"&gt;&lt;/p&gt;

&lt;p&gt;这里可以看到，我们向 &lt;code&gt;customers&lt;/code&gt;  表插入了一个&lt;code&gt;customer_id&lt;/code&gt; 为 &lt;code&gt;4&lt;/code&gt; 的新用户，随后，触发器根据代码自动向&lt;code&gt;customer_status&lt;/code&gt; 表中也插入了一个 &lt;code&gt;customer_id&lt;/code&gt; 为 &lt;code&gt;4&lt;/code&gt; 的开户成功信息。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AFTER INSERT&lt;/code&gt; 特别适合这种状态变更的关联写入操作。比如开户、暂停、注销等各类状态变更。&lt;/p&gt;

&lt;p&gt;到这里，触发器在&lt;code&gt;INSERT&lt;/code&gt;执行前、后的应用，我们已经讲完了，接着我们来讲 &lt;code&gt;UPDATE&lt;/code&gt; 触发器。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;扩展阅读：&lt;/strong&gt;《&lt;a href="https://kalacloud.com/blog/how-to-edit-mysql-configuration-file-my-cnf-ini/" rel="nofollow" target="_blank" title=""&gt;MySQL 配置文件 my.cnf / my.ini 逐行详解&lt;/a&gt;》&lt;/p&gt;
&lt;h2 id="3.BEFORE UPDATE触发器使用方法"&gt;3.&lt;code&gt;BEFORE UPDATE&lt;/code&gt;触发器使用方法&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;BEFORE UPDATE&lt;/code&gt;触发器与&lt;code&gt;BEFORE INSERT&lt;/code&gt; 触发器非常类似，我们可以使用&lt;code&gt;BEFORE UPDATE&lt;/code&gt; 触发器在更新数据之前，先做一次业务逻辑检测，避免发生误操作。&lt;/p&gt;

&lt;p&gt;刚刚我们创建示例数据库时，创建了两个级别的客户，VIP 和 BASIC 级别。卡拉云银行的客户一旦升级至 VIP，就不能再降级至 BASIC 级别了。&lt;/p&gt;

&lt;p&gt;我们使用 &lt;code&gt;BEFORE UPDATE&lt;/code&gt; 来贯彻这一规则，这个触发器将在 &lt;code&gt;UPDATE&lt;/code&gt; 语句执行之前，先判断是否为降级行为，如果是，则输出报错信息。&lt;/p&gt;

&lt;p&gt;我们来创建这个触发器：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;validate_customer_level&lt;/span&gt;
&lt;span class="k"&gt;BEFORE&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'VIP'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
&lt;span class="n"&gt;SIGNAL&lt;/span&gt; &lt;span class="k"&gt;SQLSTATE&lt;/span&gt; &lt;span class="s1"&gt;'45000'&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;MESSAGE_TEXT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'VIP 级别客户不能降级为普通级别客户'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们可以使用 &lt;code&gt;OLD&lt;/code&gt; 来获取执行 &lt;code&gt;UPDATE&lt;/code&gt; 命令前，客户的 &lt;code&gt;level&lt;/code&gt; 值。同样，我们使用该&lt;code&gt;IF...THEN...END IF&lt;/code&gt;语句来对 &lt;code&gt;level&lt;/code&gt; 值是否符合规则进行判断。&lt;/p&gt;

&lt;p&gt;我们先来查看一下 &lt;code&gt;customers&lt;/code&gt; 表中的数据。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; from customers&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/8eab72c0962f70db1655abeffd568fdb/e2d3b/06-validate-customer-level.png" title="" alt="不允许降级"&gt;&lt;/p&gt;

&lt;p&gt;好，我们选一个已经是 VIP 级别的客户，对他进行降级操作，看看我们的触发器是否能够正确执行。&lt;/p&gt;

&lt;p&gt;接下来，运行以下 SQL 命令，试试能不能将 &lt;code&gt;customer_id&lt;/code&gt; 为 &lt;code&gt;3&lt;/code&gt; 的 VIP 客户降级成 &lt;code&gt;BASIC&lt;/code&gt; 客户：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Update&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'BASIC'&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'3'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行代码后，命令行返回错误信息：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/387b96393c3722c02acbd9afae183da1/236d7/07-validate-customer-level-done.png" title="" alt="在允许范围内可通过"&gt;&lt;/p&gt;

&lt;p&gt;这说明我们刚刚设置的触发器已经起作用了。&lt;/p&gt;

&lt;p&gt;接着我们来试试，对一个&lt;code&gt;BASIC&lt;/code&gt;级别的客户运行相同的命令，看看能不能把他升级到&lt;code&gt;VIP&lt;/code&gt;级别：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Update&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="k"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'VIP'&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行成功：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Rows matched: 1  Changed: 1  Warnings: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们再来看一下 &lt;code&gt;customers&lt;/code&gt; 表中的数据情况：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; from customers&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/168b1d2493f129856e0653fdd88e14dc/daed9/08-validate-customer-level-update.png" title="" alt="客户已经升级为 VIP"&gt;&lt;/p&gt;

&lt;p&gt;可以看到刚才 &lt;code&gt;customer_id&lt;/code&gt; 为 &lt;code&gt;2&lt;/code&gt; 的 &lt;code&gt;BASIC&lt;/code&gt;  客户已经升级为 &lt;code&gt;VIP&lt;/code&gt; 客户。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BEFORE UPDATE&lt;/code&gt; 触发器用于在更新数据前进行确认，很好的守护了系统的业务规则。接着我们来看看 &lt;code&gt;AFTER UPDATE&lt;/code&gt; 在客户管理系统中的应用。&lt;/p&gt;

&lt;p&gt;扩展阅读：《&lt;a href="https://kalacloud.com/blog/mysql-workbench-tutorial/" rel="nofollow" target="_blank" title=""&gt;MySQL Workbench 操作 MySQL / MariaDB 数据库中文指南&lt;/a&gt;》&lt;/p&gt;
&lt;h2 id="4.AFTER INSERT触发器使用方法"&gt;4.&lt;code&gt;AFTER INSERT&lt;/code&gt;触发器使用方法&lt;/h2&gt;
&lt;p&gt;本节我们来演示 &lt;code&gt;AFTER UPDATE&lt;/code&gt; 在实际中的应用。&lt;code&gt;AFTER UPDATE&lt;/code&gt; &lt;strong&gt;多用于 log 记录&lt;/strong&gt;，在管理系统多操作者使用的环境中，管理员需要设置操作 log 记录，以便在出问题时，可以查看操作者对表编辑的操作，可追根溯源。&lt;/p&gt;

&lt;p&gt;我们先来创建一个对 &lt;code&gt;sales&lt;/code&gt; 表操作的 log 记录触发器。&lt;/p&gt;

&lt;p&gt;当操作者对  &lt;code&gt;sales&lt;/code&gt; 表进行修改后，操作记录会被写入 &lt;code&gt;audit_log&lt;/code&gt; 表中。&lt;/p&gt;

&lt;p&gt;触发器将监测用户 ID、更新前的销售总额、更新后的销售总额、操作者 ID、修改时间等信息，作为 log 存入 &lt;code&gt;audit_log&lt;/code&gt; 表中。&lt;/p&gt;

&lt;p&gt;使用以下命令建立这个 log 记录触发器：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;log_sales_updates&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;Insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;audit_log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;previous_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_on&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sales_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NEW&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当操作者对 &lt;code&gt;sales&lt;/code&gt; 表中的一条客户信息进行 &lt;code&gt;UPDATE&lt;/code&gt; 操作时，触发器会在&lt;code&gt;UPDATE&lt;/code&gt;操作之后，将操作行为记录在 &lt;code&gt;audit_log&lt;/code&gt; 中。包括 &lt;code&gt;sales_id&lt;/code&gt; ，修改 &lt;code&gt;sales_amount&lt;/code&gt; 值的前后变化。&lt;/p&gt;

&lt;p&gt;销售总额的变化是审计的关键数据，所以要把它记录在 &lt;code&gt;audit_log&lt;/code&gt; 中。使用&lt;code&gt;OLD&lt;/code&gt; 来获取更新前的 &lt;code&gt;sales_amount&lt;/code&gt; 值，使用 &lt;code&gt;NEW&lt;/code&gt; 来获取更新后的值。&lt;/p&gt;

&lt;p&gt;另外我们还要记录修改 &lt;code&gt;sales&lt;/code&gt; 表的操作者信息及操作时间。&lt;/p&gt;

&lt;p&gt;你可以使用 &lt;code&gt;SELECT USER()&lt;/code&gt; 来检测当前操作用户的账号，用 &lt;code&gt;NOW()&lt;/code&gt; 语句抓去当前服务器日期和时间。&lt;/p&gt;

&lt;p&gt;为了测试这个触发器，我们先在 &lt;code&gt;sales&lt;/code&gt; 表中创建一条信息记录：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sales_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'5'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'8000'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Query OK, 1 row affected (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来，我们来更新这条记录：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Update&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;sales_amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'9000'&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;sales_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'5'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;您将看到以下输出：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Rows matched: 1  Changed: 1  Warnings: 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;理论上，我们更新了 &lt;code&gt;sales&lt;/code&gt; 表后，触发器应该触发了操作，将我们刚刚的修改记录到了&lt;code&gt;audit_log&lt;/code&gt; 表中。我们用以下命令，看看&lt;code&gt;audit_log&lt;/code&gt; 表中是否已经有记录了。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;audit_log&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如下表，触发器更新了&lt;code&gt;audit_log&lt;/code&gt; 表，表中包含了&lt;code&gt;sales_amount&lt;/code&gt; 更新前的旧值和更新后的新值。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/65237917f89edd5e6d540febf1945e84/51dd6/09-after-update.png" title="" alt="log 自动记录触发器"&gt;&lt;/p&gt;

&lt;p&gt;至此，使用 &lt;code&gt;AFTER UPDATE&lt;/code&gt; 制作的 log 自动记录触发器就完成了。&lt;/p&gt;

&lt;p&gt;下一节，我们来学习 &lt;code&gt;DELETE&lt;/code&gt; 相关的触发器。&lt;/p&gt;

&lt;p&gt;扩展阅读：《&lt;a href="https://kalacloud.com/blog/how-to-get-the-sizes-of-the-tables-of-a-mysql-database/" rel="nofollow" target="_blank" title=""&gt;如何查看 MySQL 数据库、表、索引容量大小？找到占用空间最大的表&lt;/a&gt;》&lt;/p&gt;
&lt;h2 id="5.BEFORE DELETE触发器使用方法"&gt;5.&lt;code&gt;BEFORE DELETE&lt;/code&gt;触发器使用方法&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;BEFORE DELETE&lt;/code&gt;触发器会在&lt;code&gt;DELETE&lt;/code&gt;语句执行之前调用。&lt;/p&gt;

&lt;p&gt;这些类型的触发器通常用于在不同的相关表上强制执行参照完整性。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;BEFORE DELETE&lt;/code&gt; 的应用场景通常是确保有关联的数据不被错误的误删除掉。&lt;/p&gt;

&lt;p&gt;例如：&lt;code&gt;sales&lt;/code&gt; 表通过&lt;code&gt;customer_id&lt;/code&gt; 与&lt;code&gt;customers&lt;/code&gt;表相关联。如果操作者删除了&lt;code&gt;customers&lt;/code&gt; 表中的一条数据，那么 &lt;code&gt;sales&lt;/code&gt; 表中某些数据就失去了关联线索。&lt;/p&gt;

&lt;p&gt;为了避免这种情况的发生，我们需要创建一个 &lt;code&gt;BEFORE DELETE&lt;/code&gt;触发器，防止记录被误删除。&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;validate_related_records&lt;/span&gt;
&lt;span class="k"&gt;BEFORE&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
&lt;span class="n"&gt;SIGNAL&lt;/span&gt; &lt;span class="k"&gt;SQLSTATE&lt;/span&gt; &lt;span class="s1"&gt;'45000'&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;MESSAGE_TEXT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'这位客户有相关联的销售记录，不能删除。'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;现在，我们试着删除有销售关联信息的客户：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Delete&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以，你会看到以下输出：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/604ec02952a1a36005433cfcb17052de/3a1b1/10-dele-not.png" title="" alt="触发器不允许删除"&gt;&lt;/p&gt;

&lt;p&gt;这个触发器做到了先检测 &lt;code&gt;sales&lt;/code&gt; 是否与正要被删除的 &lt;code&gt;customers&lt;/code&gt; 表中的数据有关联，防止有关联信息的数据被误删除。&lt;/p&gt;

&lt;p&gt;不过有时候，我们需要删除主数据后，再让系统自动帮我们删除与之相关联的其他所有数据。这时，我们就要用到 &lt;code&gt;AFTER DELETE&lt;/code&gt; 这个触发器了。&lt;/p&gt;

&lt;p&gt;扩展阅读：《&lt;a href="https://kalacloud.com/blog/difference-between-mysql-datetime-and-timestamp-datatypes/" rel="nofollow" target="_blank" title=""&gt;在 MySQL 中 DATETIME 和 TIMESTAMP 时间类型的区别及使用场景 - 实战案例讲解&lt;/a&gt;》&lt;/p&gt;
&lt;h2 id="6.AFTER DELETE触发器使用方法"&gt;6.&lt;code&gt;AFTER DELETE&lt;/code&gt;触发器使用方法&lt;/h2&gt;
&lt;p&gt;接着说说 &lt;code&gt;AFTER DELETE&lt;/code&gt; ，一旦记录被成功删除，这个触发器就会被激活。&lt;/p&gt;

&lt;p&gt;这个触发器在实际场景用的应用也比较广泛。比如银行系统中的升级降级操作，当客户花掉自己的账户积分后，激活触发器，触发器可以判断剩余积分是否满足客户当前等级，如果不满足，自动做降级操作。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AFTER DELETE&lt;/code&gt;触发器的另一个用途是在删除主表中的数据后，与这个主表关联的数据，一起自动删除。&lt;/p&gt;

&lt;p&gt;我们来看一下这个触发器如何创建：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TRIGGER&lt;/span&gt; &lt;span class="n"&gt;delete_related_info&lt;/span&gt;
&lt;span class="k"&gt;AFTER&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt;
&lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;EACH&lt;/span&gt; &lt;span class="k"&gt;ROW&lt;/span&gt;
&lt;span class="k"&gt;Delete&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;OLD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;
&lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来，我们来试试这个触发器。删除销售记录中 &lt;code&gt;customer_id&lt;/code&gt; 为 &lt;code&gt;2&lt;/code&gt; 的销售记录：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Delete&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sales&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Query OK, 1 row affected (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接着我们检查以下 &lt;code&gt;customers&lt;/code&gt; 表中的关联信息是否一起自动删除：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;customer_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;命令行会返回 &lt;code&gt;Empty Set&lt;/code&gt; 的结果，我们刚刚删除了 &lt;code&gt;sales&lt;/code&gt; 表中的信息后，&lt;code&gt;customers&lt;/code&gt; 表中的关联信息也被一起删除了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/b1f04866841c4869946217ed0832c7d6/eee07/11-dele0triggers.png" title="" alt="在范围内允许删除"&gt;&lt;/p&gt;

&lt;p&gt;以上就是 MySQL 触发器的六种使用方式和对应的场景。&lt;/p&gt;

&lt;p&gt;扩展阅读：《&lt;a href="https://kalacloud.com/blog/best-mysql-gui-tools/" rel="nofollow" target="_blank" title=""&gt;最好用的 10 款 MySQL / MariaDB 管理工具横向测评 - 免费和付费到底怎么选？&lt;/a&gt;》&lt;/p&gt;
&lt;h2 id="7.查看触发器"&gt;7.查看触发器&lt;/h2&gt;&lt;h3 id="（1）直接查看触发器"&gt;（1）直接查看触发器&lt;/h3&gt;
&lt;p&gt;当我们想查看数据库中的触发器有哪些时，可用以下命令：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SHOW TRIGGERS&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;后面加上 &lt;code&gt;\G&lt;/code&gt; 是触发器列表竖排列：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SHOW TRIGGERS &lt;span class="se"&gt;\G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/850311b076c6112b44d4fd7a3b5ac8d0/00cd3/01-show-trigger.jpg" title="" alt="显示所有触发器"&gt;&lt;/p&gt;

&lt;p&gt;刚刚我们创建的触发器都罗列在这个列表当中了。&lt;/p&gt;
&lt;h3 id="（2）在 triggers 表中查看触发器信息"&gt;（2）在 triggers 表中查看触发器信息&lt;/h3&gt;
&lt;p&gt;在 MySQL Server 中，数据库 &lt;code&gt;information_schema&lt;/code&gt;  的 &lt;code&gt;triggers&lt;/code&gt; 表中存着所有触发器的信息。所有我们可以通过 &lt;code&gt;SELECT&lt;/code&gt; 来查看。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SELECT &lt;span class="k"&gt;*&lt;/span&gt; FROM information_schema.triggers WHERE &lt;span class="nv"&gt;trigger_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'触发器名称'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;当然，也可以不指定触发器名称，来查看所有。&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;SELECT &lt;span class="k"&gt;*&lt;/span&gt; FROM information_schema.triggers &lt;span class="se"&gt;\G&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;扩展阅读：《&lt;a href="https://kalacloud.com/blog/find-all-tables-with-specific-column-names-in-mysql/" rel="nofollow" target="_blank" title=""&gt;如何在 MySQL / MariaDB 中查询数据库中带有某个字段/列名的所有表名&lt;/a&gt;》&lt;/p&gt;
&lt;h2 id="8.删除触发器"&gt;8.删除触发器&lt;/h2&gt;
&lt;p&gt;最后，咱们来说说如何删除触发器。删除命令也很简单，&lt;code&gt;Drop trigger 触发器名字&lt;/code&gt; 即可。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Drop trigger [触发器名称];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;例如，咱们把刚刚创建的最后一个触发器删掉：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;Drop&lt;/span&gt; &lt;span class="k"&gt;trigger&lt;/span&gt; &lt;span class="n"&gt;delete_related_info&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Output
Query OK, 0 rows affected (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特别提示：我们不能对已经创建好的触发器进行修改。如果你想修改，只能先删除，再重新创建。&lt;/p&gt;

&lt;p&gt;扩展阅读：《&lt;a href="https://kalacloud.com/blog/how-to-use-the-mysql-blob-data-type-to-store-images-with-php-or-kalacloud/" rel="nofollow" target="_blank" title=""&gt;MySQL / MariaDB 中如何存储图片 BLOB 数据类型详解&lt;/a&gt;》&lt;/p&gt;
&lt;h2 id="9.总结"&gt;9.总结&lt;/h2&gt;
&lt;p&gt;在本教程中，我们展示了触发器的六种形式，即在&lt;code&gt;INSERT&lt;/code&gt;、&lt;code&gt;DELETE&lt;/code&gt;、&lt;code&gt;UPDATE&lt;/code&gt; 执行前或后执行触发器，以及对应的六个实战案例。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BEFORE INSERT&lt;/code&gt; ：在插入数据前，检测插入数据是否符合业务逻辑，如不符合返回错误信息。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AFTER INSERT&lt;/code&gt; ：在表 A 创建新账户后，将创建成功信息自动写入表 B 中。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BEFORE UPDATE&lt;/code&gt; ：在更新数据前，检测更新数据是否符合业务逻辑，如不符合返回错误信息。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AFTER INSERT&lt;/code&gt; ：在更新数据后，将操作行为记录在 log 中&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BEFORE DELETE&lt;/code&gt; ：在删除数据前，检查是否有关联数据，如有，停止删除操作。&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;AFTER DELETE&lt;/code&gt; ：删除表 A 信息后，自动删除表 B 中与表 A 相关联的信息。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;接着推荐一下卡拉云，只要你会写 MySQL，就能使用卡拉云搭建自己的数据工具，比如，数据看板，企业 CRM、ERP，权限管理后台，对账系统等。&lt;/p&gt;

&lt;p&gt;卡拉云是新一代低代码开发工具，免安装部署，可一键接入包括 MySQL 在内的常见数据库及 API。可根据自己的工作流，定制开发。无需繁琐的前端开发，只需要简单拖拽，即可快速搭建企业内部工具。&lt;strong&gt;数月的开发工作量，使用卡拉云后可缩减至数天，&lt;a href="https://kalacloud.com/" rel="nofollow" target="_blank" title=""&gt;欢迎免费试用卡拉云&lt;/a&gt;。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/18822b2a23183deb7d11dd484a7f65aa/71c1d/97-kalacloud-sql.png" title="" alt="卡拉云可一键接入常见的数据库及 API"&gt;&lt;/p&gt;

&lt;p&gt;卡拉云可一键接入常见的数据库及 API&lt;/p&gt;

&lt;p&gt;卡拉云可根据公司工作流需求，轻松搭建数据看板或其他内部工具，并且可一键分享给组内的小伙伴。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/5400a60956e16d655e0297c5d6e5a8d2/98-kalacloud-gif.gif" title="" alt="卡拉云5分钟搭建企业内部工具"&gt;&lt;/p&gt;

&lt;p&gt;下图为使用卡拉云在 5 分钟内搭建的「优惠券发放核销」后台，仅需要简单拖拽即可快速生成前端组件，只要会写 SQL，便可搭建一套趁手的数据库工具。&lt;a href="https://kalacloud.com/" rel="nofollow" target="_blank" title=""&gt;**欢迎免费试用卡拉云&lt;/a&gt;。**&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/34625d3adaea4ed250ff3f05b863e47c/cca35/99-kalacloud-sql-index.png" title="" alt="导入导出"&gt;&lt;/p&gt;

&lt;p&gt;希望本教程对你有所帮助。更多有关 MySQL 教程，欢迎访问&lt;a href="https://kalacloud.com" rel="nofollow" target="_blank" title=""&gt;卡拉云&lt;/a&gt;查看更多。&lt;/p&gt;

&lt;p&gt;有关 MySQL 教程，可继续拓展学习：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;a href="https://kalacloud.com/blog/how-to-allow-remote-access-to-mysql/" rel="nofollow" target="_blank" title=""&gt;如何远程连接 MySQL 数据库，阿里云腾讯云外网连接教程&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://kalacloud.com/blog/how-to-import-and-export-databases-excel-csv-in-mysql-or-mariadb-from-terminal/" rel="nofollow" target="_blank" title=""&gt;如何在 MySQL / MariaDB 中导入导出数据，导入导出数据库文件、Excel、CSV&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://kalacloud.com/blog/how-to-migrate-a-mysql-database-between-two-servers-aliyun-tencentyun/" rel="nofollow" target="_blank" title=""&gt;如何在两台服务器之间迁移 MySQL 数据库 阿里云腾讯云迁移案例&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://kalacloud.com/blog/how-to-reset-auto-increment-in-mysql/" rel="nofollow" target="_blank" title=""&gt;MySQL 重置自增 ID (AUTO_INCREMENT) 教程 - 完美保留表数据的终极解决方案&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>HiJiangChuan</author>
      <pubDate>Sat, 13 Nov 2021 02:01:15 +0800</pubDate>
      <link>https://ruby-china.org/topics/41879</link>
      <guid>https://ruby-china.org/topics/41879</guid>
    </item>
    <item>
      <title>如何在 MySQL / MariaDB 中导入导出数据，导入导出数据库文件、Excel、CSV</title>
      <description>&lt;p&gt;&lt;img src="https://kalacloud.com/static/958ff4b31519eee028bfe8afb7cd0421/ef245/head.jpg" title="" alt="如何在 MySQL / MariaDB 中导入导出数据，导入导出数据库文件、Excel、CSV"&gt;&lt;/p&gt;

&lt;p&gt;在日常的数据库维护工作中，经常需要对数据库进行导入导出操作，备份、分析、迁移数据都需要用到导入导出功能，在本教程中将详细讲解所有常见的 MySQL 和 MariaDB 中导入导出数据的方法（注意：MySQL 和 MariaDB 两个数据库操作命令一样，可以互换。）&lt;/p&gt;
&lt;h2 id="本教程将详细讲解"&gt;本教程将详细讲解&lt;/h2&gt;&lt;h3 id="1. MySQL / MariaDB 数据库数据「导出」"&gt;1. MySQL / MariaDB 数据库数据「导出」&lt;/h3&gt;
&lt;p&gt;（1）使用 &lt;code&gt;mysqldump&lt;/code&gt;直接导出数据至 SQL 文件&lt;/p&gt;

&lt;p&gt;（2）阿里云 / 腾讯云远程服务器中的数据库直接导出到本地计算机&lt;/p&gt;

&lt;p&gt;（3）使用 &lt;code&gt;into outfile&lt;/code&gt;命令导出数据至 CSV / Excel&lt;/p&gt;

&lt;p&gt;提示：如果你正在寻找数据迁移方案，请查看我写的的另一篇专门针对 &lt;a href="https://kalacloud.com/blog/how-to-migrate-a-mysql-database-between-two-servers-aliyun-tencentyun/" rel="nofollow" target="_blank" title=""&gt;MySQL 数据迁移的教程&lt;/a&gt;，教程中包含腾讯云、阿里云迁移实战。&lt;/p&gt;
&lt;h3 id="2. MySQL / MariaDB 数据库数据「导入」"&gt;2. MySQL / MariaDB 数据库数据「导入」&lt;/h3&gt;
&lt;p&gt;（1）将 SQL 文件导入至 MySQL / MariaDB 数据库中&lt;/p&gt;

&lt;p&gt;（2）使用 &lt;code&gt;source&lt;/code&gt; 导入数据库 SQL 文件&lt;/p&gt;

&lt;p&gt;（3）将 CSV / Excel 文件 导入至 MySQL / MariaDB 数据库中&lt;/p&gt;
&lt;h3 id="3. 使用「卡拉云」一键导入导出 MySQL / MariaDB 数据"&gt;3. 使用「卡拉云」一键导入导出 MySQL / MariaDB 数据&lt;/h3&gt;
&lt;p&gt;如何使用卡拉云，5 分钟搭建一套适应自己工作流的一键导入导出数据库系统。卡拉云无需部署，即插即用，可根据需求灵活调配，适用于后端工程师快速搭建企业内部系统、数据产品经理查看分析数据，数据分析师根据需求快速搭建数据共享平台分享给组内同学协同查看等应用场景。点这里看详情。&lt;/p&gt;
&lt;h3 id="4. 先决条件"&gt;4. 先决条件&lt;/h3&gt;
&lt;p&gt;跟随本教程学习如何导入导出 MySQL 或 MariaDB 数据库，首先要有&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  一台 Linux 服务器，本文以 Ubuntu 为例&lt;/li&gt;
&lt;li&gt;  已安装 MySQL 或 MariaDB server（还未安装，安装教程请看这篇《&lt;a href="https://kalacloud.com/blog/how-to-install-linux-apache-mysql-php-lamp-stack/" rel="nofollow" target="_blank" title=""&gt;MySQL 安装教程&lt;/a&gt;》）&lt;/li&gt;
&lt;li&gt;  MySQL 或 MariaDB Server 中有数据库（用于导出）&lt;/li&gt;
&lt;li&gt;  教程使用 MacOS 演示本地计算机操作，此操作同时适用于 Windows 及 Linux&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="一. 导出 MySQL 或 MariaDB 数据库"&gt;
&lt;a href="https://kalacloud.com/blog/how-to-import-and-export-databases-excel-csv-in-mysql-or-mariadb-from-terminal/#%E4%B8%80-%E5%AF%BC%E5%87%BA-mysql-%E6%88%96-mariadb-%E6%95%B0%E6%8D%AE%E5%BA%93" rel="nofollow" target="_blank" title=""&gt;&lt;/a&gt;一。导出 MySQL 或 MariaDB 数据库&lt;/h2&gt;&lt;h3 id="1.如何使用&amp;nbsp;mysqldump&amp;nbsp;导出数据"&gt;1.如何使用&amp;nbsp;&lt;code&gt;mysqldump&lt;/code&gt;&amp;nbsp;导出数据&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;mysqldump&lt;/code&gt;&amp;nbsp;命令是数据库导出中使用最频繁对一个工具，它可将数据库中的数据备份成已 *.sql 结尾的文本文件，表结构和数据都会存储在其中。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mysqldump&lt;/code&gt;&amp;nbsp;命令的原理也很简单，它先把需要备份的表结构查询出来，然后生成一个&amp;nbsp;&lt;code&gt;CREATE TABLE 'table'&lt;/code&gt;&amp;nbsp;语句，最后将表中所有记录转化成一条&lt;code&gt;INSERT&lt;/code&gt;语句。&lt;/p&gt;

&lt;p&gt;可以把它理解为一个批量导出导入脚本。数据导入时，按照规范语句导入数据，大幅减少奇怪的未知错误出现。&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mysqldump&lt;/code&gt;&amp;nbsp;的基本命令：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mysqldump -u username -p database_name &amp;gt; data-dump.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;username&lt;/code&gt;&amp;nbsp;是数据库的登录名&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;database_name&lt;/code&gt;&amp;nbsp;是需要导出的数据库名称&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;data-dump.sql&lt;/code&gt;&amp;nbsp;是文件输出目录的文件&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;导出实战 - 从阿里云服务器中的 MySQL 数据库导出数据&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mysqldump -u kalacloud -p kalacloud_database &amp;gt; /tmp/kalacloud-data-export.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/ccb5a47b7e118995b20cada7bc2149cf/11864/01-mysqldump.png" title="" alt="使用 mysqldump 导出数据"&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;kalacloud&lt;/code&gt;&amp;nbsp;：数据库账号&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;kalacloud_database&lt;/code&gt;&amp;nbsp;：数据库名&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;/tmp/kalacloud-data-export.sql&lt;/code&gt;&amp;nbsp;：数据库导出的文件及存放目录&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;输入数据库&amp;nbsp;&lt;code&gt;kalacloud&lt;/code&gt;&amp;nbsp;账号的密码执行命令，如果执行过程中，没有任何错误，那么命令行不会有任何输出。&lt;/p&gt;

&lt;p&gt;我们可以 cd 到 tmp 目录查看结果。上图可以看到，tmp 目录下已经生成&amp;nbsp;&lt;code&gt;kalacloud-data-export.sql&lt;/code&gt;&amp;nbsp;的导出文件。&lt;/p&gt;

&lt;p&gt;我们在用&lt;code&gt;head -n 5 kalacloud-data-export.sql&lt;/code&gt;命令检查一下。你会看到类似下图的内容。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/468a4459e57371bdb42e9976527bc95b/62da8/02-kalacloud-data-export.png" title="" alt="检查 mysqldump 导出文件是否有错误"&gt;&lt;/p&gt;

&lt;p&gt;至此，我们已经将指定数据库导出到 *.sql 文件中了，后文我们讲解如何将这些数据导入到数据库。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;进阶提示&lt;/strong&gt;：我们可以使用&amp;nbsp;&lt;code&gt;scp&lt;/code&gt;&amp;nbsp;命令，将导出文件下载至本地计算机。&lt;/p&gt;

&lt;p&gt;在本地计算机的命令行终端里，输入：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scp root@192.168.180.134:/tmp/kalacloud-data-export.sql /Users/kalacloud/Downloads
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;root&lt;/code&gt;&amp;nbsp;远程计算机的登录账号&lt;/p&gt;

&lt;p&gt;&lt;code&gt;192.168.180.134&lt;/code&gt;&amp;nbsp;为远程计算机的 IP 地址&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/tmp/kalacloud-data-export.sql&lt;/code&gt;&amp;nbsp;为需要下载到本地的数据库文件在远程计算机上的存储位置&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/Users/kalacloud/Downloads&lt;/code&gt;&amp;nbsp;为本地计算机的存储位置，远程文件将下载到这个目录中&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/6f0c7e812cb270b989d8ea684681745c/eb1d2/12-scp.png" title="" alt="使用 scp 下载文件到本地"&gt;&lt;/p&gt;

&lt;p&gt;使用&amp;nbsp;&lt;code&gt;scp&lt;/code&gt;&amp;nbsp;将导出的 SQL 文件下载到本地再进行后续处理。当然我们也可以一步导出至本地计算机，下面我们继续讲解进阶导出方法。&lt;/p&gt;

&lt;p&gt;扩展阅读：《&lt;a href="https://kalacloud.com/blog/how-to-allow-remote-access-to-mysql/" rel="nofollow" target="_blank" title=""&gt;如何远程连接 MySQL 数据库，阿里云腾讯云外网连接教程&lt;/a&gt;》&lt;/p&gt;
&lt;h3 id="2.进阶：将阿里云 / 腾讯云远程服务器中的数据库导出到本地计算机"&gt;2.进阶：将阿里云 / 腾讯云远程服务器中的数据库导出到本地计算机&lt;/h3&gt;
&lt;p&gt;前文我们讲了如何在远程服务器上操作导出数据库，导出后保存在远程服务器中。有时我们需要把数据导出给产品或运营进行数据分析，又或者我们使用的云服务是独立 MySQL 数据库，这时，你需要直接把数据导出到本地计算机中。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mysqldump -h remote_IP_address  -u username -p -P3306 --default-character-set=utf8 --set-gtid-purged=OFF database_name &amp;gt;/Users/kalacloud/Desktop/data-dump.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;remote_IP_address&lt;/code&gt;&amp;nbsp;:远程服务器的 IP&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;username&lt;/code&gt;&amp;nbsp;：拥有远程登录权限的 MySQL 账号&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;3306&lt;/code&gt;：远程登录的数据库端口，默认是 3306，如果不是可根据情况替换&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;default-character-set=utf8&lt;/code&gt;&amp;nbsp;：导出时指定字符集&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;set-gtid-purged=OFF&lt;/code&gt;&amp;nbsp;：全局事务 ID (GTID) 来强化数据库的主备一致性，故障恢复，以及容错能力。开启这个功能导入导出时，可能会出错，故关闭。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;database_name&lt;/code&gt;&amp;nbsp;：需要导出的数据库名称&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;/Users/kalacloud/Desktop/data-dump.sql&lt;/code&gt;&amp;nbsp;：本地计算机保存路径及保存文件名&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;提示：&lt;/strong&gt;&lt;code&gt;mysqldump&lt;/code&gt;常见报错：mysqldump: Couldn't execute 'SELECT COLUMN_NAME, JSON_EXTRACT(HISTOGRAM, '$."number-of-buckets-specified"')&lt;/p&gt;

&lt;p&gt;可在命令中添加&amp;nbsp;&lt;code&gt;column-statistics=0&lt;/code&gt;&amp;nbsp;参数。因 MySQL 数据库早期版本&amp;nbsp;&lt;code&gt;information_schema&lt;/code&gt;&amp;nbsp;数据库中没有名为&amp;nbsp;&lt;code&gt;COLUMN_STATISTICS&lt;/code&gt;&amp;nbsp;的数据表，新版&amp;nbsp;&lt;code&gt;mysqldump&lt;/code&gt;&amp;nbsp;默认启用，我们可以通过此命令禁用它。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;导出实战 - 将阿里云服务器中的数据库直接导出到本地计算机&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mysqldump -h123.57.56.228  -ukalacloud-remote -p -P3306 --default-character-set=utf8 --set-gtid-purged=OFF --column-statistics=0 kalacloud_database &amp;gt;/Users/kalacloud/Desktop/kalacloud-data-export.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;123.57.56.228&lt;/code&gt;：远程数据库 ip 地址&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;kalacloud-remote&lt;/code&gt;：拥有远程访问权限的数据库账号。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;-P 3306&lt;/code&gt;：数据库访问端口，可根据自己情况修改。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;/Users/kalacloud/Desktop/kalacloud-data-export.sql&lt;/code&gt;&amp;nbsp;：本地计算机保存路径及保存文件名&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/aa178c898cfbd38cd0177ca1a9800710/a878e/03-aliyun-kalacloud-data-export.png" title="" alt="导出数据至本地计算机"&gt;&lt;/p&gt;

&lt;p&gt;执行命令后，命令行并没有任何信息输出，但我们已经可以在桌面上看到导出后生成的文件了。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/dc743a333efd67df0f44f531ec237a06/2b013/04-desktop.jpg" title="" alt="在本地计算机查看服务器导出的文件"&gt;&lt;/p&gt;

&lt;p&gt;已经导出到本地桌面的远程端数据库&lt;/p&gt;

&lt;p&gt;当然，&lt;code&gt;mysqldump&lt;/code&gt;&amp;nbsp;也可以分表备份，比较常见的场景有&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 备份单个库
mysqldump -uroot -p -R -E --single-transactio --databases [database_one] &amp;gt; database_one.sql

# 备份部分表
mysqldump -uroot -p --single-transaction [database_one] [table_one] [table_two] &amp;gt; database_table12.sql

# 排除某些表
mysqldump -uroot -p [database_one] --ignore-table=[database_one.table_one] --ignore-table=[database_one.table_two] &amp;gt; database_one.sql

# 只备份结构
mysqldump -uroot -p [database_one] --no-data &amp;gt; [database_one.defs].sql

# 只备份数据
mysqldump -uroot -p [database_one] --no-create-info &amp;gt; [database_one.data].sql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;扩展阅读：有关数据库在两台服务器之间迁移的问题可看我写的《&lt;a href="https://kalacloud.com/blog/how-to-migrate-a-mysql-database-between-two-servers-aliyun-tencentyun/" rel="nofollow" target="_blank" title=""&gt;如何迁移 MySQL 数据库，阿里云、腾讯云迁移案例&lt;/a&gt;》&lt;/p&gt;
&lt;h3 id="3.使用&amp;nbsp;into outfile&amp;nbsp;命令导出 MySQL / MariaDB 数据至 CSV / Excel"&gt;3.使用&amp;nbsp;&lt;code&gt;into outfile&lt;/code&gt;&amp;nbsp;命令导出 MySQL / MariaDB 数据至 CSV / Excel&lt;/h3&gt;
&lt;p&gt;有时我们需要将数据导出给运营或产品进行数据分析，这时导出 CSV 文件会更加方便使用。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; select * from users into outfile '/var/lib/mysql-files/users.csv' FIELDS TERMINATED BY ',';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;FIELDS TERMINATED BY ','&lt;/code&gt;&amp;nbsp;数据以 , 进行分隔。&lt;/p&gt;

&lt;p&gt;首先我们登录 MySQL shell，选择需要导出的数据库&lt;code&gt;use kalacloud_database;&lt;/code&gt;&amp;nbsp;然后执行导出命令。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/2cbee0a902e4c829cb7f1d98aa750542/b8471/05-use-kalacloud-database.png" title="" alt="使用 into outfile 导出数据"&gt;&lt;/p&gt;

&lt;p&gt;导出后会显示成功提示，&lt;code&gt;CD&lt;/code&gt;&amp;nbsp;到导出目录可看到 CSV 文件已导出。&lt;/p&gt;

&lt;p&gt;提示：&lt;code&gt;into outfile&lt;/code&gt;&amp;nbsp;常见报错&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这是因为你的 MySQL 配置了&lt;code&gt;--secure-file-priv&lt;/code&gt;&amp;nbsp;限制了导出文件的存放位置。&lt;/p&gt;

&lt;p&gt;你可以使用以下命令来查看具体配置信息&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;show global variables like '%secure_file_priv%';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;[&lt;img src="https://www.kalacloud.com/static/b122081289d2af49c9adaa964479b553/511f0/06-secure-file-priv.png" title="" alt="show global variables like '%secure_file_priv%'"&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;secure_file_priv&lt;/code&gt;&amp;nbsp;为 NULL 时，表示不允许导入或导出。&amp;nbsp;&lt;code&gt;secure_file_priv&lt;/code&gt;&amp;nbsp;为路径时（/var/lib/mysql-files/ ）时，表示只允许在路径目录中执行。&amp;nbsp;&lt;code&gt;secure_file_priv&lt;/code&gt;&amp;nbsp;没有值时，表示可在任意目录的导入导出。&lt;/p&gt;

&lt;p&gt;你可以打开 my.cnf 或 my.ini，添加以下语句，重启 MySQL server 即可&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;secure_file_priv=''
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;扩展阅读：有关把 MySQL 查询出来的结果保存到文件可看我写的这篇《&lt;a href="https://kalacloud.com/blog/how-to-save-mysql-mariadb-query-output-to-a-file/" rel="nofollow" target="_blank" title=""&gt;如何在 MySQL 中保存查询结果到文件&lt;/a&gt;》教程。&lt;/p&gt;
&lt;h2 id="二. MySQL 或 MariaDB 数据库导入数据"&gt;二。MySQL 或 MariaDB 数据库导入数据&lt;/h2&gt;
&lt;p&gt;接着我们讲解如何将 *.sql 导入到数据库中。我们先建一个新数据库用作演示。&lt;/p&gt;

&lt;p&gt;我们以 root 或有足够权限的账号登录 MySQL：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mysql -u root -p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;输入登录密码后，进入 MySQL shell 状态。接着我们创建一个新数据库，在这个例子中，我们用&amp;nbsp;&lt;code&gt;kalacloud_new_database&lt;/code&gt;&amp;nbsp;作为新数据库名称。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; CREATE DATABASE kalacloud_new_database；
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;执行命令后返回内容&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Query OK, 1 row affected (0.00 sec)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;用于演示的新数据库创建完成，我们使用&amp;nbsp;&lt;code&gt;CTRL+D&lt;/code&gt;&amp;nbsp;退出 MySQL shell&lt;/p&gt;
&lt;h3 id="1.直接使用&amp;nbsp;mysql&amp;nbsp;导入 SQL 文件"&gt;1.直接使用&amp;nbsp;&lt;code&gt;mysql&lt;/code&gt;&amp;nbsp;导入 SQL 文件&lt;/h3&gt;
&lt;p&gt;在命令行中我们导入上文导出的&amp;nbsp;&lt;code&gt;/tmp/kalacloud-data-export.sql&lt;/code&gt;&amp;nbsp;文件（注意：以下命令在&lt;strong&gt;命令行中执行&lt;/strong&gt;，不是在 mysql&amp;gt; 状态下执行）&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql -u root -p kalacloud_new_database &amp;lt; /tmp/kalacloud-data-export.sql
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;root&lt;/code&gt;&amp;nbsp;：你可以登录数据库的用户名。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;kalacloud_new_database&lt;/code&gt;&amp;nbsp;：刚刚新建的空数据库，这条命令会把数据导入到这其中。&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;/tmp/kalacloud-data-export.sql&lt;/code&gt;&amp;nbsp;：是上文我们从数据库导出的 sql 文件，这里我们把它再导入到新数据库中。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;如果运行成功，命令行不会有任何提示。如果运行失败，命令行会提示失败原因。要检测是否导入成功，我们可以登录到 MySQL 查看并检查数据库中的数据。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/e25b61e1a7bd0c11d252beb51772dad5/79166/07-select.png" title="" alt="打开数据库中的表检查导入结果"&gt;&lt;/p&gt;

&lt;p&gt;登录 MySQL server，使用&amp;nbsp;&lt;code&gt;USE kalacloud_new_database;&lt;/code&gt;&amp;nbsp;选择刚刚我们导入数据的新建数据库，然后使用&lt;code&gt;SHOW TABLES;&lt;/code&gt;&amp;nbsp;查看数据库中包含的表，最后用&lt;code&gt;SELECT * FROM users;&lt;/code&gt;打开表查看内容。&lt;/p&gt;

&lt;p&gt;扩展阅读：《&lt;a href="https://kalacloud.com/blog/how-to-edit-mysql-configuration-file-my-cnf-ini/" rel="nofollow" target="_blank" title=""&gt;MySQL 配置文件逐行解析&lt;/a&gt;》教程&lt;/p&gt;
&lt;h3 id="2.使用&amp;nbsp;source&amp;nbsp;导入 MySQL / MariaDB 数据库 SQL 文件"&gt;2.使用&amp;nbsp;&lt;code&gt;source&lt;/code&gt;&amp;nbsp;导入 MySQL / MariaDB 数据库 SQL 文件&lt;/h3&gt;
&lt;p&gt;进入 MySQL shell 状态，我们还是导入本教程前文导出的&amp;nbsp;&lt;code&gt;/tmp/kalacloud-data-export.sql&lt;/code&gt;&amp;nbsp;文件，到新数据库中。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; USE kalacloud_new_database;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先选择需要导入的数据库&lt;code&gt;kalacloud_new_database&lt;/code&gt;，返回结果。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database changed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后使用 source&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; source /tmp/kalacloud_new_database.sql;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://kalacloud.com/static/47c27f60effa0d35d40c369e5ac30c67/d7ceb/10-source.png" title="" alt="使用 source 导入数据"&gt;&lt;/p&gt;

&lt;p&gt;执行&amp;nbsp;&lt;code&gt;source&lt;/code&gt;&amp;nbsp;命令后，MySQL 开始执行导入，接着我们使用&amp;nbsp;&lt;code&gt;SHOW TABLES&lt;/code&gt;&amp;nbsp;和&amp;nbsp;&lt;code&gt;select&lt;/code&gt;&amp;nbsp;来查看 SQL 文件是否导入正常。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/fbdb9f9d4addc37d41e236771b206364/79166/11-source-show-table.png" title="" alt="检查 source 命令是否导入成功"&gt;&lt;/p&gt;

&lt;p&gt;上图可以看到，数据已经导入成功。&lt;/p&gt;

&lt;p&gt;特别提示：&lt;code&gt;source&lt;/code&gt;&amp;nbsp;和&amp;nbsp;&lt;code&gt;mysql &amp;lt;&lt;/code&gt;&amp;nbsp;两种导入方式的区别&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;命令执行环境不同：&lt;/strong&gt;&lt;code&gt;source&lt;/code&gt;&amp;nbsp;在 MySQL sell 里执行，&lt;code&gt;mysql &amp;lt;&lt;/code&gt;&amp;nbsp;在终端命令行中执行&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;返回结果的不同：&lt;/strong&gt;&lt;code&gt;source&lt;/code&gt;&amp;nbsp;会连续返回每一行导入结果，如果量大可能会影响速度，&lt;code&gt;mysql &amp;lt;&lt;/code&gt;&amp;nbsp;全部完成后返回结果。&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;报错是否停止执行：&lt;/strong&gt;&lt;code&gt;source&lt;/code&gt;&amp;nbsp;遇到报错不会终止执行，&lt;code&gt;mysql &amp;lt;&lt;/code&gt;&amp;nbsp;遇到报错会终止执行。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;扩展阅读：《&lt;a href="https://kalacloud.com/blog/how-to-manage-and-use-mysql-database-triggers/" rel="nofollow" target="_blank" title=""&gt;MySQL 触发器六种情况一次讲透，应用实战案例&lt;/a&gt;》&lt;/p&gt;
&lt;h3 id="3.MySQL / MariaDB 数据库中导入 CSV 文件"&gt;3.MySQL / MariaDB 数据库中导入 CSV 文件&lt;/h3&gt;
&lt;p&gt;除了直接导入 sql 类文件外，有时候我们还会碰到需要导入 CSV 文件。导入 CSV 文件的步骤与直接导入 sql 有很大的不同，接着我们来讲解如何导入 CSV 文件。&lt;/p&gt;

&lt;p&gt;我们先进入 MySQL Shell：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ mysql -uroot -p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后新建一个空数据库：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql&amp;gt; CREATE DATABASE kalacloud_new_database;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;与导入 *.sql 不同，导入 CSV 文件需要先创建「表」，我们需要根据 CSV 文件中包含的列，使用&lt;code&gt;CREATE TABLE&lt;/code&gt;&amp;nbsp;创建表。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE `users` (
  `id` VARCHAR(255) NULL,
  `name` VARCHAR(255) NULL,
  `phone` VARCHAR(255) NULL,
  `states` VARCHAR(255) NULL,
  `file_size` VARCHAR(255) NULL,
  `sale` VARCHAR(255) NULL,
  `copyright` VARCHAR(255) NULL,
  `homepage` VARCHAR(255),
  `complaint` VARCHAR(255) NULL,
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;建议所有字段都设为接纳&amp;nbsp;&lt;code&gt;NULL&lt;/code&gt;&amp;nbsp;值，也暂时不要设置主键。因为我们并不知道即将导入的 CSV 文件中的数据是否完整和规范。&lt;/p&gt;

&lt;p&gt;建议即便是数字，也先使用&lt;code&gt;VARCHAR&lt;/code&gt;字段，以防止文件中的数据格式不正确导致的奇怪错误。&lt;/p&gt;

&lt;p&gt;我们可以在数据导入后，在对数据库进行验证、清理和修正。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/d3246777ab4a78e4be20e807f751a6cd/b5a09/08-create-excel.png" title="" alt="使用 excel 导入数据.png"&gt;&lt;/p&gt;

&lt;p&gt;上文中我们从&amp;nbsp;&lt;code&gt;kalacloud_database&lt;/code&gt;&amp;nbsp;中导出了表&amp;nbsp;&lt;code&gt;users&lt;/code&gt;&amp;nbsp;存放到了&lt;code&gt;/var/lib/mysql-files/users.csv&lt;/code&gt;&amp;nbsp;里，下面我们使用&amp;nbsp;&lt;code&gt;LOAD DATA INFILE SQL&lt;/code&gt;&amp;nbsp;语句把这个 CSV 文件导入新建的表中。&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;load data infile '/var/lib/mysql-files/users.csv'
into table users
FIELDS TERMINATED BY ','
ENCLOSED BY '"';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;字段使用逗号分隔，字符串用双引号括起来。如果你的 CSV 第一行是标题而非数据，那么还可以添加&amp;nbsp;&lt;code&gt;IGNORE 1 ROWS;&lt;/code&gt;&amp;nbsp;导入时，忽略第一行。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/530d230f067b4dff0257f8d853616a75/a8979/09-users-csv.png" title="" alt="使用 csv 导入数据"&gt;&lt;/p&gt;

&lt;p&gt;导入成功后，使用&amp;nbsp;&lt;code&gt;select * from users;&lt;/code&gt;&amp;nbsp;初步检查表中数据是否正确。&lt;/p&gt;

&lt;p&gt;扩展阅读：我们也可以使用 Workbench 这种免费的 MySQL 图形管理工具来操作，了解更多可看我写的这篇《&lt;a href="https://kalacloud.com/blog/mysql-workbench-tutorial/" rel="nofollow" target="_blank" title=""&gt;MySQL Workbench 中文指南&lt;/a&gt;》教程。&lt;/p&gt;
&lt;h2 id="三. 使用「卡拉云」一键导入导出数据"&gt;三。使用「卡拉云」一键导入导出数据&lt;/h2&gt;
&lt;p&gt;除了&lt;a href="https://kalacloud.com/blog/how-to-connect-to-a-mysql-server-remotely-with-mysql-workbench/" rel="nofollow" target="_blank" title=""&gt;MySQL / MariaDB 数据迁移&lt;/a&gt;这类适合使用终端命令操作外，大多数对 MySQL / MariaDB 数据导入导出操作还是为了数据展示、分析、协同共享等产品和运营层面的应用场景。&lt;/p&gt;

&lt;p&gt;比如后端工程师接到产品需求，协助导出某类数据等场景，如果这类需求频繁出现，推荐使用卡拉云，卡拉云是新一代低代码开发工具，免安装部署，可一键接入包括 MySQL 在内的常见数据库及 API。&lt;/p&gt;

&lt;p&gt;不仅可以像命令行一样灵活，还可根据自己的工作流，定制开发。无需繁琐的前端开发，只需要简单拖拽，即可快速搭建企业内部工具。&lt;strong&gt;数月的开发工作量，使用卡拉云后可缩减至数天，欢迎试用我们开发的&lt;a href="https://kalacloud.com/?utm_medium=register" rel="nofollow" target="_blank" title=""&gt;卡拉云&lt;/a&gt;。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/f98d898ab39be0bac391c36484bd77c8/71c1d/15-sql-all.png" title="" alt="卡拉云可快速接入常见的数据库及API"&gt;&lt;/p&gt;

&lt;p&gt;卡拉云可快速接入的常见数据库及 API&lt;/p&gt;

&lt;p&gt;卡拉云可根据公司工作流需求，轻松搭建数据看板，并且可分享给组内的小伙伴共享数据&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/5400a60956e16d655e0297c5d6e5a8d2/14-kalacloud-gif.gif" title="" alt="卡拉云仅需拖拽即可快速生成前端组件"&gt;&lt;/p&gt;

&lt;p&gt;仅需拖拽一键生成前端代码，简单一行代码即可映射数据到指定组件中。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://kalacloud.com/static/f5669b9993d738a2c3b4d4a72419ae85/d3e24/13-kalacloud-list.jpg" title="" alt="卡拉云可轻松导出各类数据格式"&gt;&lt;/p&gt;

&lt;p&gt;卡拉云可直接添加导出按钮，导出适用于各类分析软件的数据格式，方便快捷。&lt;a href="https://kalacloud.com/?utm_medium=register" rel="nofollow" target="_blank" title=""&gt;立即开通卡拉云&lt;/a&gt;，导入导出你的数据&lt;/p&gt;
&lt;h2 id="四.总结"&gt;四。总结&lt;/h2&gt;
&lt;p&gt;在本教程中，我们讲解了如何导入导出数据库至 SQL 文件和 CSV 文件。mysqldump 还有很多使用变化，你可以参考 mysqldump&amp;nbsp;&lt;a href="https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html" rel="nofollow" target="_blank" title=""&gt;官方文档&lt;/a&gt;了解更多。更多数据库相关教程可访问&amp;nbsp;&lt;a href="https://kalacloud.com/" rel="nofollow" target="_blank" title=""&gt;卡拉云&lt;/a&gt;&amp;nbsp;查看。&lt;/p&gt;</description>
      <author>HiJiangChuan</author>
      <pubDate>Wed, 10 Nov 2021 16:57:55 +0800</pubDate>
      <link>https://ruby-china.org/topics/41870</link>
      <guid>https://ruby-china.org/topics/41870</guid>
    </item>
    <item>
      <title>PostgreSQL 有没有能定时镜像线上数据到测试数据库的工具</title>
      <description>&lt;p&gt;计划把线上的数据脱敏后同步到测试环境一份儿，想请教各位 postgresql 有没有类似的工具，对实时性要求不高，能周期执行就行，比如定个时，每晚执行一次。&lt;/p&gt;</description>
      <author>v2up</author>
      <pubDate>Sun, 07 Nov 2021 20:01:19 +0800</pubDate>
      <link>https://ruby-china.org/topics/41854</link>
      <guid>https://ruby-china.org/topics/41854</guid>
    </item>
    <item>
      <title>null</title>
      <description>&lt;p&gt;null&lt;/p&gt;</description>
      <author>a112121788</author>
      <pubDate>Thu, 30 Sep 2021 19:17:04 +0800</pubDate>
      <link>https://ruby-china.org/topics/41733</link>
      <guid>https://ruby-china.org/topics/41733</guid>
    </item>
    <item>
      <title>TiDB &amp; ActiveRecord 避坑指南</title>
      <description>&lt;p&gt;最近为了调研 TiDB 与 ActiveRecord 的兼容程度，搭建了一个 CI 环境，用来跑 TiDB 和 ActiveRecord 的单元测试。把（TiDB 5.1，TiDB nightly）x（AR 6-1-stable，AR main）都已经跑通。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Finished in 841.391538s, 9.0695 runs/s, 29.2872 assertions/s.
7631 runs, 24642 assertions, 0 failures, 0 errors, 135 skips&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/hooopo/33db40bd-cab6-42dd-afea-f577e5912669.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;下面主要讲一下目前存在的问题、影响以及解决办法。&lt;/p&gt;
&lt;h2 id="Default Character Set and Collation"&gt;Default Character Set and Collation&lt;/h2&gt;
&lt;p&gt;TiDB 默认 &lt;code&gt;collation&lt;/code&gt; 行为与&lt;code&gt;MySQL&lt;/code&gt;不一致，TiDB 默认使用的是&lt;code&gt;utf8mb4_bin&lt;/code&gt;，会影响到字符串比较和匹配，MySQL 默认是大小写不敏感（Case-insensitive），TiDB 默认大小写敏感（Case-sensitive），如果想完全兼容 MySQL，需要额外的配置。&lt;/p&gt;

&lt;p&gt;TiDB 默认情况，Case-sensitive：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="k"&gt;COLLATION&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Charset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'utf8mb4'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-------------+---------+------+---------+----------+---------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Collation&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Charset&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Default&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Compiled&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Sortlen&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-------------+---------+------+---------+----------+---------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_bin&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="mi"&gt;46&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Yes&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Yes&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-------------+---------+------+---------+----------+---------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;NAMES&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_general_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;--由于new_collations_enabled_on_first_bootstrap 没有设置为true，collate设置并没有生效。&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="s1"&gt;'%a%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="s1"&gt;'A'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="s1"&gt;'%a%'&lt;/span&gt;
&lt;span class="c1"&gt;-----------+----------------&lt;/span&gt;
 &lt;span class="mi"&gt;0&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果想和 MySQL 默认行为完全兼容，解决方法是启动集群时开启 &lt;code&gt;new_collations_enabled_on_first_bootstrap = true&lt;/code&gt;，并且&lt;code&gt;collation&lt;/code&gt;设置为 &lt;code&gt;utf8mb4_general_ci&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SHOW&lt;/span&gt; &lt;span class="k"&gt;COLLATION&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;Charset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'utf8mb4'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;--------------------+---------+------+---------+----------+---------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Collation&lt;/span&gt;          &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Charset&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Id&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;Default&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Compiled&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Sortlen&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;--------------------+---------+------+---------+----------+---------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_bin&lt;/span&gt;        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="mi"&gt;46&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Yes&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Yes&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_general_ci&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="mi"&gt;45&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Yes&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;224&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Yes&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;       &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;--------------------+---------+------+---------+----------+---------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;tiup playground 简单配置演示：&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tiup playground &lt;span class="nt"&gt;--db&lt;/span&gt;.config config.toml
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;config.toml
new_collations_enabled_on_first_bootstrap &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;开启&lt;code&gt;new_collations_enabled_on_first_bootstrap&lt;/code&gt;之后的结果与 MySQL 默认行为一致：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;NAMES&lt;/span&gt; &lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_general_ci&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;mysql&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="s1"&gt;'%A%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+----------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'A'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt; &lt;span class="k"&gt;like&lt;/span&gt; &lt;span class="s1"&gt;'%A%'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+----------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;         &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;              &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+----------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;database.yml 修改：&lt;/p&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/config/database.yml b/config/database.yml
index fa6ab2d..e7226a8 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/config/database.yml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/config/database.yml
&lt;/span&gt;&lt;span class="p"&gt;@@ -12,10 +12,15 @@&lt;/span&gt;
 default: &amp;amp;default
   adapter: mysql2
   encoding: utf8mb4
&lt;span class="gi"&gt;+  collation: utf8mb4_general_ci
&lt;/span&gt;   pool: &amp;lt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;
   username: root
&lt;span class="gi"&gt;+  host: 127.0.0.1
+  port: 4000
&lt;/span&gt;   password:
&lt;span class="gd"&gt;-  socket: /tmp/mysql.sock
&lt;/span&gt;&lt;span class="gi"&gt;+  variables:
+    tidb_enable_noop_functions: ON
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Unsupported multi schema change"&gt;Unsupported multi schema change&lt;/h2&gt;
&lt;p&gt;TiDB 相关 issue：&lt;a href="https://github.com/pingcap/tidb/issues/14766" rel="nofollow" target="_blank"&gt;https://github.com/pingcap/tidb/issues/14766&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ActiveRecord 支持将多个 DDL 语句合并成一条：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BulkTest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;6.1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;change&lt;/span&gt;
    &lt;span class="n"&gt;change_table&lt;/span&gt; &lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;bulk: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integer&lt;/span&gt; &lt;span class="ss"&gt;:new_column1&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;  &lt;span class="ss"&gt;:new_column2&lt;/span&gt;
      &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;boolean&lt;/span&gt; &lt;span class="ss"&gt;:new_column3&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;产生 SQL 语句：&lt;/p&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;Migrating&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;BulkTest&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20210728090251&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;110&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;ALTER&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`posts`&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="nv"&gt;`new_column1`&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="nv"&gt;`new_column2`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;ADD&lt;/span&gt; &lt;span class="nv"&gt;`new_column3`&lt;/span&gt; &lt;span class="nb"&gt;tinyint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目前 TiDB 还不支持，相关 issue 正在开发中。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;20210728090251&lt;/span&gt; &lt;span class="no"&gt;BulkTest&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;migrating&lt;/span&gt; &lt;span class="o"&gt;=========================================&lt;/span&gt;
&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;change_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:bulk&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;rails&lt;/span&gt; &lt;span class="n"&gt;aborted!&lt;/span&gt;
&lt;span class="no"&gt;StandardError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;An&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;has&lt;/span&gt; &lt;span class="n"&gt;occurred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all&lt;/span&gt; &lt;span class="n"&gt;later&lt;/span&gt; &lt;span class="n"&gt;migrations&lt;/span&gt; &lt;span class="ss"&gt;canceled:

&lt;/span&gt;&lt;span class="no"&gt;Mysql2&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;Unsupported&lt;/span&gt; &lt;span class="n"&gt;multi&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="n"&gt;change&lt;/span&gt;
&lt;span class="sr"&gt;/Users/&lt;/span&gt;&lt;span class="n"&gt;hooopo&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ping&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;migrate&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;20210728090251&lt;/span&gt;&lt;span class="n"&gt;_bulk_test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="ss"&gt;:in&lt;/span&gt; &lt;span class="sb"&gt;`change'
/Users/hooopo/w/ping/myapp/bin/rails:5:in `&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'
/Users/hooopo/w/ping/myapp/bin/spring:10:in `block in &amp;lt;top (required)&amp;gt;'&lt;/span&gt;
&lt;span class="sr"&gt;/Users/&lt;/span&gt;&lt;span class="n"&gt;hooopo&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ping&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;myapp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="ss"&gt;:in&lt;/span&gt; &lt;span class="sb"&gt;`tap'
/Users/hooopo/w/ping/myapp/bin/spring:7:in `&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;临时解决方法是 patch mysql adapter 的 &lt;code&gt;supports_bulk_alter?&lt;/code&gt; 方法，patch 之后，Rails 会自动忽略 bulk 选项。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'active_record/connection_adapters/mysql2_adapter'&lt;/span&gt;
&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ConnectionAdapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mysql2Adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;supports_bulk_alter?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Unsupported get_lock and release_lock functions"&gt;Unsupported get_lock and release_lock functions&lt;/h2&gt;
&lt;p&gt;TiDB 相关 issue：&lt;a href="https://github.com/pingcap/tidb/issues/14994" rel="nofollow" target="_blank"&gt;https://github.com/pingcap/tidb/issues/14994&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ActiveRecord 里使用&lt;code&gt;get_lock&lt;/code&gt;和&lt;code&gt;release_lock&lt;/code&gt;来防止并发 migration 问题，大多数场景是不需要的，可以有两种办法解决：&lt;/p&gt;

&lt;p&gt;第一种是在&lt;code&gt;database.yml&lt;/code&gt;里设置变量&lt;code&gt;tidb_enable_noop_functions: ON&lt;/code&gt;：&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;default&lt;/span&gt;
  &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysql2&lt;/span&gt;
  &lt;span class="na"&gt;encoding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;utf8mb4&lt;/span&gt;
  &lt;span class="na"&gt;collation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;utf8mb4_general_ci&lt;/span&gt;
  &lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;127.0.0.1&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4000&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tidb_enable_noop_functions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ON&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;另外一种方法是 patch mysql adapter 的 &lt;code&gt;supports_advisory_locks?&lt;/code&gt; 方法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'active_record/connection_adapters/mysql2_adapter'&lt;/span&gt;
&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ConnectionAdapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Mysql2Adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;supports_advisory_locks?&lt;/span&gt;
    &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="Unsupported savepoint"&gt;Unsupported savepoint&lt;/h2&gt;
&lt;p&gt;TiDB 相关 issue：&lt;a href="https://github.com/pingcap/tidb/issues/6840" rel="nofollow" target="_blank"&gt;https://github.com/pingcap/tidb/issues/6840&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ActiveRecord 里 savepoint 的使用场景有两种，一种是清理构建测试集所产生的临时数据；另一种是实现嵌套事物。&lt;/p&gt;

&lt;p&gt;测试场景解决方案很简单，只需要设置 &lt;code&gt;use_transactional_tests&lt;/code&gt; 为 &lt;code&gt;false&lt;/code&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;ActiveRecord&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestCase&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveSupport&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TestCase&lt;/span&gt; &lt;span class="c1"&gt;#:nodoc:&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_transactional_tests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;false&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;嵌套事物场景，可以使用下面的的补丁来临时解决，TiDB 的 savepoint 功能已经在开发中，估计不久就会支持：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'active_record/connection_adapters/abstract/database_statements.rb'&lt;/span&gt;


&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ConnectionAdapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;DatabaseStatements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; 
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;requires_new: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;isolation: &lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;joinable: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;requires_new&lt;/span&gt;
      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt; &lt;span class="s2"&gt;"savepoint statement was used, but your db not support, ignored savepoint."&lt;/span&gt;
      &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt; &lt;span class="nb"&gt;caller&lt;/span&gt;
      &lt;span class="n"&gt;requires_new&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;requires_new&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;current_transaction&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;joinable?&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;isolation&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;TransactionIsolationError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"cannot set isolation when joining a transaction"&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="k"&gt;yield&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="n"&gt;transaction_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;within_new_transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;isolation: &lt;/span&gt;&lt;span class="n"&gt;isolation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;joinable: &lt;/span&gt;&lt;span class="n"&gt;joinable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Rollback&lt;/span&gt;
    &lt;span class="c1"&gt;# rollbacks are silently swallowed&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="show keys from  is not compatible with mysql"&gt;
&lt;code&gt;show keys from&lt;/code&gt;  is not compatible with mysql&lt;/h2&gt;
&lt;p&gt;TiDB 相关 issue： &lt;a href="https://github.com/pingcap/tidb/issues/26110" rel="nofollow" target="_blank"&gt;https://github.com/pingcap/tidb/issues/26110&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;由于 &lt;code&gt;show keys from&lt;/code&gt; 返回结果与 MySQL 不兼容，导致 schema.rb 导出索引数据不正确，对应用程序本身无影响。TiDB 最新代码已经修复了这个问题，只有旧版本会遇到。
可以通过设置 schema_format = :sql 来临时解决：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;YourApp&lt;/span&gt;
  &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Application&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_defaults&lt;/span&gt; &lt;span class="mf"&gt;6.0&lt;/span&gt;
    &lt;span class="c1"&gt;# Add this line:&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;active_record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;schema_format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:sql&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="others"&gt;others&lt;/h2&gt;
&lt;p&gt;下面这些 issue 是使用场景非常小，实际使用中极小概率会遇到的：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create table select from:&lt;a href="https://github.com/pingcap/tidb/issues/4754" rel="nofollow" target="_blank"&gt;https://github.com/pingcap/tidb/issues/4754&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;modify auto_increment:&lt;a href="https://github.com/pingcap/tidb/issues/10190" rel="nofollow" target="_blank"&gt;https://github.com/pingcap/tidb/issues/10190&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SPATIAL functions:&lt;a href="https://github.com/pingcap/tidb/issues/6347" rel="nofollow" target="_blank"&gt;https://github.com/pingcap/tidb/issues/6347&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Drop temporary table:&lt;a href="https://github.com/pingcap/tidb/issues/26278" rel="nofollow" target="_blank"&gt;https://github.com/pingcap/tidb/issues/26278&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="结论"&gt;结论&lt;/h2&gt;
&lt;p&gt;这就是跑完 ActiveRecord 大概 25000 个测试用例发现的所有问题。可以说 TiDB 和 ActiveRecord 集成完全没有问题了。接下来可能会提供 TiDB ActiveRecord Adapter，提供 TiDB 特有功能的支持。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CI 地址：&lt;a href="https://buildkite.com/hooopo" rel="nofollow" target="_blank"&gt;https://buildkite.com/hooopo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;ActiveRecord 测试集地址：&lt;a href="https://github.com/tidb-incubator/rails" rel="nofollow" target="_blank"&gt;https://github.com/tidb-incubator/rails&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>hooopo</author>
      <pubDate>Fri, 30 Jul 2021 15:57:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/41508</link>
      <guid>https://ruby-china.org/topics/41508</guid>
    </item>
    <item>
      <title>PostgreSQL 的 pg_try_advisory_xact_lock 正确使用姿势是什么</title>
      <description>&lt;p&gt;看到 pg_try_advisory_xact_lock（咨询锁）可以事务中免等待&lt;/p&gt;

&lt;p&gt;实际场景是啥？&lt;/p&gt;

&lt;p&gt;看很多描述是秒杀场景，那就拿秒杀来说，，一件商品，多人下单，那就是多个事物，只有一个人能下单（扣减库存）成功。&lt;/p&gt;

&lt;p&gt;然后没拿到锁的呢？直接前端抛出个抢购失败的异常？然后刷新页面一看还有库存？业务代码逻辑自动重试？重试多少次呢？&lt;/p&gt;

&lt;p&gt;因为很多场景其实并不都是极限秒杀场景（成百上千人抢），可能就是平常的一个商品，某个店铺搞了个活动（平台也不晓得）突然就大流量上来了。&lt;/p&gt;

&lt;p&gt;就是不固定不定时毫无预兆的普通商品抢购，自动重试次数少了，刷新页面看还有库存。自动重试次数多了，那还不如事物里锁这条数据呢。&lt;/p&gt;

&lt;p&gt;更具体的使用场景或姿势是啥？&lt;/p&gt;</description>
      <author>cevin</author>
      <pubDate>Sat, 03 Jul 2021 19:50:06 +0800</pubDate>
      <link>https://ruby-china.org/topics/41439</link>
      <guid>https://ruby-china.org/topics/41439</guid>
    </item>
    <item>
      <title>为什么 PostgreSQL 所有会话都在等待磁盘读操作，导致所有 sql 都无法响应？</title>
      <description>&lt;h2 id="问题"&gt;问题&lt;/h2&gt;
&lt;p&gt;在生产环境遇到一个问题，在请求量非高峰期间，所有 sql 语句都很慢，响应时间平均在 7s，最慢 10s。这个现象持续了大约 10s，才慢慢恢复正常。&lt;/p&gt;

&lt;p&gt;下图是 AWS RDS performance insight 的图，可以看到所有 pg 会话都在等待读磁盘（具体等待事件是 DataFileRead 和 buffer io）。这种问题最近一周出现一次。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/hjiangwen/3ba3f91b-1f76-47aa-9324-e3bf399528cf.png!large" title="" alt=""&gt;&lt;/p&gt;

&lt;p&gt;从&lt;a href="https://www.postgresql.org/docs/10/monitoring-stats.html" rel="nofollow" target="_blank" title=""&gt;pg 文档&lt;/a&gt;中，我们可以知道等待事件的具体含义&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DataFileWrite:    Waiting for a write to a relation data file.&lt;/li&gt;
&lt;li&gt;buffer_io: Waiting for I/O on a data page.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="解释"&gt;解释&lt;/h2&gt;
&lt;p&gt;我想这表示&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;pg 无法从共享内存的 shared_buffer 中找到对应的 table/index page&lt;/li&gt;
&lt;li&gt;那些执行 sql 的会话就得等 pg 从磁盘中读取 page（DataFileWrite event）&lt;/li&gt;
&lt;li&gt;然后写到 shared_buffer 中（buffer_io event）&lt;/li&gt;
&lt;li&gt;会话此时才能继续执行&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="猜测"&gt;猜测&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;是不是请求量超过了数据库 IO 极限？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;不是，我查了当时的 IOPS，没达到那台数据库的极限。而且当时的 IO 也比不上每日高峰，数据库的 IO 能力能满足每日高峰，所以一定也能满足当时的请求量。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;会不会是 shared_buffer 不足？&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可是每日高峰也能正常工作呀，如果 shared_buffer 不足，每日高峰应该也会遇到这个问题，所以我觉得不是内存配置问题。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;会不会是&lt;a href="https://www.percona.com/blog/2018/08/06/basic-understanding-bloat-vacuum-postgresql-mvcc/" rel="nofollow" target="_blank" title=""&gt;bloat tuple&lt;/a&gt;？

&lt;ul&gt;
&lt;li&gt;可能，我们的表行数挺多，而且经常被更新，的确会产生 dead tuple。而 pg 的定期 vacuum 只会把 dead tuple 空出来，一个用户在一个表的所有 rows 在 table page 文件会比较分散，导致当我们在一个表查用户的所有 rows 时，一个 query 要在磁盘上查很多 index/table page 才能找出所有 rows。&lt;/li&gt;
&lt;li&gt;如何验证？如何复现呢？🤔  通过 explain 来检查一个 query 要查多少 page？&lt;/li&gt;
&lt;li&gt;如果真的是这个问题，那我们可以用&lt;a href="https://github.com/reorg/pg_repack" rel="nofollow" target="_blank" title=""&gt;pg_repack&lt;/a&gt;来解决它。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="求助"&gt;求助&lt;/h3&gt;
&lt;p&gt;大家觉得是什么问题呢？&lt;/p&gt;</description>
      <author>hjiangwen</author>
      <pubDate>Sun, 27 Jun 2021 22:21:14 +0800</pubDate>
      <link>https://ruby-china.org/topics/41415</link>
      <guid>https://ruby-china.org/topics/41415</guid>
    </item>
  </channel>
</rss>
