<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Xupeng's blog]]></title>
  <link href="https://blog.xupeng.me/feed/" rel="self"/>
  <link href="https://blog.xupeng.me/"/>
  <updated>2016-05-11T03:08:07+00:00</updated>
  <id>https://blog.xupeng.me/</id>
  <author>
    <name><![CDATA[xupeng]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[团结紧张，严肃活泼]]></title>
    <link href="https://blog.xupeng.me/2016/03/06/jokes-ease-life/"/>
    <updated>2016-03-06T21:22:00+00:00</updated>
    <id>https://blog.xupeng.me/2016/03/06/jokes-ease-life</id>
    <content type="html"><![CDATA[<p><img src="https://blog.xupeng.me/downloads/jokes-ease-life/798-1.jpg"></p>

<!-- more -->


<p>今天，3 月 6 日，是豆瓣 11 岁生日，我做梦一样游了 798，看到大蔡司，还以为蔡司在这里开店了呢，</p>

<p><img src="https://blog.xupeng.me/downloads/jokes-ease-life/798-2.jpg">
后来才发现污龙了，</p>

<p><img src="https://blog.xupeng.me/downloads/jokes-ease-life/798-3.jpg">
然后莫名其妙来到中二街，</p>

<p><img src="https://blog.xupeng.me/downloads/jokes-ease-life/798-4.jpg">
稀里糊涂和自由女神合了个影，</p>

<p><img src="https://blog.xupeng.me/downloads/jokes-ease-life/798-5.jpg">
受到了严重警告，</p>

<p><img src="https://blog.xupeng.me/downloads/jokes-ease-life/798-6.jpg">
突然醒悟虽然圆外那么大，但可能眼界之内都是荒草。</p>

<hr />

<p>说完正经的，接下来是不正经的。</p>

<p>我基本上是一个严肃的人，尤其是前些年工作中讨论或者解决技术问题的时候，这倒也符合其他角色对工程师的一贯印象，想起来大概是 08 年的时候，在前东家做 Intacct 的一个项目，Intacct 的 CTO Aaron Harris 对我的其他评价我现在已经记不清了，但是 “Poker face” 我至今都还记得很清楚，形容得相当精准，翻译成中文的话，我想最接近的应该是「面瘫」。</p>

<p>去年参加了几次培训，遇到了一群全都不是工程师的人，第一天的时候我如坐针毡脸上自然也是 Poker 状，当然后面几天也没比第一天好到哪儿去，回来之后，有位同学看到我时不时在朋友圈里讲冷笑话，说：「哦，原来你不是我想象中的那种工程师。」</p>

<p>其实，工作和生活中都有很多很好玩儿的事情，谐音、双关、翻译、重复、断句、曲解、自黑，处处都是脑洞，我还能记起来最早亲历的冷笑话，比我被称作 Poker face 还要更早一些，是同事们教老外 BA 学汉语时说，立春，意思就是 Standing Spring，他们还把公司的中文名字「华美汉盛」翻译成 “China Beauty Handsome&#8221;，正是这些好玩儿的事情，让我时刻不能放松学习的工作和生活更多了些趣味。</p>

<p>最近我快三岁的女儿开始尝试自行解释一些她可能还不明白的词，比如因为大人长的大的所以叫大人，长的高就叫高人，长的小就叫小人，妈妈扫地扫的好所以叫扫地师，好像也很有道理的样子；再比如妈妈比较喜庆所以叫喜羊羊，爸爸走的慢所以叫慢羊羊，她自己很美所以叫美羊羊。</p>

<p>我也自行解释一下我搞不明白的一个词：严肃活泼，那就是一本正经讲笑话，嘻嘻哈哈过难关，劳逸结合把事儿办。</p>

<p>说起来汗颜，我前几天才刚刚读过一遍周爱民老师的《大道至简》，好书就是这种清清楚楚地把我已经知道、甚至已经在实践的事情再简洁地解释一遍，把我虽然有体悟但还有点模糊的道理一针见血地指出来，比如谈到到士气「三鼓而竭」这个话题，他说不认可甚至否定「积极、主动、敏锐、勇敢、主见」这些良好品质，是大多数公司无效率无创新的根源，「想法好不好」是技术问题，但态度是要认可的。恰好在几个月前，我觉得某个同事的想法太超脱实际而不认可他的那些主意，但同时也在无意中打击了背后的良好品质，读到这里，我真是直冒冷汗，这关乎中二，关乎眼界，关乎眼界太窄导致了中二。</p>

<p>最近两年多我个人最大的一个变化是，我在工作和生活中可以越来越坦然地承认和面对自己过往的错误，并在自黑中纠正，但在更早的时候，害怕犯错、害怕面对已经发生了的错误的确给我带来过很大的压力，要我说，以玩笑的态度的去释放压力，在这个过程中给了我最大的帮助。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[最容易被忽视的，是常识]]></title>
    <link href="https://blog.xupeng.me/2016/02/27/neglected-common-sense/"/>
    <updated>2016-02-27T13:40:00+00:00</updated>
    <id>https://blog.xupeng.me/2016/02/27/neglected-common-sense</id>
    <content type="html"><![CDATA[<p>同事最近在拆分数据库和清理数据，这当中有一些趣事，比如在拆分一个叫 luz 的集群时，新拆出的集群被命名为了 lua，可以从至少两个角度来理解这个名字，最显而易见的它是一门语言，除此之外它还代表从 z 到 a 的新一轮轮回；再比如拆分另一个叫 eag 的集群时，新集群被命名为 eager，不过它诞生时的含义其实是「eag 的 er 砸」。</p>

<!-- more -->


<p>也有一些哭笑不得的问题，比如发现有些应用的数据清理 cron 曾经有日子没被成功执行，导致数据文件的体积相比它应有的大小暴涨了一个数量级；还有比如发现 <a href="https://getsentry.com/">Sentry</a> 的 <a href="https://github.com/getsentry/sentry/blob/8.1.2/docs/nodestore.rst">nodestore_node</a> 表涨到了几百 GB，检查的时候看到 nodestore 的 schema：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='mysql'><span class='line'><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="ss">`nodestore_node`</span> <span class="p">(</span>
</span><span class='line'>    <span class="ss">`id`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">40</span><span class="p">)</span> <span class="k">NOT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`data`</span> <span class="kt">longtext</span> <span class="k">NOT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`timestamp`</span> <span class="kt">datetime</span> <span class="k">NOT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="ss">`id`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`nodestore_node_d80b9c9a`</span> <span class="p">(</span><span class="ss">`timestamp`</span><span class="p">)</span>
</span><span class='line'><span class="p">)</span> <span class="kp">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span> <span class="k">DEFAULT</span> <span class="kp">CHARSET</span><span class="o">=</span><span class="n">utf8</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<h2>它都有哪些问题呢？</h2>

<p>脱离业务场景，孤立地看这个 schema，除了这三点可疑之处外其实并没有太多值得挑剔的：</p>

<ol>
<li>Primary key 比较长（40 个字符），通常是一个不当设计的预警信号。</li>
<li>写入纪录的顺序和主键索引序可能不同，潜在的问题是影响写入性能，并可能导致数据页碎片化进一步影响读性能。</li>
<li>timestamp 字段使用了 MySQL 的关键字。</li>
</ol>


<p>那么再了解一点点业务，通过阅读 Sentry 这部分的代码可以了解到这些：</p>

<ol>
<li>id 要存储的原始数据是 UUID version 4，16 字节，但在存储之前用 base64          编码为可打印字符串，编码后的长度为 24 字节。</li>
<li>data 所存储的数据是把结构化数据序列化，之后用 zlib 压缩，再把压缩之后的数据使用 base64 编码为可打印字符串。</li>
<li>timestamp 字段是数据写入时的时间，这个字段的索引用于按时间删除旧数据。</li>
</ol>


<p>考虑业务的需要，这个 schema 还有哪些问题呢？ id 字段使用了 utf8 编码（在 MySQL 中是 1-3 字节长度的变长编码），最大长度是 40 字符，id 字段是主键，在索引中以最大宽度对齐，再加上 varchar 类型最大长度标示 2 字节，也就是 40 * 3 + 2 = 122 字节，不仅在主键中如此，在二级索引 <code>nodestore_node_d80b9c9a</code> 中也是如此。</p>

<h2>那么怎么改进呢？</h2>

<ol>
<li>由于业务上 id 是 16 字节 的 UUID version 4，但编码为 24 字节的可打印字符串，因此在不修改业务代码的前提下，可以对业务透明地把 id 列的定义修改为 <code>id char(24) CHARACTER SET latin1 NOT NULL</code>，这样在索引中的宽度就可以从 122 字节减小为 24 字节。</li>
<li>如果对业务代码做小幅重构，可以使用 binary(16) 来存储 UUID version 4，可以进一步减小为 16 字节。</li>
<li>data 字段所存储数据的长度大多分布在几 KB 到几十 KB 之间，以 20KB(20480) 为例，base64 编码之后的长度为 27308，比未编码数据增长了 33+%，如果可以牺牲对人类的可读性（实际上人类也直读不了 base64 编码的字符串），直接存储 zlib 压缩之后的二进制数据，就可以省掉这 33% 的空间浪费，这一部分的浪费在整个系统中所占的比重相当可观。</li>
<li>在 Sentry 的场景下，nodestore 是典型的写多读少，如果再加入一列自增列作为 primary key，把 id 改为有唯一性约束的二级索引，可以提升写入性能、减轻数据页的碎片化，同时还能进一步<strong>节省</strong>空间。</li>
<li>使用自增列（记作 <code>dummy_id</code>）作为 primary key 之后，在这个场景下 timestamp 字段实际上和 <code>dummy_id</code> 同序，按时间删除旧数据的需求就可以不必借助 timestamp 字段的索引，使用笨笨的二分法找到需要的时间边界，然后按对应的 <code>dummy_id</code> 作为条件来删除即可，每行纪录可以节省 24 字节。</li>
</ol>


<h2>做到这些有多难呢？</h2>

<p>回顾一下上面所提到的问题和相应的优化手段，可以发现所需要的只是在理解业务的基础上，对数据库的工作方式也有基本的理解，但更实际的情况是，大量的产品开发者在编写 Model 代码时随手就会写下 max_length=255，完全没有意识到产生的列定义会是 varchar(255)，或者压根儿没有意识到这么做会有什么影响，几年前我自己在做产品开发的工作时也是这样的习惯。相对来说，我愿意相信这里写了 <a href="https://github.com/getsentry/sentry/blob/8.1.2/src/sentry/nodestore/django/models.py#L21">max_length=40</a> 而不是 max_length=255 是一个经过深思熟虑了的决定。</p>

<p>要做到对数据库和索引的工作方式有基本的理解，难吗？很多同学会觉得不就是读一读文档吗没有什么技术含量啊，事实也正是如此，只要能阅读和理解文档，加上必要的动手实践，都能有超越类似场景要求的理解，比掌握一门语言要简单太多，既不需要高于普通人的智商，也不需要加班加点地埋头苦干，所需要的仅仅是踏实的态度和一点耐心，再加上一点思考。</p>

<h2>再举个例子</h2>

<p>这是随手从某著名产品中摘出的 schema，就不在这里做更多的问题分析了，不过我相信仅仅依靠上面提到的几点常识，就能看到很多问题，这多多少少能进一步说明，优秀的产品一样会有糟糕的实现，但是，这些产品不都好好的是吗？是不是应该再思考一下技术的价值，以及技术和产品的关系呢？😂</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
<span class='line-number'>43</span>
<span class='line-number'>44</span>
<span class='line-number'>45</span>
<span class='line-number'>46</span>
<span class='line-number'>47</span>
<span class='line-number'>48</span>
<span class='line-number'>49</span>
<span class='line-number'>50</span>
<span class='line-number'>51</span>
<span class='line-number'>52</span>
<span class='line-number'>53</span>
<span class='line-number'>54</span>
<span class='line-number'>55</span>
<span class='line-number'>56</span>
<span class='line-number'>57</span>
<span class='line-number'>58</span>
<span class='line-number'>59</span>
<span class='line-number'>60</span>
<span class='line-number'>61</span>
<span class='line-number'>62</span>
<span class='line-number'>63</span>
<span class='line-number'>64</span>
<span class='line-number'>65</span>
<span class='line-number'>66</span>
<span class='line-number'>67</span>
<span class='line-number'>68</span>
<span class='line-number'>69</span>
<span class='line-number'>70</span>
<span class='line-number'>71</span>
</pre></td><td class='code'><pre><code class='mysql'><span class='line'><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="ss">`storage_blobs`</span> <span class="p">(</span>
</span><span class='line'>    <span class="ss">`id`</span> <span class="kt">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="no">NULL</span> <span class="kp">AUTO_INCREMENT</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`oid`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`size`</span> <span class="kt">bigint</span><span class="p">(</span><span class="mi">20</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`created_at`</span> <span class="kt">datetime</span> <span class="k">NOT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="ss">`id`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">UNIQUE</span> <span class="k">KEY</span> <span class="ss">`index_storage_blobs_on_oid`</span> <span class="p">(</span><span class="ss">`oid`</span><span class="p">)</span>
</span><span class='line'><span class="p">)</span> <span class="kp">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span> <span class="kp">AUTO_INCREMENT</span><span class="o">=</span><span class="mi">2580</span> <span class="k">DEFAULT</span> <span class="kp">CHARSET</span><span class="o">=</span><span class="n">utf8</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="ss">`users`</span> <span class="p">(</span>
</span><span class='line'>    <span class="ss">`id`</span> <span class="kt">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="no">NULL</span> <span class="kp">AUTO_INCREMENT</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`login`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">NOT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`crypted_password`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">40</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`salt`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">40</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`created_at`</span> <span class="kt">datetime</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`updated_at`</span> <span class="kt">datetime</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`remember_token`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`remember_token_expires_at`</span> <span class="kt">datetime</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`wants_email`</span> <span class="kt">tinyint</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="s1">&#39;1&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`disabled`</span> <span class="kt">tinyint</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="s1">&#39;0&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`plan`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`billed_on`</span> <span class="kt">date</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`auth_token`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`upgrade_ignore`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`upgrade_accept`</span> <span class="kt">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`role`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`billing_attempts`</span> <span class="kt">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="s1">&#39;0&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`spammy`</span> <span class="kt">tinyint</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="s1">&#39;0&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`last_ip`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`plan_duration`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`bouncing_email`</span> <span class="kt">tinyint</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`billing_extra`</span> <span class="kt">text</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`gift`</span> <span class="kt">tinyint</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`type`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="s1">&#39;User&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`last_read_broadcast_id`</span> <span class="kt">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`raw_data`</span> <span class="kt">blob</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`referral_code`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`billing_type`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="s1">&#39;card&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`braintree_customer_id`</span> <span class="kt">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`bcrypt_auth_token`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`suspended_at`</span> <span class="kt">datetime</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`organization_billing_email`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`gravatar_email`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`time_zone_name`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`session_fingerprint`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`token_secret`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">40</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`require_2fa`</span> <span class="kt">tinyint</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`restrict_oauth_applications`</span> <span class="kt">tinyint</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`spammy_reason`</span> <span class="kt">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="k">DEFAULT</span> <span class="no">NULL</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`seats`</span> <span class="kt">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="no">NULL</span> <span class="k">DEFAULT</span> <span class="s1">&#39;0&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`split_diff_preferred`</span> <span class="kt">tinyint</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="k">NOT</span> <span class="no">NULL</span> <span class="k">DEFAULT</span> <span class="s1">&#39;0&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`custom_volume_discount`</span> <span class="kt">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="no">NULL</span> <span class="k">DEFAULT</span> <span class="s1">&#39;0&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="ss">`tenant_id`</span> <span class="kt">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="no">NULL</span> <span class="k">DEFAULT</span> <span class="s1">&#39;1&#39;</span><span class="p">,</span>
</span><span class='line'>    <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="ss">`id`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">UNIQUE</span> <span class="k">KEY</span> <span class="ss">`unique_index_users_on_login`</span> <span class="p">(</span><span class="ss">`login`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">UNIQUE</span> <span class="k">KEY</span> <span class="ss">`unique_index_users_on_login_and_tenant_id`</span> <span class="p">(</span><span class="ss">`login`</span><span class="p">,</span><span class="ss">`tenant_id`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_billed_on`</span> <span class="p">(</span><span class="ss">`billed_on`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_type`</span> <span class="p">(</span><span class="ss">`type`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_referral_id`</span> <span class="p">(</span><span class="ss">`referral_code`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_last_ip`</span> <span class="p">(</span><span class="ss">`last_ip`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_plan_and_billing_type_and_type`</span> <span class="p">(</span><span class="ss">`plan`</span><span class="p">,</span><span class="ss">`billing_type`</span><span class="p">,</span><span class="ss">`type`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_updated_at`</span> <span class="p">(</span><span class="ss">`updated_at`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_spammy`</span> <span class="p">(</span><span class="ss">`spammy`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_braintree_customer_id`</span> <span class="p">(</span><span class="ss">`braintree_customer_id`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_suspended_at`</span> <span class="p">(</span><span class="ss">`suspended_at`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`by_billing_email`</span> <span class="p">(</span><span class="ss">`organization_billing_email`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`by_gravatar_email`</span> <span class="p">(</span><span class="ss">`gravatar_email`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_role`</span> <span class="p">(</span><span class="ss">`role`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_created_at`</span> <span class="p">(</span><span class="ss">`created_at`</span><span class="p">),</span>
</span><span class='line'>    <span class="k">KEY</span> <span class="ss">`index_users_on_tenant_id`</span> <span class="p">(</span><span class="ss">`tenant_id`</span><span class="p">)</span>
</span><span class='line'><span class="p">)</span> <span class="kp">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span> <span class="kp">AUTO_INCREMENT</span><span class="o">=</span><span class="mi">198</span> <span class="k">DEFAULT</span> <span class="kp">CHARSET</span><span class="o">=</span><span class="n">utf8</span><span class="p">;</span>
</span></code></pre></td></tr></table></div></figure>


<p>我昨天由这个问题吐槽了一下 Sentry，引来了一些讨论和建议，比如大句哥哥和 XTAo 说 Sentry 官方推荐用 PostgreSQL，不过就这个具体的问题来说，如此设计恐怕在 PostgreSQL 下也不是好的实践，更重要的不在于用什么，而是怎么用好它，话说回来，要把 Sentry 用好，也得对 Sentry 有更多的了解和理解才行。</p>

<h2>最后，推荐几则不错的文档和书</h2>

<ul>
<li><a href="http://dev.mysql.com/doc/refman/5.6/en/">MySQL Reference Manual</a></li>
<li><a href="http://use-the-index-luke.com/">Use The Index, Luke!</a></li>
<li><a href="http://modern-sql.com/">Modern SQL</a></li>
<li><a href="https://book.douban.com/subject/4898636/">SQL Antipatterns</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[MySQL "replace into" 的坑]]></title>
    <link href="https://blog.xupeng.me/2013/10/11/mysql-replace-into-trap/"/>
    <updated>2013-10-11T07:23:00+00:00</updated>
    <id>https://blog.xupeng.me/2013/10/11/mysql-replace-into-trap</id>
    <content type="html"><![CDATA[<p>MySQL 对 SQL 有很多扩展，有些用起来很方便，但有一些被误用之后会有性能问题，还会有一些意料之外的副作用，比如 REPLACE INTO。</p>

<!-- more -->


<p>比如有这样一张表：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>CREATE TABLE `auto` (
</span><span class='line'>  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
</span><span class='line'>  `k` int(10) unsigned NOT NULL,
</span><span class='line'>  `v` varchar(100) DEFAULT NULL,
</span><span class='line'>  `extra` varchar(200) DEFAULT NULL,
</span><span class='line'>  PRIMARY KEY (`id`),
</span><span class='line'>  UNIQUE KEY `uk_k` (`k`)
</span><span class='line'>) ENGINE=InnoDB DEFAULT CHARSET=latin1</span></code></pre></td></tr></table></div></figure>


<p>auto 表有一个自增的 id 字段作为主键，字段 k 有 UNIQUE KEY 做唯一性约束。写入几条记录之后会是这样：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>xupeng@diggle7:3600(dba_m) [dba] mysql&gt; INSERT INTO auto (k, v, extra) VALUES (1, '1', 'extra 1'), (2, '2', 'extra 2'), (3, '3', 'extra 3');
</span><span class='line'>Query OK, 3 rows affected (0.01 sec)
</span><span class='line'>Records: 3  Duplicates: 0  Warnings: 0
</span><span class='line'>
</span><span class='line'>xupeng@diggle7:3600(dba_m) [dba] mysql&gt; SHOW CREATE TABLE auto\G
</span><span class='line'>*************************** 1. row ***************************
</span><span class='line'>       Table: auto
</span><span class='line'>Create Table: CREATE TABLE `auto` (
</span><span class='line'>  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
</span><span class='line'>  `k` int(10) unsigned NOT NULL,
</span><span class='line'>  `v` varchar(100) DEFAULT NULL,
</span><span class='line'>  `extra` varchar(200) DEFAULT NULL,
</span><span class='line'>  PRIMARY KEY (`id`),
</span><span class='line'>  UNIQUE KEY `uk_k` (`k`)
</span><span class='line'>) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
</span><span class='line'>1 row in set (0.01 sec)
</span><span class='line'>
</span><span class='line'>xupeng@diggle7:3600(dba_m) [dba] mysql&gt; SELECT * FROM auto;
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>| id | k | v    | extra   |
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>|  1 | 1 | 1    | extra 1 |
</span><span class='line'>|  2 | 2 | 2    | extra 2 |
</span><span class='line'>|  3 | 3 | 3    | extra 3 |
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>3 rows in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>在 slave 节点上是和 master 一致的：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>xupeng@diggle8:3600(dba_s) [dba] mysql&gt; SELECT * FROM auto;
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>| id | k | v    | extra   |
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>|  1 | 1 | 1    | extra 1 |
</span><span class='line'>|  2 | 2 | 2    | extra 2 |
</span><span class='line'>|  3 | 3 | 3    | extra 3 |
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>3 rows in set (0.00 sec)
</span><span class='line'>
</span><span class='line'>xupeng@diggle8:3600(dba_s) [dba] mysql&gt; SHOW CREATE TABLE auto\G
</span><span class='line'>*************************** 1. row ***************************
</span><span class='line'>       Table: auto
</span><span class='line'>Create Table: CREATE TABLE `auto` (
</span><span class='line'>  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
</span><span class='line'>  `k` int(10) unsigned NOT NULL,
</span><span class='line'>  `v` varchar(100) DEFAULT NULL,
</span><span class='line'>  `extra` varchar(200) DEFAULT NULL,
</span><span class='line'>  PRIMARY KEY (`id`),
</span><span class='line'>  UNIQUE KEY `uk_k` (`k`)
</span><span class='line'>) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1
</span><span class='line'>1 row in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>可以看到，写入三条记录之后，auto 表的 AUTO_INCREMENT 增长为 4，也就是说下一条不手工为 id 指定值的记录，id 字段的值会是 4。</p>

<p>接下来使用 REPLACE INTO 来写入一条记录：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>xupeng@diggle7:3600(dba_m) [dba] mysql&gt; REPLACE INTO auto (k, v) VALUES (1, '1-1');
</span><span class='line'>Query OK, 2 rows affected (0.01 sec)
</span><span class='line'>
</span><span class='line'>xupeng@diggle7:3600(dba_m) [dba] mysql&gt; SELECT * FROM auto;
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>| id | k | v    | extra   |
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>|  2 | 2 | 2    | extra 2 |
</span><span class='line'>|  3 | 3 | 3    | extra 3 |
</span><span class='line'>|  4 | 1 | 1-1  | NULL    |
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>3 rows in set (0.00 sec)
</span><span class='line'>
</span><span class='line'>xupeng@diggle7:3600(dba_m) [dba] mysql&gt; SHOW CREATE TABLE auto\G
</span><span class='line'>*************************** 1. row ***************************
</span><span class='line'>       Table: auto
</span><span class='line'>Create Table: CREATE TABLE `auto` (
</span><span class='line'>  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
</span><span class='line'>  `k` int(10) unsigned NOT NULL,
</span><span class='line'>  `v` varchar(100) DEFAULT NULL,
</span><span class='line'>  `extra` varchar(200) DEFAULT NULL,
</span><span class='line'>  PRIMARY KEY (`id`),
</span><span class='line'>  UNIQUE KEY `uk_k` (`k`)
</span><span class='line'>) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1
</span><span class='line'>1 row in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>可以看到 MySQL 说 &#8220;2 rows affected&#8221;，可是明明是只写一条记录，为什么呢？这是因为 MySQL 在执行 REPLACE INTO auto (k) VALUES (1) 时首先尝试 INSERT INTO auto (k) VALUES (1)，但由于已经存在一条 k=1 的记录，发生了 duplicate key error，于是 MySQL 会先删除已有的那条 k=1 即 id=1 的记录，然后重新写入一条新的记录。</p>

<p>这时候 slave 上出现了诡异的问题：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>xupeng@diggle8:3600(dba_s) [dba] mysql&gt; SHOW CREATE TABLE auto\G
</span><span class='line'>*************************** 1. row ***************************
</span><span class='line'>       Table: auto
</span><span class='line'>Create Table: CREATE TABLE `auto` (
</span><span class='line'>  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
</span><span class='line'>  `k` int(10) unsigned NOT NULL,
</span><span class='line'>  `v` varchar(100) DEFAULT NULL,
</span><span class='line'>  `extra` varchar(200) DEFAULT NULL,
</span><span class='line'>  PRIMARY KEY (`id`),
</span><span class='line'>  UNIQUE KEY `uk_k` (`k`)
</span><span class='line'>) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1</span></code></pre></td></tr></table></div></figure>


<p>可以知道，当前表内数据 id 字段的最大值是 4，AUTO_INCREMENT 应该为 5，但在 slave 上 AUTO_INCREMENT 却并未更新，这会有什么问题呢？把这个 slave 提升为 master 之后，由于 AUTO_INCREMENT 比实际的 next id 还要小，写入新记录时就会发生 duplicate key error，每次冲突之后 AUTO_INCREMENT += 1，直到增长为 max(id) + 1 之后才能恢复正常：</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>xupeng@diggle8:3600(dba_s) [dba] mysql&gt; REPLACE INTO auto (k, v) VALUES (4, '4');
</span><span class='line'>ERROR 1062 (23000): Duplicate entry '4' for key 'PRIMARY'
</span><span class='line'>xupeng@diggle8:3600(dba_s) [dba] mysql&gt; REPLACE INTO auto (k, v) VALUES (5, '5');
</span><span class='line'>Query OK, 1 row affected (0.00 sec)
</span><span class='line'>
</span><span class='line'>xupeng@diggle8:3600(dba_s) [dba] mysql&gt; SELECT * FROM auto;
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>| id | k | v    | extra   |
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>|  2 | 2 | 2    | extra 2 |
</span><span class='line'>|  3 | 3 | 3    | extra 3 |
</span><span class='line'>|  4 | 1 | 1-1  | NULL    |
</span><span class='line'>|  5 | 5 | 5    | NULL    |
</span><span class='line'>+----+---+------+---------+
</span><span class='line'>4 rows in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>没有预料到 MySQL 在数据冲突时实际上是删掉了旧记录，再写入新记录，这是使用 REPLACE INTO 时最大的一个误区，拿之前的例子来说，执行完 REPLACE INTO auto (k, v) VALUES (1, &#8216;1-1&#8217;) 之后，由于新写入记录时并未给 extra 字段指定值，原记录 extra 字段的值就「丢失」了，而通常这并非是业务上所预期的，更常见的需求实际上是，当存在 k=1 的记录时，就把 v 字段的值更新为 &#8216;1-1&#8217;，其他未指定的字段则保持原状，而满足这一需求的 MySQL 方言是 INSERT INTO auto (k, v) VALUES (1, &#8216;1-1&#8217;) ON DUPLICATE KEY UPDATE v=VALUES(v);</p>

<p>鉴于此，很多使用 REPLACE INTO 的场景，实际上需要的是 INSERT INTO … ON DUPLICATE KEY UPDATE，在正确理解 REPLACE INTO 行为和副作用的前提下，谨慎使用 REPLACE INTO。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[MySQLdb 参数处理的坑]]></title>
    <link href="https://blog.xupeng.me/2013/09/25/mysqldb-args-processing/"/>
    <updated>2013-09-25T07:23:00+00:00</updated>
    <id>https://blog.xupeng.me/2013/09/25/mysqldb-args-processing</id>
    <content type="html"><![CDATA[<p>前几天又有同事掉进了给 SQL 的 IN 条件传参的坑，就像 SELECT col1, col2 FROM table1 WHERE id IN (1, 2, 3) 这类 SQL，如果是一个可变的列表作为 IN 的参数，那这个参数应该怎么传呢？</p>

<!-- more -->


<p>我见过至少这么几种：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">id_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span><span class='line'><span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s">&#39;SELECT col1, col2 FROM table1 WHERE id IN (</span><span class="si">%s</span><span class="s">)&#39;</span><span class="p">,</span> <span class="n">id_list</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>这种方式是语法错误的，原因是 MySQLdb 做字符串格式化时占位符和参数个数不匹配。</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">id_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span><span class='line'><span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s">&#39;SELECT col1, col2 FROM table1 WHERE id IN (</span><span class="si">%s</span><span class="s">)&#39;</span><span class="p">,</span> <span class="p">(</span><span class="n">id_list</span><span class="p">,))</span>
</span></code></pre></td></tr></table></div></figure>


<p>这种方式语法是正确的，但语义是错误的，因为生成的 SQL 是 SELECT col1, col2 FROM table1 WHERE id IN ((&#8216;1&#8217;, &#8216;2&#8217;, &#8216;3&#8217;))</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">id_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span><span class='line'><span class="n">id_list</span> <span class="o">=</span> <span class="s">&#39;,&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">id_list</span><span class="p">])</span>
</span><span class='line'><span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s">&#39;SELECT col1, col2 FROM table1 WHERE id IN (</span><span class="si">%s</span><span class="s">)&#39;</span><span class="p">,</span> <span class="n">id_list</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>这种方式语义也是错误的，因为生成的 SQL 是 SELECT col1, col2 FROM table1 WHERE id IN (&#8216;1,2,3&#8217;)</p>

<p>这三种是第一次使用 MySQLdb 给 IN 传参时犯的最多的错误，大多数人遇到第一种错和掉进后两个坑之后，转而采用了下面的方式：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">id_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span><span class='line'><span class="n">id_list</span> <span class="o">=</span> <span class="s">&#39;,&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="nb">str</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">id_list</span><span class="p">])</span>
</span><span class='line'><span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s">&#39;SELECT col1, col2 FROM table1 WHERE id IN (</span><span class="si">%s</span><span class="s">)&#39;</span> <span class="o">%</span> <span class="n">id_list</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>这个方式对于可信的参数(比如自己生成的列表：<code>range(1, 10, 2)</code>)来说可以用，但由于参数未经 escape，对于从用户端接受的不可信参数来说，存在 SQL 注入的风险。</p>

<p>严防 SQL 注入的问题时刻都不能松懈，于是就有了这样的改进版本：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">id_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span><span class='line'><span class="n">id_list</span> <span class="o">=</span> <span class="s">&#39;,&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="nb">str</span><span class="p">(</span><span class="n">cursor</span><span class="o">.</span><span class="n">connection</span><span class="o">.</span><span class="n">literal</span><span class="p">(</span><span class="n">i</span><span class="p">))</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">id_list</span><span class="p">])</span>
</span><span class='line'><span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s">&#39;SELECT col1, col2 FROM table1 WHERE id IN (</span><span class="si">%s</span><span class="s">)&#39;</span> <span class="o">%</span> <span class="n">id_list</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>这个方式控制了 SQL 注入问题的滋生，但由于 <code>cursor.connection.literal</code> 是内部接口，并不推荐从外部使用。</p>

<p>然后就有了这样的方式：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">id_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span><span class='line'><span class="n">arg_list</span> <span class="o">=</span> <span class="s">&#39;,&#39;</span><span class="o">.</span><span class="n">join</span><span class="p">([</span><span class="s">&#39;</span><span class="si">%s</span><span class="s">&#39;</span><span class="p">]</span> <span class="o">*</span> <span class="nb">len</span><span class="p">(</span><span class="n">id_list</span><span class="p">))</span>
</span><span class='line'><span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s">&#39;SELECT col1, col2 FROM table1 WHERE id IN (</span><span class="si">%s</span><span class="s">)&#39;</span> <span class="o">%</span> <span class="n">arg_list</span><span class="p">,</span> <span class="n">id_list</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>


<p>这个方式是先生成与参数个数相同的 %s 占位，拼出 &#8216;SELECT col1, col2 FROM table1 WHERE id IN (%s,%s,%s)&#8217; 这样的 SQL，然后使用安全的方式来传参。</p>

<p>就是想传一个参数而已，怎么会这么麻烦呢？触令丧惨！</p>

<p><strong><span style="color: red;">更正：以下划线内容为未经充分测试的错误结论，仅做记录：</span></strong></p>

<p><strike>
一直以为 MySQLdb 是不支持给 IN 传参的，直到这次又有同事掉坑我才读了 MySQLdb escape 部分的代码，然后发现，MySQLdb 是在很多类型的 Python object 和 SQL 支持的类型之间做自动转换的，比如 MySQLdb 会对 list 和 tuple 内的元素逐个进行 escape，生成一个 tuple，因此这才是正确的给 IN 传参的方式：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">id_list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span><span class='line'><span class="n">cursor</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s">&#39;SELECT col1, col2 FROM table1 WHERE id IN </span><span class="si">%s</span><span class="s">&#39;</span><span class="p">,</span> <span class="p">(</span><span class="n">id_list</span><span class="p">,))</span>
</span></code></pre></td></tr></table></div></figure>


<p>可以把 MySQLdb 处理参数的过程简化描述为：</p>

<ol>
<li>对参数 (id_list,) 做 escape 得到 ((&#8216;1&#8217;, &#8216;2&#8217;, &#8216;3&#8217;),)</li>
<li>用 escape 过的参数对 SQL 进行格式化：&#8217;SELECT col1, col2 FROM table1 WHERE id IN %s&#8217; % ((&#8216;1&#8217;, &#8216;2&#8217;, &#8216;3&#8217;),)，得到完整 SQL：&#8217;SELECT col1, col2 FROM table1 WHERE id IN (&#8216;1&#8217;, &#8216;2&#8217;, &#8216;3&#8217;)</li>
</ol>


<p>整理一下口诀：IN 的参数和其他参数一样，是一个整体，就要不要对属于参数一部分的 <code>()</code> 念念不忘了……
</strike></p>

<p>总结一下评论中对这个方法提出的问题：</p>

<ol>
<li>如果参数列表只有一个元素，比如 <code>cursor.execute('SELECT col1, col2 FROM table1 WHERE id IN %s', ([1],))</code>，生成的 SQL 是 <code>SELECT col1, col2 FROM  table1 WHERE id IN ('1',)</code>，是语法错误的</li>
<li>对列表内元素做 esacpe 时增加的引号会被留下，如果列表元素是字符串，结果会是错误的，比如 <code>cursor.execute('SELECT col1, col2 FROM  table1 WHERE id IN %s', (["1", "2"],))</code> 生成的 SQL 是 <code>SELECT col1, col2 FROM  table1 WHERE id IN ("'1'", "'2'")</code>，而对于数字参数恰好能正确工作的原因是，在执行 SQL 时如果列定义是 int 而传参为字符串，MySQL 会做隐式类型转换（<a href="http://dev.mysql.com/doc/refman/5.5/en/type-conversion.html">Type Conversion in Expression Evaluation</a>）。</li>
</ol>


<p>MySQLdb 支持对各种类型的 Python object 进行转换和 escape，感兴趣的同学可以看看 <code>MySQLdb.converters</code> 和 <code>_mysql.c</code> 中 <code>*_escape*</code> 系列的函数，另外 MySQLdb 也支持自定义转换规则，参见 <code>MySQLdb.connect</code> 的 <code>conv</code> 参数。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[在 Linux 下追溯进程的发起者]]></title>
    <link href="https://blog.xupeng.me/2013/09/24/find-process-initiator-on-linux/"/>
    <updated>2013-09-24T16:13:00+00:00</updated>
    <id>https://blog.xupeng.me/2013/09/24/find-process-initiator-on-linux</id>
    <content type="html"><![CDATA[<p>在 Linux 下要确认一个进程的发起者身份，比如用户 tom 登录系统，<code>sudo su -</code> 到 root，然后执行了脚本 <code>hey.sh</code>，要想在 <code>hey.sh</code> 中追溯到发起进程的是 tom 这个用户，并不是很容易做到准确无误，花了点时间，找到了一个相对靠谱的方式。</p>

<!-- more -->


<p>先说说遇到的几个问题：</p>

<ol>
<li>用户登录之后，使用 <code>sudo hey.sh</code> 的方式执行 <code>hey.sh</code>，倒是可以通过环境变量 <code>SUDO_USER</code> 获取到 <code>sudo</code> 之前的用户，但没有办法解决上面提到的 <code>sudo su -</code> 这样不继承之前用户环境变量的问题。</li>
<li>可以用 <code>tty</code> 命令获取当前进程的 controlling terminal，然后通过 controlling terminal 文件的属主来确认登录用户，这可以解决 1 中提到的问题，但是如果当前进程的父进程是 daemon 或者关掉了标准输入，就没有 controlling terminal。</li>
<li>可以通过读取 <code>/proc/self/loginuid</code> 获取当前进程的登录用户 ID，对于没有 controlling terminal 的进程也可以获取到 ID，但对于 daemon 进程来说，获取的 ID 可能是 4294967295。</li>
</ol>


<p>在我的场景下，对于第三个问题，并不是特别要紧，找不到对应的用户，fallback 回 effective user 就好， 所以，用来追溯进程发起用户身份的 bash 脚本是这样：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="c">#!/bin/bash</span>
</span><span class='line'>
</span><span class='line'><span class="c"># A relatively reliable way to find process initiator on Linux</span>
</span><span class='line'>
</span><span class='line'><span class="nv">user_entry</span><span class="o">=</span><span class="sb">`</span>getent passwd <span class="k">$(</span>cat /proc/self/loginuid<span class="k">)</span><span class="sb">`</span>
</span><span class='line'><span class="k">if</span> <span class="o">[</span> <span class="nv">$?</span> -eq 0 <span class="o">]</span>; <span class="k">then</span>
</span><span class='line'><span class="k">     </span><span class="nv">login_user</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span> <span class="k">${</span><span class="nv">user_entry</span><span class="k">}</span> | cut -d: -f1<span class="sb">`</span>
</span><span class='line'><span class="k">else</span>
</span><span class='line'><span class="k">     </span><span class="nv">login_user</span><span class="o">=</span><span class="k">${</span><span class="nv">SUDO_USER</span><span class="k">:-${</span><span class="nv">LOGNAME</span><span class="k">}}</span>
</span><span class='line'>     <span class="k">if</span> <span class="o">[</span> <span class="s2">&quot;${login_user}&quot;</span> <span class="o">=</span> <span class="s2">&quot;&quot;</span> <span class="o">]</span>; <span class="k">then</span>
</span><span class='line'><span class="k">          </span><span class="nv">login_user</span><span class="o">=</span><span class="sb">`</span>id -urn<span class="sb">`</span>
</span><span class='line'>     <span class="k">fi</span>
</span><span class='line'><span class="k">fi</span>
</span><span class='line'><span class="k"> </span>
</span><span class='line'><span class="nb">echo</span> <span class="k">${</span><span class="nv">login_user</span><span class="k">}</span>
</span></code></pre></td></tr></table></div></figure>



]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[这几年犯的错]]></title>
    <link href="https://blog.xupeng.me/2013/06/27/mistakes-in-production-system-these-years/"/>
    <updated>2013-06-27T21:47:00+00:00</updated>
    <id>https://blog.xupeng.me/2013/06/27/mistakes-in-production-system-these-years</id>
    <content type="html"><![CDATA[<p>得知<a href="http://www.xiachufang.com">下厨房</a>的<a href="http://blog.xiachufang.com/article/5595/">数据被误删</a>了，正在紧张恢复中。作为犯过很多次严重错误的人，我最想说的是，善待当事人吧，此刻他在承受着巨大的压力，比其他任何人都要心焦，他会很感激你的善言和善意。</p>

<p>这几年犯过很多次严重影响线上服务的错误，像重启了错误的节点这样的事情应该算作能够对线上造成影响的最微不足道的错误，就只简单说几件现在都还让我心有余悸的事吧。</p>

<!-- more -->


<h2>停用线上 memcached 集群</h2>

<p>在调整 memcached 客户端配置的使用和部署方式之前，尽管经过了多次测试，比如在部分节点先上线，确认没有问题之后上线所有的应用服务器，但还是使用了错误的配置，导致线上所有应用禁用了 memcached，巨大的访问压力瞬间拖垮了数据库，从发现问题到完全恢复持续了将近二十分钟。</p>

<h2>软件 bug 导致线上 memcached 集群被污染</h2>

<p>上线的代码在特定条件下会禁掉对 memcached 的使用，导致在本应清除 cache 的情况下没有清除，污染了整个线上的 memcached 集群，后果是各处功能出现诡异的问题，比如提醒死也叉不掉… 不得不将整个 memcached 集群 flush 一遍消除影响，耗时半天。</p>

<h2>恢复数据时删除了更多数据</h2>

<p>线上有豆列被误删了，从备份紧急恢复时，使用 mysqldump 导出需要恢复的那部分数据，但是遗憾的是连 <code>DROP TABLE ...</code> 也一起 dump 了，并且在线上执行之前都没有意识到。结果，原本需要恢复的只是一个豆列，恢复之后只剩下了一个豆列，豆列功能紧急只读，重新恢复数据并做数据合并…</p>

<h2>数据库主从切换时从库还未跟上同步</h2>

<p>我在给从库热身，准备切换主从，这时候有两个不愿意透露姓名的同事来找我聊另外一件事情，很愉快地聊完，我愉快地发现已经热身完了，于是愉快地用土脚本做了主从切换，然后悲剧地收到了报警，数据冲突同步中断了，原来是热身过程中从库因为压力比较高造成的滞后还未追平，而我已经愉快地做了切换。</p>

<h2>误操作并误删数据文件</h2>

<p>在一次主从切换之后，我突然发现新的 slave 同步在继续，但是 binlog 却停止写入了，之后惊讶地发现 master 上的 <code>SQL_LOG_BIN</code> 竟然是一个 global 级别的变量，并且值是 0。原来是之前在 slave 上调整索引时，本该 <code>SET SQL_LOG_BIN=0</code>，却无意识地执行了 <code>SET GLOBAL SQL_LOG_BIN=0</code>，禁用了整个 slave 实例而不只是当前 session 的 binlog，主从切换之后，整个集群就只剩下了一个节点有完整的数据，在我发现并修复这个问题之前，新 slave 上已经缺失了 3 分钟的数据。尝试了各种方法，想要准确无误地修复这 3 分钟的数据还是挺有难度的，尤其是在承受巨大精神压力的情况下，只好选择了从 master 重建。然而在我备份完 master 节点，重建新 slave 时又误删了 slave 上的数据文件，这下更刺激了，在新的 slave 重建完成之前，如果 master 宕机，我就真的连一个可以应付线上压力的节点都没有了，哪怕是一个缺少了 3 分钟数据的实例。</p>

<p>在发生这些事情时，真的是想死的心都有，支撑我的还有一个信念：无论如何把这个烂摊子收拾完了再死！而有过这样的经历，我就非常感激那些本来有绝对权利责难我，但在事情发生时立刻马上挽起袖子和我一起解决问题，事后帮我一起想办法如何避免这样的问题再发生的人，比如不愿意透露姓名的 <a href="http://www.douban.com/people/hongqn/">hongqn</a> 和 <a href="http://www.douban.com/people/flycondor/">flycondor</a>。</p>

<p>经历和看到过越多这样的事情，我就越觉得犯错是不可避免的，无论你思维有多缜密，行事有多谨慎，只要做事就无可避免地会犯错误，或大或小，甚至现在每隔一段时间发现自己没有在线上犯错误，就会想：我最近在干嘛？</p>

<p>最后，备份不做，日子甭过，真的会有半夜鬼敲门，另外，作为 SA 或 DBA，真的需要确保每一个危险操作都至少是可以 rollback 到上一步的，并且在进行下一步操作之前，要确认所有已知状态都是正常的，工具比人更擅长做这些事，人的精力应该花在让这些工具更加可靠上。</p>

<p>注：最后一个问题涉及 MySQL 5.5 相对于之前版本的一个行为变化，参考：</p>

<ol>
<li><a href="http://bugs.mysql.com/bug.php?id=67433">Bug #67433 Using SET GLOBAL SQL_LOG_BIN should not be allowed</a></li>
<li><a href="https://github.com/twitter/mysql/commit/9302f22d9da38558908f0e2c8ae24af75c0b8a8b">Twitter 修复了它</a></li>
</ol>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[使用 hosts 解析一个名字到多个 IP]]></title>
    <link href="https://blog.xupeng.me/2013/05/15/multiple-ips-with-hosts/"/>
    <updated>2013-05-15T20:49:00+00:00</updated>
    <id>https://blog.xupeng.me/2013/05/15/multiple-ips-with-hosts</id>
    <content type="html"><![CDATA[<p>之前一直以为 <code>hosts</code> 不支持把一个名字解析到多个 IP，因此凡是有解析到多个 IP 需求的场景，全部都使用了 DNS，偶然看 <code>host.conf</code> 的 man page，发现并不是这样，有些场景下仍然是可以使用 <code>hosts</code> 的。</p>

<!-- more -->


<p>使用 <code>hosts</code> 解析时，resolver 顺序读取 hosts 文件，返回第一条匹配记录对应的 IP，这也是我之前对 <code>hosts</code> 的理解，比如对于这样的 <code>hosts</code> 文件：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>1.1.1.1 host1
</span><span class='line'>1.1.1.2 host1</span></code></pre></td></tr></table></div></figure>


<p><code>ping host1</code> 会看到 <code>1.1.1.1</code> 这个 IP，但如果在 <code>/etc/host.conf</code> 中设置 <code>multi on</code>，虽然 <code>ping host1</code> 仍然只能看到 <code>1.1.1.1</code> 这个 IP，但 resolver 会返回 <code>hosts</code> 中对应于 <code>host1</code> 条目的所有 IP，以 Python 为例：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>>>> socket.gethostbyname_ex('host1')
</span><span class='line'>('host1', [], ['1.1.1.1', '1.1.1.2'])</span></code></pre></td></tr></table></div></figure>


<p>获取到的 IP 列表顺序和 <code>hosts</code> 中定义的顺序一样，且是固定的，这与 Round-robin DNS 的行为仍有区别，但在有些场景下使用 <code>hosts</code> + 推送 + <code>nscd</code> 名字缓存可以获得相对于 DNS 更好的总体性能和稳定性，比如 <code>doubanservice</code> 客户端获取服务器列表或许就可以改为使用 <code>hosts</code> 替换 DNS，获得更好的性能，同时也规避了比如 <a href="http://cr.yp.to/djbdns.html">tinydns</a> 对一个名字最多只返回 8 个 IP 地址的问题。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[如何同步 GRANT/REVOKE 操作]]></title>
    <link href="https://blog.xupeng.me/2012/09/29/how-to-replicate-grant-and-revoke-with-mysql/"/>
    <updated>2012-09-29T22:19:00+00:00</updated>
    <id>https://blog.xupeng.me/2012/09/29/how-to-replicate-grant-and-revoke-with-mysql</id>
    <content type="html"><![CDATA[<p>发现在 MySQL master 上用 GRANT 语句授予用户权限和用 REVOKE 语句收回权限的操作没有被同步到 slave 上，有两个 bug 报告的行为和我遇到的一致：<a href="http://bugs.mysql.com/bug.php?id=25482">#25482</a> <a href="http://bugs.mysql.com/bug.php?id=50460">#50460</a>，但从文档看，#25482 描述的是 bug，而 #50460 描述的内容则是 MySQL 的预期行为。</p>

<!-- more -->


<p><a href="http://dev.mysql.com/doc/refman/5.1/en/replication-rules-db-options.html">这里</a> 和 <a href="http://dev.mysql.com/doc/refman/5.1/en/replication-rules-table-options.html">这里</a> 描述了 MySQL 如何判定是否要写 binlog，对于我遇到的问题，原因是做 GRANT/REVOKE 操作时没有选择默认数据库，而 MySQL 在没有默认数据库的情况下根本就不会把 GRANT/REVOKE 操作写入 binlog，当然也就不会被同步到 slave 上，要解决这个问题，在 GRANT/REVOKE 之前 <code>USE</code> 任意没有被 <code>--replicate-ignore-db</code> 忽略的数据库即可。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[KILL 和 SIGPIPE]]></title>
    <link href="https://blog.xupeng.me/2012/08/11/kill-and-sigpipe/"/>
    <updated>2012-08-11T14:49:00+00:00</updated>
    <id>https://blog.xupeng.me/2012/08/11/kill-and-sigpipe</id>
    <content type="html"><![CDATA[<p>有朋友用 PHP 写了一个工具（<code>limit.php</code>），用来限制另一个进程的执行时间，代码如下：</p>

<!-- more -->




<figure class='code'><figcaption><span>limit.php  </span></figcaption>
 <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
</pre></td><td class='code'><pre><code class='php'><span class='line'><span class="cp">&lt;?php</span>
</span><span class='line'><span class="k">declare</span><span class="p">(</span><span class="nx">ticks</span> <span class="o">=</span> <span class="mi">1</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="nv">$argc</span><span class="o">&lt;</span><span class="mi">2</span><span class="p">)</span> <span class="k">die</span><span class="p">(</span><span class="s2">&quot;Wrong parameter</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">);</span>
</span><span class='line'><span class="nv">$cmd</span> <span class="o">=</span> <span class="nv">$argv</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
</span><span class='line'><span class="nv">$tl</span> <span class="o">=</span> <span class="nb">isset</span><span class="p">(</span><span class="nv">$argv</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span> <span class="o">?</span> <span class="nb">intval</span><span class="p">(</span><span class="nv">$argv</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span> <span class="o">:</span> <span class="mi">3</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nv">$pid</span> <span class="o">=</span> <span class="nb">pcntl_fork</span><span class="p">();</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">==</span> <span class="nv">$pid</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">die</span><span class="p">(</span><span class="s1">&#39;FORK_FAILED&#39;</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nv">$pid</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nb">exec</span><span class="p">(</span><span class="nv">$cmd</span><span class="p">);</span>
</span><span class='line'>    <span class="nb">posix_kill</span><span class="p">(</span><span class="nb">posix_getppid</span><span class="p">(),</span> <span class="nx">SIGALRM</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>    <span class="nb">pcntl_signal</span><span class="p">(</span><span class="nx">SIGALRM</span><span class="p">,</span> <span class="nb">create_function</span><span class="p">(</span><span class="s1">&#39;$signo&#39;</span><span class="p">,</span><span class="s2">&quot;die(&#39;EXECUTE_ENDED</span><span class="se">\n</span><span class="s2">&#39;);&quot;</span><span class="p">));</span>
</span><span class='line'>    <span class="nb">sleep</span><span class="p">(</span><span class="nv">$tl</span><span class="p">);</span>
</span><span class='line'>    <span class="nb">posix_kill</span><span class="p">(</span><span class="nv">$pid</span><span class="p">,</span> <span class="nx">SIGKILL</span><span class="p">);</span>
</span><span class='line'>    <span class="k">die</span><span class="p">(</span><span class="s2">&quot;TIMEOUT_KILLED : </span><span class="si">$pid</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>使用这个工具对 <code>php -r 'while(1){sleep(1);echo PHP_OS;};'</code> 做测试：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>php limit.php <span class="s2">&quot;php -r &#39;while(1){sleep(1);echo PHP_OS;};&#39;&quot;</span> 10
</span></code></pre></td></tr></table></div></figure>


<p>可以看到共有三个进程：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>ps -u <span class="nv">$USER</span> -opid,ppid,pgid,command|grep whil<span class="o">[</span>e<span class="o">]</span>
</span><span class='line'>21233 20858 21233 php limit.php php -r <span class="s1">&#39;while(1){sleep(1);echo PHP_OS;};&#39;</span> 10
</span><span class='line'>21234 21233 21233 php limit.php php -r <span class="s1">&#39;while(1){sleep(1);echo PHP_OS;};&#39;</span> 10
</span><span class='line'>21235 21234 21233 php -r <span class="k">while</span><span class="o">(</span>1<span class="o">){</span>sleep<span class="o">(</span>1<span class="o">)</span>;<span class="nb">echo </span>PHP_OS;<span class="o">}</span>;
</span></code></pre></td></tr></table></div></figure>


<p>其中:</p>

<ol>
<li>PID 为 21233 的进程 (<code>进程 A</code>) 是第一个启动的进程</li>
<li>PID 为 21234 的进程 (<code>进程 B</code>) 是在 21233 中 fork 出来的子进程</li>
<li>PID 为 21235 的进程 (<code>进程 C</code>) 是 21234 中使用 <code>exec</code> fork 并替换的孙子进程</li>
</ol>


<p>在 10 秒钟之后，<code>进程 A</code> 向 <code>进程 B</code> 发 KILL 信号，之后 <code>A</code>, <code>B</code>, <code>C</code> 3 个进程都退出了，符合对 <code>limit.php</code> 预想功能的期望。</p>

<p>但是，将上面 <code>进程 C</code> 稍微改动一下，移除输出语句：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>php limit.php <span class="s2">&quot;php -r &#39;while(1){sleep(1);};&#39;&quot;</span> 10
</span></code></pre></td></tr></table></div></figure>


<p>在 10 秒钟之后，<code>进程 A</code> 和 <code>进程 B</code> 退出，而 <code>进程 C</code> 仍然在继续运行：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>ps -u <span class="nv">$USER</span> -opid,ppid,pgid,command|grep whil<span class="o">[</span>e<span class="o">]</span>
</span><span class='line'>21372 20858 21372 php limit.php php -r <span class="s1">&#39;while(1){sleep(1);};&#39;</span> 10
</span><span class='line'>21373 21372 21372 php limit.php php -r <span class="s1">&#39;while(1){sleep(1);};&#39;</span> 10
</span><span class='line'>21374 21373 21372 php -r <span class="k">while</span><span class="o">(</span>1<span class="o">){</span>sleep<span class="o">(</span>1<span class="o">)</span>;<span class="o">}</span>;
</span><span class='line'><span class="nv">$ </span>ps -u <span class="nv">$USER</span> -opid,ppid,pgid,command|grep whil<span class="o">[</span>e<span class="o">]</span>
</span><span class='line'>21374     1 21372 php -r <span class="k">while</span><span class="o">(</span>1<span class="o">){</span>sleep<span class="o">(</span>1<span class="o">)</span>;<span class="o">}</span>;
</span></code></pre></td></tr></table></div></figure>


<p>至此，疑问产生了，标准输出会影响 KILL 么？</p>

<p>而实际上，父进程退出之后，子进程作为孤儿进程继续运行，这才是 Linux 下预期的正常行为，那么在第一种情况下，<code>进程 B</code> 被杀死之后，<code>进程 C</code> (<code>php -r 'while(1){sleep(1);echo PHP_OS;};'</code>) 为什么也退出了呢？</p>

<p>使用 <code>strace</code> 跟踪 <code>php -r 'while(1){sleep(1);echo PHP_OS;};'</code>：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'><span class="nv">$ </span>php limit.php <span class="s2">&quot;strace -ftt -o limit.strace php -r &#39;while(1){sleep(1);echo PHP_OS;};&#39;&quot;</span> 10
</span><span class='line'><span class="nv">$ </span>grep -C 2 Broken limit.strace
</span><span class='line'>22558 17:26:13.606751 rt_sigprocmask<span class="o">(</span>SIG_SETMASK, <span class="o">[]</span>, NULL, 8<span class="o">)</span> <span class="o">=</span> 0
</span><span class='line'>22558 17:26:13.606823 nanosleep<span class="o">({</span>1, 0<span class="o">}</span>, 0x7fffcbe00a60<span class="o">)</span> <span class="o">=</span> 0
</span><span class='line'>22558 17:26:14.607080 write<span class="o">(</span>1, <span class="s2">&quot;Linux&quot;</span>, 5<span class="o">)</span> <span class="o">=</span> -1 EPIPE <span class="o">(</span>Broken pipe<span class="o">)</span>
</span><span class='line'>22558 17:26:14.607186 --- SIGPIPE <span class="o">(</span>Broken pipe<span class="o">)</span> @ 0 <span class="o">(</span>0<span class="o">)</span> ---
</span><span class='line'>22558 17:26:14.607321 close<span class="o">(</span>2<span class="o">)</span>          <span class="o">=</span> 0
</span><span class='line'>22558 17:26:14.607403 close<span class="o">(</span>1<span class="o">)</span>          <span class="o">=</span> 0
</span></code></pre></td></tr></table></div></figure>


<p>可以看到 <code>php -r 'while(1){sleep(1);echo PHP_OS;};'</code> 在向标准输出写 <code>Linux</code> 时收到了 <code>SIGPIPE</code> 信号，它没有忽略或者处理 <code>SIGPIPE</code> 信号，于是就退出了。之所以会收到 <code>SIGPIPE</code> 信号，是因为随着它的父进程退出，它和父进程之间管道的读端被关闭了。因此，当有输出时进程看起来是被 <code>KILL</code> 掉了只是假象，歪打正着而已：</p>

<figure class='code'><figcaption><span>man 2 write </span></figcaption>
<div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>EPIPE  fd  is  connected  to  a  pipe  or  socket whose reading end is
</span><span class='line'>       closed.  When  this  happens  the  writing  process  will  also
</span><span class='line'>       receive  a  SIGPIPE  signal.   (Thus, the write return value is
</span><span class='line'>       seen only if the program catches, blocks or ignores  this  sig‐
</span><span class='line'>       nal.)</span></code></pre></td></tr></table></div></figure>


<p>为了实现这里期望的行为：<code>一个进程退出之后，它的子进程也随着退出</code>，通常不能只是对单个的进程发 <code>KILL</code> 信号，而是要对整个进程组发 <code>KILL</code> 信号，仍然拿这个 PHP 脚本来做例子，则是需要在 fork 子进程之后，将子进程放进单独的进程组，之后向这个进程组发 <code>KILL</code> 信号：</p>

<figure class='code'><figcaption><span>limit.php  </span></figcaption>
 <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
</pre></td><td class='code'><pre><code class='php'><span class='line'><span class="cp">&lt;?php</span>
</span><span class='line'><span class="k">declare</span><span class="p">(</span><span class="nx">ticks</span> <span class="o">=</span> <span class="mi">1</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="nv">$argc</span><span class="o">&lt;</span><span class="mi">2</span><span class="p">)</span> <span class="k">die</span><span class="p">(</span><span class="s2">&quot;Wrong parameter</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">);</span>
</span><span class='line'><span class="nv">$cmd</span> <span class="o">=</span> <span class="nv">$argv</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
</span><span class='line'><span class="nv">$tl</span> <span class="o">=</span> <span class="nb">isset</span><span class="p">(</span><span class="nv">$argv</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span> <span class="o">?</span> <span class="nb">intval</span><span class="p">(</span><span class="nv">$argv</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span> <span class="o">:</span> <span class="mi">3</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nv">$pid</span> <span class="o">=</span> <span class="nb">pcntl_fork</span><span class="p">();</span>
</span><span class='line'><span class="k">if</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">==</span> <span class="nv">$pid</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">die</span><span class="p">(</span><span class="s1">&#39;FORK_FAILED&#39;</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span> <span class="k">elseif</span> <span class="p">(</span><span class="nv">$pid</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="c1">// 创建新的进程组</span>
</span><span class='line'>    <span class="nv">$_pid</span> <span class="o">=</span> <span class="nb">posix_getpid</span><span class="p">();</span>
</span><span class='line'>    <span class="nb">posix_setpgid</span><span class="p">(</span><span class="nv">$_pid</span><span class="p">,</span> <span class="nv">$_pid</span><span class="p">);</span>
</span><span class='line'>    <span class="nb">exec</span><span class="p">(</span><span class="nv">$cmd</span><span class="p">);</span>
</span><span class='line'>    <span class="nb">posix_kill</span><span class="p">(</span><span class="nb">posix_getppid</span><span class="p">(),</span> <span class="nx">SIGALRM</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span><span class='line'>    <span class="nb">pcntl_signal</span><span class="p">(</span><span class="nx">SIGALRM</span><span class="p">,</span> <span class="nb">create_function</span><span class="p">(</span><span class="s1">&#39;$signo&#39;</span><span class="p">,</span><span class="s2">&quot;die(&#39;EXECUTE_ENDED</span><span class="se">\n</span><span class="s2">&#39;);&quot;</span><span class="p">));</span>
</span><span class='line'>    <span class="nb">sleep</span><span class="p">(</span><span class="nv">$tl</span><span class="p">);</span>
</span><span class='line'>    <span class="c1">// 向整个进程组发 KILL 信号</span>
</span><span class='line'>    <span class="nb">posix_kill</span><span class="p">(</span><span class="o">-</span><span class="nv">$pid</span><span class="p">,</span> <span class="nx">SIGKILL</span><span class="p">);</span>
</span><span class='line'>    <span class="k">die</span><span class="p">(</span><span class="s2">&quot;TIMEOUT_KILLED : </span><span class="si">$pid</span><span class="se">\n</span><span class="s2">&quot;</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>另外 <code>Linux</code> 平台也提供了父进程退出时通知子进程的机制，方法是子进程设置 <code>pdeath_signal</code> 属性，这样当父进程退出时，检查到子进程设置了 <code>pdeath_signal</code>，就向子进程发送预设的信号，下面的代码来自 <code>kernel/exit.c:forget_original_parent</code>:</p>

<figure class='code'><figcaption><span>kernel/exit.c:forget_original_parent  </span></figcaption>
 <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='c'><span class='line'><span class="n">list_for_each_entry_safe</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">father</span><span class="o">-&gt;</span><span class="n">children</span><span class="p">,</span> <span class="n">sibling</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">t</span> <span class="o">=</span> <span class="n">p</span><span class="p">;</span>
</span><span class='line'>    <span class="k">do</span> <span class="p">{</span>
</span><span class='line'>        <span class="n">t</span><span class="o">-&gt;</span><span class="n">real_parent</span> <span class="o">=</span> <span class="n">reaper</span><span class="p">;</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="n">t</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">==</span> <span class="n">father</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>            <span class="n">BUG_ON</span><span class="p">(</span><span class="n">task_ptrace</span><span class="p">(</span><span class="n">t</span><span class="p">));</span>
</span><span class='line'>            <span class="n">t</span><span class="o">-&gt;</span><span class="n">parent</span> <span class="o">=</span> <span class="n">t</span><span class="o">-&gt;</span><span class="n">real_parent</span><span class="p">;</span>
</span><span class='line'>        <span class="p">}</span>
</span><span class='line'>        <span class="k">if</span> <span class="p">(</span><span class="n">t</span><span class="o">-&gt;</span><span class="n">pdeath_signal</span><span class="p">)</span>
</span><span class='line'>            <span class="n">group_send_sig_info</span><span class="p">(</span><span class="n">t</span><span class="o">-&gt;</span><span class="n">pdeath_signal</span><span class="p">,</span>
</span><span class='line'>                        <span class="n">SEND_SIG_NOINFO</span><span class="p">,</span> <span class="n">t</span><span class="p">);</span>
</span><span class='line'>    <span class="p">}</span> <span class="n">while_each_thread</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">t</span><span class="p">);</span>
</span><span class='line'>    <span class="n">reparent_leader</span><span class="p">(</span><span class="n">father</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dead_children</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>最后，这是一段演示如何使用这个通知机制来让子进程优雅退出的 Python 代码：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="kn">import</span> <span class="nn">os</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">sys</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">time</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">signal</span>
</span><span class='line'>
</span><span class='line'><span class="kn">import</span> <span class="nn">prctl</span>
</span><span class='line'>
</span><span class='line'><span class="n">pid</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">fork</span><span class="p">()</span>
</span><span class='line'><span class="k">if</span> <span class="n">pid</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span><span class='line'>    <span class="k">def</span> <span class="nf">sig_handler</span><span class="p">(</span><span class="n">sig</span><span class="p">,</span> <span class="n">frame</span><span class="p">):</span>
</span><span class='line'>        <span class="k">if</span> <span class="n">sig</span> <span class="o">==</span> <span class="n">signal</span><span class="o">.</span><span class="n">SIGTERM</span><span class="p">:</span>
</span><span class='line'>            <span class="k">print</span> <span class="s">&#39;exit&#39;</span>
</span><span class='line'>            <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span><span class='line'>    <span class="n">prctl</span><span class="o">.</span><span class="n">set_pdeathsig</span><span class="p">(</span><span class="n">signal</span><span class="o">.</span><span class="n">SIGTERM</span><span class="p">)</span>
</span><span class='line'>    <span class="n">signal</span><span class="o">.</span><span class="n">signal</span><span class="p">(</span><span class="n">signal</span><span class="o">.</span><span class="n">SIGTERM</span><span class="p">,</span> <span class="n">sig_handler</span><span class="p">)</span>
</span><span class='line'>    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
</span><span class='line'>        <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span><span class='line'><span class="k">else</span><span class="p">:</span>
</span><span class='line'>    <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
</span><span class='line'>        <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span></code></pre></td></tr></table></div></figure>



]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[这周遇到的三个问题]]></title>
    <link href="https://blog.xupeng.me/2012/08/10/problems-happened-this-week/"/>
    <updated>2012-08-10T23:51:00+00:00</updated>
    <id>https://blog.xupeng.me/2012/08/10/problems-happened-this-week</id>
    <content type="html"><![CDATA[<p>这周遇到了三个值得说一说的问题，每一个问题都带来了不小的麻烦，作为教训再来回顾一下。</p>

<!-- more -->


<p>第一个问题，在某次上线之后，突然出现了很多 <code>AttrubuteError</code> 和 <code>TypeError</code>，这些错误看起来都是不应该出现的，究其原因，是从 memcached 中取回的数据和预期的不同。</p>

<p>在代码中对容易出问题的 key 做跟踪，却找不到有任何代码对这些 key 做过 set 操作，但 memcached 中的数据的确是错的，由此基本可以推定并不是有代码 set 了错误的 key。在定位问题的过程中发现，重启应用时会有很大的机率触发 memcached 数据错乱，以此为线索，<a href="http://www.douban.com/people/hongqn/">hongqn</a> 最终定位到了导致此问题的原因：<a href="http://quixote.ca/">Quixote</a> 在 fork 子进程之前 import 了部分应用代码，某个 changset 中的改动又使得部分代码在被 import 时就创建了到 memcached 的连接，多个子进程使用同一个 memcached 连接，数据包交织导致了 memcached 通讯混乱。</p>

<p>这里主要有两个问题：<a href="http://quixote.ca/">Quixote</a> 在 fork 之前调用了应用代码、memcached 客户端库非线程安全；解决这两个问题中的任意一个都可以避免数据错乱的问题发生，第二个问题也可以通过在 fork 之后重新建立连接来作为 workaround 手段而绕过其线程安全的问题。</p>

<p>第二个问题危害就更大了，几乎所有的 MySQL 实例都在轮番 crash，基本上都是同一个原因：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>Some pointers may be invalid and cause the dump to abort.
</span><span class='line'>Query (0x7f7fb07f52f0): is an invalid pointer
</span><span class='line'>Connection ID (thread ID): 170604
</span><span class='line'>Status: NOT_KILLED</span></code></pre></td></tr></table></div></figure>


<p>这个原因看起来和存储没有直接的联系，但由于最初 crash 的实例都部署在 SSD 上，还是不自觉地把 SSD 作为了首要的怀疑对象，然而还没找到 SSD 本身的任何问题，部署在硬盘上的实例也开始 crash 了，接下来就是一系列的猜测和快速推翻猜测：</p>

<ol>
<li>SSD 可靠性有问题？和 SSD 没有任何关系的数据库也 crash 了</li>
<li>服务器内存错误？多台物理服务器上的很多个不同的实例多次 crash，这么多服务器的内存在短时间内同时出问题的概率还是挺小的</li>
<li>MySQL bug？这个版本的 MySQL 已经在线上稳定运行了 1 年以上</li>
<li>特殊的查询导致 crash？

<ol>
<li>使用一组抓取的读查询对数据库热身，导致其 crash，重启之后用同一组查询再次热身，不能重现，但同一物理机上的另外一个实例 crash 了</li>
<li>如果说是特殊的写操作导致的 crash，但是 master 不 crash slave crash 的案例存在，master crash slave 不 crash 的案例也存在</li>
</ol>
</li>
<li>和压力有关？压力非常小，并且是没有查询的 slave 也会 crash</li>
<li>和 MySQL 的运行时间有关？运行了一年多的节点和每周都会重启一次的节点都会 crash</li>
<li>&#8230;</li>
</ol>


<p>之后 Redis 和 <a href="https://github.com/douban/beansdb">BeansDB</a> 也开始有实例 crash，才最终停止了对 MySQL 本身的追查，把目光转向了系统，在最近的变更中找到了可疑的目标：对这些服务器新增加的 RAID 健康状态监控。</p>

<p>在 <a href="http://www.anchor.com.au/blog/2012/03/bugfixing-the-in-kernel-megaraid_sas-driver-from-crash-to-patch/">这里</a> 确认了 megaraid_sas 驱动的 bug，并且特定版本的 MegaCli (version 8.01.06) 工具会触发它。一句话概括这个bug：<strong>它可能向任意内存地址写数据！</strong> 可以升级 MegaCli 到比 8.01.06 更新的版本，这样可以不触发这个 bug，或者给它打 <a href="http://article.gmane.org/gmane.linux.scsi/71946">patch</a> 来修正 bug。</p>

<p>第三个问题也相当严重，某应用上线之后内存使用突涨，其内存占用增长的特殊性导致目前的内存监控失效，最终一组服务器全部宕机，使用 <a href="http://en.wikipedia.org/wiki/Strace">strace</a> 跟踪到了奇怪的问题，在查明根本原因之前先回滚了相关的代码。</p>

<p>这个事故暴露了产品开发和服务监控的问题：新上线的功能务必要能在工作时间生效，比如这次上线的功能生效的条件是偶数日期，也刚好今天的日期是偶数，这个问题及时地暴露了出来，想像一下如果是在奇数日生效而明天又是周六，过了凌晨一组服务器全部宕机，那又会是多么严重的问题。而对于监控来说，依赖应用的协作进行的监控是不靠谱的，而外部监控又很难设定普适的异常条件，这也是一个需要花更多精力来做好的事情。</p>

<p><em>更正：对于第一个问题，虽然在多线程环境下共享连接也会有同样的问题，但就这次的事故而言，和线程安全无关，之前的表述有误。</em></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[MySQL-python: Commands out of sync]]></title>
    <link href="https://blog.xupeng.me/2012/03/13/mysql-python-commands-out-of-sync/"/>
    <updated>2012-03-13T18:12:00+00:00</updated>
    <id>https://blog.xupeng.me/2012/03/13/mysql-python-commands-out-of-sync</id>
    <content type="html"><![CDATA[<p>在给 MySQL 数据库访问层增加新功能时遇到了这样的错误：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>ProgrammingError: (2014, "Commands out of sync; you can't run this command now")</span></code></pre></td></tr></table></div></figure>


<p>之前零星地见到过几次，因为发生频度很低，就没有太在意，这次找了一下原因，MySQL 文档对 <code>Commands out of sync</code> 错误的描述是这样的：</p>

<!-- more -->




<blockquote><p>If you get Commands out of sync; you can&#8217;t run this command now in your client code, you are calling client functions in the wrong order.</p><p>This can happen, for example, if you are using mysql_use_result() and try to execute a new query before you have called mysql_free_result(). It can also happen if you try to execute two queries that return data without calling mysql_use_result() or mysql_store_result() in between.</p><footer><strong>Commands out of sync</strong> <cite><a href='http://dev.mysql.com/doc/refman/5.1/en/commands-out-of-sync.html'>dev.mysql.com/doc/refman/5.1/en/&hellip;</a></cite></footer></blockquote>


<p>MySQL 和客户端通信使用的是“半双工”的应答协议，客户端每发送一个查询，服务器“强行推送”结果给客户端，客户端需要执行 <code>mysql_use_result()</code> 或 <code>mysql_store_result()</code> 从服务器端取回结果，这是一次完整的查询交互，只提交查询而不取回结果就会导致 <code>Commands out of sync</code>。</p>

<p>由于使用的是 MySQL-python，所以第一种情况所说的没有调用 <code>mysql_free_result()</code> 的问题不大可能存在，并且 MySQLdb 的默认 cursor 使用的是 <code>mysql_store_result()</code> 而不是 <code>mysql_use_result()</code>，所以应该是第二种情况。</p>

<p>抓取了一些有可能会导致这个问题的查询，发现了类似这样的语句：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>SELECT a, b FROM t LIMIT 1; -- some comments</span></code></pre></td></tr></table></div></figure>


<p>执行了这样的查询之后，再执行任何查询都会遇到 <code>Commands out of sync</code> 问题。</p>

<p>因为这其实是由分号隔开的两个独立的查询语句，使用 MySQLdb 执行时，作为一个语句发送给 MySQL server，之后 MySQLdb 执行了一次 <code>mysql_store_result()</code>。</p>

<p>之前需要自动地给每一个查询增加注释，而个别的查询在末尾写了分号，追加注释之后就触发了这个问题，那么只需要在加注释前 strip 掉分号就好了。</p>

<p>MySQLdb 有四种 cursor：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>class Cursor(CursorStoreResultMixIn, CursorTupleRowsMixIn,                      
</span><span class='line'>             BaseCursor):                                                       
</span><span class='line'>                                                                                
</span><span class='line'>    """This is the standard Cursor class that returns rows as tuples            
</span><span class='line'>    and stores the result set in the client."""                                 
</span><span class='line'>                                                                                
</span><span class='line'>                                                                                
</span><span class='line'>class DictCursor(CursorStoreResultMixIn, CursorDictRowsMixIn,                   
</span><span class='line'>                 BaseCursor):                                                   
</span><span class='line'>                                                                                
</span><span class='line'>     """This is a Cursor class that returns rows as dictionaries and            
</span><span class='line'>    stores the result set in the client."""                                     
</span><span class='line'>                                                                                
</span><span class='line'>                                                                                
</span><span class='line'>class SSCursor(CursorUseResultMixIn, CursorTupleRowsMixIn,                      
</span><span class='line'>               BaseCursor):                                                     
</span><span class='line'>                                                                                
</span><span class='line'>    """This is a Cursor class that returns rows as tuples and stores            
</span><span class='line'>    the result set in the server."""                                            
</span><span class='line'>                                                                                
</span><span class='line'>                                                                                
</span><span class='line'>class SSDictCursor(CursorUseResultMixIn, CursorDictRowsMixIn,                   
</span><span class='line'>                   BaseCursor):                                                 
</span><span class='line'>                                                                                
</span><span class='line'>    """This is a Cursor class that returns rows as dictionaries and             
</span><span class='line'>    stores the result set in the server."""</span></code></pre></td></tr></table></div></figure>


<p>默认使用的是 <code>Cursor</code>，在提交查询之后执行 <code>mysql_store_result()</code>，将 MySQL Server 返回的所有数据都取回并缓存在本地，<code>SSCursor</code> 则是使用 <code>mysql_use_result()</code>，将结果“缓存”在服务器端，客户端逐行取回结果，好处是速度会比 <code>mysql_store_result()</code> 快，并且客户端可以节省内存，但在高并发环境下，还是使用默认的 <code>Cursor</code> 比较好，因为 MySQL server 只有在客户端取回所有的结果之后才会释放相关的锁（如果有的话），而逐行取回并分别对每行做处理大多会需要更长的时间。</p>

<p>Reference:</p>

<ol>
<li><a href="http://dev.mysql.com/doc/refman/5.1/en/mysql-store-result.html">http://modperlbook.org/html/20-2-4-mysql_use_result-Versus-mysql_store_result-Attribute.html</a></li>
<li><a href="http://dev.mysql.com/doc/refman/5.1/en/mysql-store-result.html">http://dev.mysql.com/doc/refman/5.1/en/mysql-use-result.html</a></li>
<li><a href="http://dev.mysql.com/doc/refman/5.1/en/mysql-store-result.html">http://dev.mysql.com/doc/refman/5.1/en/mysql-store-result.html</a></li>
</ol>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[类型转换对 MySQL 选择索引的影响]]></title>
    <link href="https://blog.xupeng.me/2012/02/08/type-conversion-and-index-selection-of-mysql/"/>
    <updated>2012-02-08T08:29:00+00:00</updated>
    <id>https://blog.xupeng.me/2012/02/08/type-conversion-and-index-selection-of-mysql</id>
    <content type="html"><![CDATA[<p>遇到了几例 MySQL 没用使用预期索引的问题，读了些文档之后，发现 MySQL 的类型转换对索引选择的影响还真是一个不大不小的坑。</p>

<p>比如有这样一张 MySQL 表：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>CREATE TABLE `indextest` (
</span><span class='line'>  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
</span><span class='line'>  `name` varchar(10) DEFAULT NULL,
</span><span class='line'>  `age` tinyint(3) unsigned NOT NULL DEFAULT '0',
</span><span class='line'>  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
</span><span class='line'>  PRIMARY KEY (`id`),
</span><span class='line'>  KEY `idx_name` (`name`),
</span><span class='line'>  KEY `idx_age` (`age`),
</span><span class='line'>  KEY `idx_create` (`create_time`)
</span><span class='line'>) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1</span></code></pre></td></tr></table></div></figure>




<!-- more -->


<p><code>name</code> 是一个有索引的 <code>varchar</code> 字段，表内数据是这样的：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>+----+--------+-----+---------------------+
</span><span class='line'>| id | name   | age | create_time         |
</span><span class='line'>+----+--------+-----+---------------------+
</span><span class='line'>|  1 | hello  |  10 | 2012-02-01 20:00:00 |
</span><span class='line'>|  2 | world  |  20 | 2012-02-02 20:00:00 |
</span><span class='line'>|  3 | 111222 |  30 | 2012-02-03 20:00:00 |
</span><span class='line'>|  4 | wow    |  40 | 2012-02-04 20:00:00 |
</span><span class='line'>+----+--------+-----+---------------------+</span></code></pre></td></tr></table></div></figure>


<p>使用字符串 <code>'111222'</code> 作为参数对 <code>name</code> 字段查询，Execution Plan 如预期的一样，会使用 <code>name</code> 字段上的索引 <code>idx_name</code>：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mysql [localhost] {msandbox} (test) &gt; explain select age from
</span><span class='line'>    -&gt; indextest where name='111222'\G
</span><span class='line'>*************************** 1. row ***************************
</span><span class='line'>           id: 1
</span><span class='line'>  select_type: SIMPLE
</span><span class='line'>        table: indextest
</span><span class='line'>         type: ref
</span><span class='line'>possible_keys: idx_name
</span><span class='line'>          key: idx_name
</span><span class='line'>      key_len: 13
</span><span class='line'>          ref: const
</span><span class='line'>         rows: 1
</span><span class='line'>        Extra: Using where
</span><span class='line'>1 row in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>而使用数字作为参数对 name 字段做查询时，<code>explain</code> 表明这将是全表扫描：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mysql [localhost] {msandbox} (test) &gt; explain select age from
</span><span class='line'>    -&gt; indextest where name=111222\G
</span><span class='line'>*************************** 1. row ***************************
</span><span class='line'>           id: 1
</span><span class='line'>  select_type: SIMPLE
</span><span class='line'>        table: indextest
</span><span class='line'>         type: ALL
</span><span class='line'>possible_keys: idx_name
</span><span class='line'>          key: NULL
</span><span class='line'>      key_len: NULL
</span><span class='line'>          ref: NULL
</span><span class='line'>         rows: 4
</span><span class='line'>        Extra: Using where
</span><span class='line'>1 row in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>究其原因，是当文本字段与数字进行比较时，由于类型不同，MySQL 需要做隐式类型转换才能进行比较，结果就如上面的例子所提到的一样。</p>

<p>MySQL 的文档 (<a href="http://dev.mysql.com/doc/refman/5.1/en/type-conversion.html">Type Conversion in Expression Evaluation</a>) 中提到，在做比较时，会按这样的规则进行必要的类型转换：</p>

<ul>
<li>两个参数至少有一个是 NULL 时，比较的结果也是 NULL，例外是使用 <code>&lt;=&gt;</code> 对两个 NULL 做比较时会返回 1，这两种情况都不需要做类型转换</li>
<li>两个参数都是字符串，会按照字符串来比较，不做类型转换</li>
<li>两个参数都是整数，按照整数来比较，不做类型转换</li>
<li>十六进制的值和非数字做比较时，会被当做二进制串，和数字做比较时会按下面的规则处理</li>
<li>有一个参数是 <code>TIMESTAMP</code> 或 <code>DATETIME</code>，并且另外一个参数是常量，常量会被转换为 <code>timestamp</code></li>
<li>有一个参数是 <code>decimal</code> 类型，如果另外一个参数是 <code>decimal</code> 或者整数，会将整数转换为 <code>decimal</code> 后进行比较，如果另外一个参数是浮点数，则会把 <code>decimal</code> 转换为浮点数进行比较</li>
<li>所有其他情况下，两个参数都会被转换为浮点数再进行比较</li>
</ul>


<p>比如：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mysql [localhost] {msandbox} (test) &gt; SELECT '18015376320243459' =
</span><span class='line'>    -&gt; 18015376320243459;
</span><span class='line'>+-----------------------------------------+
</span><span class='line'>| '18015376320243459' = 18015376320243459 |
</span><span class='line'>+-----------------------------------------+
</span><span class='line'>|                                       0 |
</span><span class='line'>+-----------------------------------------+
</span><span class='line'>1 row in set (0.00 sec)
</span><span class='line'>
</span><span class='line'>mysql [localhost] {msandbox} (test) &gt; SELECT '18015376320243459' + 0;
</span><span class='line'>+-------------------------+
</span><span class='line'>| '18015376320243459' + 0 |
</span><span class='line'>+-------------------------+
</span><span class='line'>|    1.80153763202435e+16 |
</span><span class='line'>+-------------------------+
</span><span class='line'>1 row in set (0.00 sec)
</span><span class='line'>
</span><span class='line'>
</span><span class='line'>mysql [localhost] {msandbox} (test) &gt; SELECT
</span><span class='line'>    -&gt; cast('18015376320243459' as unsigned) =  18015376320243459;
</span><span class='line'>+-----------------------------------------------------------+
</span><span class='line'>| cast('18015376320243459' as unsigned) = 18015376320243459 |
</span><span class='line'>+-----------------------------------------------------------+
</span><span class='line'>|                                                         1 |
</span><span class='line'>+-----------------------------------------------------------+
</span><span class='line'>1 row in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>因为浮点数精度(53 bits)问题，并且 MySQL 将字符串转换为浮点数和将整数转换为浮点数使用不同的方法，字符串 <code>'18015376320243459'</code> 和整数 <code>18015376320243459</code> 相比较就不相等，如果要避免隐式浮点数转换带来的精度问题，可以显式地使用 cast 做类型转换，将字符串转换为整数。</p>

<p>按照这些规则，对于上面的例子来说，<code>name</code> 字段的值和查询参数 <code>'111222'</code> 都会被转换为浮点数才会做比较，而很多文本都能转换为和 <code>111222</code> 相等的数值，比如 <code>'111222'</code>, <code>'111222aabb'</code>, <code>' 111222'</code> 和 <code>'11122.2e1'</code>，所以 MySQL 不能有效使用索引，就退化为索引扫描甚至是全表扫描。</p>

<p>而反过来，如果使用一个字符串作为查询参数，对一个数字字段做比较查询，MySQL 则是可以有效利用索引的：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mysql [localhost] {msandbox} (test) &gt; explain select name from
</span><span class='line'>    -&gt; indextest where age='30'\G
</span><span class='line'>*************************** 1. row ***************************
</span><span class='line'>           id: 1
</span><span class='line'>  select_type: SIMPLE
</span><span class='line'>        table: indextest
</span><span class='line'>         type: ref
</span><span class='line'>possible_keys: idx_age
</span><span class='line'>          key: idx_age
</span><span class='line'>      key_len: 1
</span><span class='line'>          ref: const
</span><span class='line'>         rows: 1
</span><span class='line'>        Extra:
</span><span class='line'>1 row in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>原因则是，MySQL 可以将查询参数 <code>'30'</code> 转换为确定的数值 <code>30</code>，之后可以快速地在索引中找到与之相等的数值。</p>

<p>除此之外，使用函数对索引字段做显式类型转换或者计算也会使 MySQL 无法使用索引：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mysql [localhost] {msandbox} (test) &gt; explain select name from
</span><span class='line'>    -&gt; indextest where cast(age as unsigned)=30\G
</span><span class='line'>*************************** 1. row ***************************
</span><span class='line'>           id: 1
</span><span class='line'>  select_type: SIMPLE
</span><span class='line'>        table: indextest
</span><span class='line'>         type: ALL
</span><span class='line'>possible_keys: NULL
</span><span class='line'>          key: NULL
</span><span class='line'>      key_len: NULL
</span><span class='line'>          ref: NULL
</span><span class='line'>         rows: 4
</span><span class='line'>        Extra: Using where
</span><span class='line'>1 row in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>如上，使用 <code>cast</code> 函数对 <code>age</code> 做显式的类型转换，会使索引失效，当然了，在实际的代码中很少会有这样的写法，但类似下面这样对时间字段做运算的用法就比较多了：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mysql [localhost] {msandbox} (test) &gt; explain select * from
</span><span class='line'>    -&gt; indextest where date(create_time)='2012-02-02'\G
</span><span class='line'>*************************** 1. row ***************************
</span><span class='line'>           id: 1
</span><span class='line'>  select_type: SIMPLE
</span><span class='line'>        table: indextest
</span><span class='line'>         type: ALL
</span><span class='line'>possible_keys: NULL
</span><span class='line'>          key: NULL
</span><span class='line'>      key_len: NULL
</span><span class='line'>          ref: NULL
</span><span class='line'>         rows: 4
</span><span class='line'>        Extra: Using where
</span><span class='line'>1 row in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>对于本例的需求，是想查找 <code>create_time</code> 是 <code>2012-02-02</code> 这一天的记录，用变通的方法，避免在索引字段上做运算就可以有效使用索引了：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mysql [localhost] {msandbox} (test) &gt; explain select * from
</span><span class='line'>    -&gt; indextest where create_time between '2012-02-02' and '2012-02-03'\G
</span><span class='line'>*************************** 1. row ***************************
</span><span class='line'>           id: 1
</span><span class='line'>  select_type: SIMPLE
</span><span class='line'>        table: indextest
</span><span class='line'>         type: range
</span><span class='line'>possible_keys: idx_create
</span><span class='line'>          key: idx_create
</span><span class='line'>      key_len: 4
</span><span class='line'>          ref: NULL
</span><span class='line'>         rows: 1
</span><span class='line'>        Extra: Using where
</span><span class='line'>1 row in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>MySQL 的 <a href="https://www.google.com/search?sourceid=chrome&amp;ie=UTF-8&amp;q=intitle%3Ahow+mysql#sclient=psy-ab&amp;hl=en&amp;newwindow=1&amp;source=hp&amp;q=intitle:how+site%3Adev.mysql.com%2Fdoc%2Frefman%2F5.1%2Fen%2F&amp;pbx=1&amp;oq=intitle:how+site%3Adev.mysql.com%2Fdoc%2Frefman%2F5.1%2Fen%2F&amp;aq=f&amp;aqi=&amp;aql=&amp;gs_sm=e&amp;gs_upl=4683l4716l3l5178l2l2l0l0l0l1l433l697l2-1.0.1l2l0&amp;bav=on.2,or.r_gc.r_pw.r_cp.,cf.osb&amp;fp=99e1c73b90310b55&amp;biw=1159&amp;bih=683">How &#8230;</a> 系列文档值得读一读，比如：</p>

<ul>
<li><a href="http://dev.mysql.com/doc/refman/5.1/en/mysql-indexes.html">How MySQL Uses Indexes</a></li>
<li><a href="http://dev.mysql.com/doc/refman/5.1/en/memory-use.html">How MySQL Uses Memory</a></li>
<li><a href="http://dev.mysql.com/doc/refman/5.1/en/internal-temporary-tables.html">How MySQL Uses Internal Temporary Tables</a></li>
<li><a href="http://dev.mysql.com/doc/refman/5.1/en/innodb-deadlocks.html">How to Cope with Deadlocks</a></li>
<li><a href="http://dev.mysql.com/doc/refman/5.1/en/table-cache.html">How MySQL Opens and Closes Tables</a></li>
<li><a href="http://dev.mysql.com/doc/refman/5.1/en/connection-threads.html">How MySQL Uses Threads for Client Connections</a></li>
<li><a href="http://dev.mysql.com/doc/refman/5.1/en/what-is-crashing.html">How to Determine What is Causing a Problem</a></li>
</ul>


<p>伟大开源软件的文档总是需要经过反复阅读，才能逐步被理解和正确运用，RTFM 和 RTFS 的光辉无限！</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[MySQL 的临时目录]]></title>
    <link href="https://blog.xupeng.me/2012/02/04/how-mysql-uses-tmpdir/"/>
    <updated>2012-02-04T07:18:00+00:00</updated>
    <id>https://blog.xupeng.me/2012/02/04/how-mysql-uses-tmpdir</id>
    <content type="html"><![CDATA[<p>MySQL 服务器设置的 binlog 单文件最大为 1GB，偶然发现会有十几 GB 大小的 binlog 文件，从产生的时间上看像是某个 cron job 使用了超大的 transaction，为了找出“罪魁祸首”，我需要分析一下 binlog。</p>

<!-- more -->


<p>在使用 mysqlbinlog 将 binary log 转换为文本文件时，发现根分区很快就被塞满了，使用 lsof 发现 mysqlbinlog 在往 /tmp 下写临时文件：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class=''><span class='line'># lsof -p 17423
</span><span class='line'>COMMAND     PID USER   FD   TYPE DEVICE    SIZE/OFF      NODE NAME
</span><span class='line'>mysqlbinl 17423 root    1w   REG   0,19   791765366   3186036 /mfs/user/xupeng/tmp/bigbinlogs/bigbinlog.sql
</span><span class='line'>mysqlbinl 17423 root    2u   CHR  136,7         0t0        10 /dev/pts/7
</span><span class='line'>mysqlbinl 17423 root    3r   REG   0,19 13863171331   3172073 /mfs/user/xupeng/tmp/bigbinlogs/log.000323
</span><span class='line'>mysqlbinl 17423 root    4u   REG    8,1   612122624 135332782 /tmp/tmp.rWTNda (deleted)
</span><span class='line'>mysqlbinl 17423 root    5u   REG    8,1     2490368 135332784 /tmp/tmp.spKjTA (deleted)
</span><span class='line'>
</span><span class='line'>…</span></code></pre></td></tr></table></div></figure>


<p>看 mysqlbinlog 的 Man page，发现并没有参数可以指定临时目录，翻了一下 mysqlbinlog (client/mysqlbinlog.cc) 的代码，看到了下面的代码：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='c'><span class='line'>  <span class="n">MY_TMPDIR</span> <span class="n">tmpdir</span><span class="p">;</span>
</span><span class='line'>  <span class="n">tmpdir</span><span class="p">.</span><span class="n">list</span><span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">dirname_for_local_load</span><span class="p">)</span>
</span><span class='line'>  <span class="p">{</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">init_tmpdir</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tmpdir</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
</span><span class='line'>      <span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
</span><span class='line'>    <span class="n">dirname_for_local_load</span><span class="o">=</span> <span class="n">my_strdup</span><span class="p">(</span><span class="n">my_tmpdir</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tmpdir</span><span class="p">),</span> <span class="n">MY_WME</span><span class="p">);</span>
</span><span class='line'>  <span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p><code>init_tmpdir</code> 定义在 <code>mysys/mf_tempdir.c</code> 中：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
</pre></td><td class='code'><pre><code class='c'><span class='line'><span class="n">my_bool</span> <span class="nf">init_tmpdir</span><span class="p">(</span><span class="n">MY_TMPDIR</span> <span class="o">*</span><span class="n">tmpdir</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">pathlist</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="kt">char</span> <span class="o">*</span><span class="n">end</span><span class="p">,</span> <span class="o">*</span><span class="n">copy</span><span class="p">;</span>
</span><span class='line'>  <span class="kt">char</span> <span class="n">buff</span><span class="p">[</span><span class="n">FN_REFLEN</span><span class="p">];</span>
</span><span class='line'>  <span class="n">DBUG_ENTER</span><span class="p">(</span><span class="s">&quot;init_tmpdir&quot;</span><span class="p">);</span>
</span><span class='line'>  <span class="n">DBUG_PRINT</span><span class="p">(</span><span class="s">&quot;enter&quot;</span><span class="p">,</span> <span class="p">(</span><span class="s">&quot;pathlist: %s&quot;</span><span class="p">,</span> <span class="n">pathlist</span> <span class="o">?</span> <span class="n">pathlist</span> <span class="o">:</span> <span class="s">&quot;NULL&quot;</span><span class="p">));</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">pthread_mutex_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tmpdir</span><span class="o">-&gt;</span><span class="n">mutex</span><span class="p">,</span> <span class="n">MY_MUTEX_INIT_FAST</span><span class="p">);</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="n">my_init_dynamic_array</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tmpdir</span><span class="o">-&gt;</span><span class="n">full_list</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">),</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">5</span><span class="p">))</span>
</span><span class='line'>    <span class="k">goto</span> <span class="n">err</span><span class="p">;</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pathlist</span> <span class="o">||</span> <span class="o">!</span><span class="n">pathlist</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span><span class='line'>  <span class="p">{</span>
</span><span class='line'>    <span class="cm">/* Get default temporary directory */</span>
</span><span class='line'>    <span class="n">pathlist</span><span class="o">=</span><span class="n">getenv</span><span class="p">(</span><span class="s">&quot;TMPDIR&quot;</span><span class="p">);</span>  <span class="cm">/* Use this if possible */</span>
</span><span class='line'><span class="cp">#if defined( __WIN__) || defined(__NETWARE__)</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pathlist</span><span class="p">)</span>
</span><span class='line'>      <span class="n">pathlist</span><span class="o">=</span><span class="n">getenv</span><span class="p">(</span><span class="s">&quot;TEMP&quot;</span><span class="p">);</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pathlist</span><span class="p">)</span>
</span><span class='line'>      <span class="n">pathlist</span><span class="o">=</span><span class="n">getenv</span><span class="p">(</span><span class="s">&quot;TMP&quot;</span><span class="p">);</span>
</span><span class='line'><span class="cp">#endif</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pathlist</span> <span class="o">||</span> <span class="o">!</span><span class="n">pathlist</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
</span><span class='line'>      <span class="n">pathlist</span><span class="o">=</span><span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span> <span class="n">P_tmpdir</span><span class="p">;</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">...</span>
</span></code></pre></td></tr></table></div></figure>


<p>可以看到，在没有指定 pathlist 时，会使用 <code>TMPDIR</code> 这个环境变量指定的目录作为临时目录，如果 <code>TMPDIR</code> 这个环境变量也不存在，在 Windows 下会接着查找 <code>TEMP</code> 和 <code>TMP</code> 这两个环境变量，从环境变量查找临时目录失败，会使用 <code>P_tmpdir</code> 作为默认临时目录，在 Linux 上 <code>P_tmpdir</code> 是 <code>/tmp</code> (定义在 stdio.h 中）。</p>

<p>所以在运行 mysqlbinlog 之前设置 <code>TMPDIR</code> 这个环境变量就好了。</p>

<p>MySQL server 和 客户端工具都使用这个临时目录查找策略，怪不得使用 <code>mysqlbinlog tmp directory</code> 作为关键词没有搜到需要的结果，而使用 <code>mysql tmp directory</code> 作为关键词，第一条搜索结果就是 <a href="http://dev.mysql.com/doc/refman/5.0/en/temporary-files.html">Where MySQL Stores Temporary Files</a>，选择正确的关键词很重要啊。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Code Swarm]]></title>
    <link href="https://blog.xupeng.me/2012/01/12/code-swarm/"/>
    <updated>2012-01-12T06:49:00+00:00</updated>
    <id>https://blog.xupeng.me/2012/01/12/code-swarm</id>
    <content type="html"><![CDATA[<p><a href="http://www.michaelogawa.com/code_swarm/">Code swarm</a> 是一个可视化项目，最常见的用途是把代码仓库的提交历史可视化，changesets 以时间顺序回放，每个发生变更的文件作为一个闪亮的光点从各处汇聚在对应的 committer 身上，把项目的演进历史以视频的方式形象地呈现出来，通常还会配上激动人心的背景音乐，令程序员们潸然泪下。</p>

<!-- more -->


<p>很多著名的开源项目比如 <a href="http://vimeo.com/1093745">Python</a>、<a href="http://vimeo.com/1265258">Subversion</a>、<a href="http://vimeo.com/1223937">Django</a>、<a href="http://vimeo.com/1081680">PostgreSQL</a>、<a href="http://www.vimeo.com/1076588">Apache</a> 等等都有 code swarm 视频，很多互联网公司也都有 code swarm 视频，比如 <a href="http://vimeo.com/9225227">Twitter</a>、<a href="http://vimeo.com/2249514">Last.fm</a>。</p>

<p>可视化是一项伟大的技术，它使非专业人士也能形象地感受到“虚无事物”的发展历程，向致力于可视化的科学家和程序员致敬！</p>

<p>豆瓣发展至今已经有 7 年了，2008 年的时候 <a href="http://www.douban.com/people/hongqn/">hongqn</a> 做过一个 <a href="http://www.youtube.com/watch?v=4f2xkvKAAXo">code swarm 视频</a>，时至今日，豆瓣的工程师队伍已经壮大了数倍，再次做了 code swarm 视频。</p>

<p>这次我使用了 <a href="http://www.michaelogawa.com/code_swarm/">code_swarm</a> 项目的一个 <a href="https://github.com/rictic/code_swarm">fork</a>，这个 fork 最大的亮点是加入了头像支持，除了 committer 的 ID 之外，还可以显示头像，使辨识度更高，按照项目的简要文档可以很容易地为单仓库生成视频。</p>

<p>code_swarm 接受的输入是一个配置文件和一个 XML 格式的 events 序列，每一个发生变更的文件加上 commiter ID 和时间（1970-01-01 00:00:00 起的毫秒数 / UNIX timestamp * 1000）组成一个 event，最终的 events 文件会是类似这个样子：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
</pre></td><td class='code'><pre><code class='xml'><span class='line'><span class="cp">&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;</span>
</span><span class='line'><span class="nt">&lt;file_events&gt;</span>
</span><span class='line'>  <span class="nt">&lt;event</span> <span class="na">date=</span><span class="s">&quot;1104508800000&quot;</span> <span class="na">author=</span><span class="s">&quot;bo&quot;</span> <span class="na">filename=</span><span class="s">&quot;/trunk/luz/doulist/douledit_ui.ptl&quot;</span><span class="nt">&gt;&lt;/event&gt;</span>
</span><span class='line'>  <span class="nt">&lt;event</span> <span class="err">…</span><span class="nt">&gt;&lt;/event&gt;</span>
</span><span class='line'><span class="nt">&lt;/file_events&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>为了生成 events 文件，需要先将代码仓库的变更历史导出，code_swarm 的 <a href="https://github.com/rictic/code_swarm/blob/master/bin/convert_logs.py">convert_logs.py</a> 工具提供了对多种版本仓库的支持，可以从仓库导出历史并转换为 events log，不过不太符合我的需求。</p>

<p>为了把很多个代码仓库做为一个整体来呈现，需要把所有仓库的历史按时间顺序合并到一起，于是我将这个过程分成了四个串行的步骤：</p>

<ol>
<li>从各个仓库导出历史</li>
<li>解析历史为中间格式并合并为一个文件：<code>&lt;committer ID&gt; &lt;timestamp&gt; &lt;file path&gt;</code></li>
<li>对中间格式的文件按时间排序</li>
<li>生成最终需要的 events log</li>
</ol>


<p>这个过程不再赘述，每个步骤都比较简单，这中间比较比较麻烦的是，在不同的代码仓库中尤其是分布式版本仓库中同一个 committer 的 ID 可能是不一样的，甚至同一个 committer 会有很多个不同的 ID，需要 normalize 为辨识度最高的那一个，另外还有一些系统账号的自动 commit，也需要排除在外。</p>

<p>另外一个输入是配置文件，可以在 <a href="https://github.com/rictic/code_swarm/blob/master/defaults/code_swarm.config">默认配置文件</a> 基础上做调整，以达到比较好的可视效果，这个步骤是整个过程中耗时最长，也是耗费硬件资源最多的，为了在大屏幕上有一个较好的效果，可能还需要修改一些硬编码在代码中的参数、更换或微调整物理引擎，不同的项目不同的变更历史不同的 committer 数量需要使用的参数差别可能比较大，只能逐个调整并测试…</p>

<p>另外需要准备每个 committer 的头像，使用 PNG 格式，以 committer 的 ID 命名，需要注意的是，头像需要是正方形的，可以借助 PIL 或 imagemagick 来做辅助的切割和格式、大小转换，长宽相差比较多的头像可能需要手工裁剪才能得到比较好的可视效果。</p>

<p>code_swarm 直接实时地呈现视频，但并不是直接输出视频文件，而是生成一帧一帧的图片文件，这样比较方便对最终的视频做定制，比较遗憾的是，code_swarm 没有 headless 模式，没有办法选择不显示实时视频而只生成图片文件，所以这个过程会很慢，在我的 MBP 990 上生成 7 年 12780 帧图片需要 40 分钟以上…</p>

<p>图片生成之后，可以使用视频编码工具将图片合并为视频，强大的 mencoder 和 ffmpeg 都能轻松完成这个任务，比如使用 ffmpeg 生成 quicktime 格式的视频是这样：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>ffmpeg -f image2 -r 43 -i ./code_swarm_frames/%05d.png <span class="se">\</span>
</span><span class='line'>  -sameq ./codebang.mov -pass 2
</span></code></pre></td></tr></table></div></figure>


<p>其中 -r 43 是指每秒钟 43 帧，因为需要配乐，对音乐做裁剪相对麻烦并且影响听觉效果，所以可以直接生成与音乐长度相当的视频，这里的 43FPS 是总帧数除以音频文件秒数得到的，帧速可以使用小数，所以可以精确控制视频长度。</p>

<p>项目演进过程中肯定会发生很多大事件，以字幕的形式标注这些大事件是一个比较好的方法，srt 是最简单的字幕格式，每一节字幕是这样的：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>1
</span><span class='line'>00:00:07,414 --> 00:00:11,414
</span><span class='line'>2005年3月6日 豆瓣上线</span></code></pre></td></tr></table></div></figure>


<p>第一行是字幕序号，递增，第二行是起始时间和结束时间，精确到毫秒，控制字幕出现和隐去的时间，第三行之后是字幕，可以有多行。两节字幕之间以空行分割。因为有大事记，所以都会有具体的日期，根据视频的长度，和仓库变更的起始、结束日期，可以计算出每一节字幕在视频中的绝对位置，所以生成字幕这个步骤可以半自动化，在测试搭配不同长度的音乐时可以节省不少手工调整字幕的时间。</p>

<p>最后一个步骤是将视频、音频和字幕合成为一个文件，我使用了 mencoder：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>mencoder -o codebang.avi -ovc lavc <span class="se">\</span>
</span><span class='line'>  -lavcopts <span class="nv">vcodec</span><span class="o">=</span>mpeg4:mbd<span class="o">=</span>2:vbitrate<span class="o">=</span>16000 <span class="se">\</span>
</span><span class='line'>  -oac mp3lame -lameopts cbr:br<span class="o">=</span>320:q<span class="o">=</span>0 -sub codebang.srt <span class="se">\</span>
</span><span class='line'>  -utf8 -subfont-text-scale 2.5 -overlapsub <span class="se">\</span>
</span><span class='line'>  -font <span class="s2">&quot;Hiragino Sans GB W6&quot;</span> -subpos 92 codebang.mov <span class="se">\</span>
</span><span class='line'>  -audiofile background.mp3
</span></code></pre></td></tr></table></div></figure>


<p>要得到好的可视效果，这个过程实际上还是挺耗费精力的，不过很值得，工程师嘛，就这点追求&#8230;</p>

<p>更新：Code swarm 视频在<a href="http://v.youku.com/v_show/id_XMzQzNDc4MDk2.html">这里</a>。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[No SOPA! 将我的域名从 Godaddy 转出]]></title>
    <link href="https://blog.xupeng.me/2011/12/23/no-sopa-transfer-my-domains-out-of-godaddy/"/>
    <updated>2011-12-23T23:49:00+00:00</updated>
    <id>https://blog.xupeng.me/2011/12/23/no-sopa-transfer-my-domains-out-of-godaddy</id>
    <content type="html"><![CDATA[<p>近来美国在尚未通过的 <a href="http://en.wikipedia.org/wiki/Stop_Online_Piracy_Act">SOPA</a> 法案上产生了巨大争议，该法案最邪恶的地方在于，它使得 ISP 和版权方有权利因为某网站上有一点侵权内容而“拔其网线” - 使其域名无法解析，此权利也很容易被滥用。</p>

<!-- more -->


<p>此举最大的获益者可能是日益衰落不思进取的传统出版、娱乐等行业，而代表先进技术生产力的互联网和互联网企业则遭受极大威胁，这是以损害创新为代价、以知识产权保护为名来庇护濒死的产业模式，实在是饮鸩止渴，阻碍信息传播和技术进步。</p>

<p>我的域名一直都是在 Godaddy 注册的，而 Godaddy 站在了 <a href="http://en.wikipedia.org/wiki/Stop_Online_Piracy_Act">SOPA</a> 支持者行列，颇让人意外，其实公开表示支持的公司还挺多的，比如 <a href="http://judiciary.house.gov/issues/Rouge%20Websites/SOPA%20Supporters.pdf">这里</a> 就有一个不完全列表，但令人感到欣慰的是，我喜欢或者因其而受益的美国公司或组织全都是坚定的 SOPA 反对者，比如：</p>

<ul>
<li><a href="http://www.google.com">Google</a></li>
<li><a href="http://www.facebook.com">Facebook</a></li>
<li><a href="https://twitter.com/">Twitter</a></li>
<li><a href="http://www.wikipedia.org">Wikipedia</a></li>
<li><a href="http://www.quora.com/">Quora</a></li>
<li><a href="http://www.linode.com/?r=cd5198f9daf6a668424aea5534f74baf343f4759">Linode</a></li>
<li><a href="https://github.com/">Github</a></li>
<li><a href="http://www.opendns.com/">OpenDNS</a></li>
<li>&#8230;</li>
</ul>


<p>等等等等。</p>

<p>鉴于此，我打算将我在 Godaddy 的域名全部 transfer 到别的域名注册商，name.com 和 namecheap.com 都是不错的选择，做了一些比较之后，我打算将域名 transfer 到 name.com，之所以选择 name.com，主要是因为 name.com 也算是一个比较老牌的注册商/代理商了，另外也是因为 name.com 的价格比较公道。</p>

<p>Transfer 的详细步骤就不重复了，互联网上已经有很多详细的流程解析，比如：</p>

<ul>
<li><a href="http://blog.jeffepstein.me/post/14629857835/a-step-by-step-guide-to-transfer-domains-out-of-godaddy">Transfer 到 namecheap</a></li>
<li>Transfer 到 name.com 可以参考上面链接的 Godaddy 部分和 <a href="http://www.name.com/faq/transfer-into-name.com">这里</a></li>
</ul>


<p>不同的域名注册商/代理商的 Transfer 步骤大同小异，大致的必要步骤是这样：</p>

<ol>
<li>从转出方获取域名授权码（Authorization Key / EPP Key)</li>
<li>在转入方填写要 transfer 的域名和对应的 EPP key</li>
<li>等待转入方向域名的 Administrative Contact email 发送验证邮件，收到邮件后根据提示确认</li>
<li>等待转入方开始 transfer，之后可能需要到转出方处授权允许转出</li>
<li>等待 transfer 完成，这个步骤需要几个小时到几天不等，耐心等待就好</li>
</ol>


<p>需要注意的是：</p>

<ol>
<li>域名需要是两个月之前注册的，比如 name.com 就不能 tranfser 两个月内新注册的域名</li>
<li>域名不能被锁定，也就是说 whois 信息中显示的状态里没有 clientTransferProhibited，可以先在转出方那里解锁</li>
<li>你能够通过 whois 信息中显示的 Administrative Contact email 接收邮件</li>
</ol>


<p>有一些 transfer 域名的小提示和建议：</p>

<ul>
<li>利用一些 coupon 节省开支，比如 name.com 可以使用 <code>NODADDY</code> 这个 coupon 节省 10% 的 transfer 费用</li>
<li>有的域名注册商会将转出方处设定的域名记录迁移过去，这样的话整个域名的迁移是平滑的，不至于有不可解析的时间</li>
<li>或者预先在转入方设定要使用的域名服务器，比如我使用的是 Linode 的 DNS 服务，我先在 name.com 处填写 Linode 的 DNS server，这样也可以做到平滑迁移</li>
</ul>


<p>另外，还有一个费用和域名有效期的问题，比如之前我的 xupeng.me 到期时间是 2013/10/29，我需要支付一年的 transfer 费用，transfer 完成之后，域名的有效时间延期一年到 2014/10/29。</p>

<p>更新（2011-11-24 08:36）：很高兴一早就看到了 Godaddy 不再支持 SOPA 的消息！一点微薄的行动有了效果，说不定我花两个小时迁出几个域名是压垮 Godaddy 的最后一根稻草 :) 就 Godaddy 支持 SOPA 这件事本身而言，从 Godady 转不转出域名都对个人都没什么大不了的影响， 转出只代表一种立场，代表眼里揉不得沙子的态度，用自己的实际行动投出微薄的一个反对票，使得反对者更多，最终使立法失败！</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[迁移到 Octopress]]></title>
    <link href="https://blog.xupeng.me/2011/12/14/migrate-to-octopress/"/>
    <updated>2011-12-14T22:23:00+00:00</updated>
    <id>https://blog.xupeng.me/2011/12/14/migrate-to-octopress</id>
    <content type="html"><![CDATA[<p>用了三年多 Wordpress，由于实在很懒，没有写过多少东西，但跑在 <a href="http://www.linode.com/?r=cd5198f9daf6a668424aea5534f74baf343f4759">Linode VPS</a> 上的 Wordpress 却一直占用了很多资源，几个 <a href="http://php-fpm.org/">PHP-FPM</a> 进程加上 MySQL 就用掉了将近 400MB 内存，却没有什么访问量，觉得很不划算，再加上 Wrodpress 越来越臃肿，就想把它换成一个静态内容发布系统。</p>

<!-- more -->


<p>简单看了一下，很快就找到了 <a href="http://octopress.org/">Octopress</a>，一眼就看上了，花了半个小时试用，还是很符合我的使用习惯的：</p>

<ol>
<li>配置简单，简单修个几个配置就能使用</li>
<li><a href="http://daringfireball.net/projects/markdown/">Markdown</a> 语法，VIM + Terminal 就能完成内容书写与发布</li>
<li>定制简单，plugin 看起来也不复杂，不过写 plugin 要学一点 ruby</li>
<li>默认的模版就很漂亮，也很便于阅读</li>
</ol>


<p>花了几个小时把 Wordpress 里旧的文章导出、导入到 Octopress，手工编辑了部分不像样的文章，一个焕然一新的静态发布 blog 就基本可用了，在这个过程中我主要参考了这些文档和文章：</p>

<ul>
<li><a href="http://octopress.org/docs/">http://octopress.org/docs/</a></li>
<li><a href="http://felipecypriano.com/2011/09/16/why-ive-migrated-to-octopress/">http://felipecypriano.com/2011/09/16/why-ive-migrated-to-octopress/</a></li>
<li><a href="http://mattgemmell.com/2011/09/12/blogging-with-octopress/">http://mattgemmell.com/2011/09/12/blogging-with-octopress/</a></li>
</ul>


<p>把这些读一遍就能顺利完成迁移和部署，所以就不重复步骤了。</p>

<p>问题也是有的，比如插件过少，甚至原作者都没有提供 Tag Cloud 支持，可能也有 bug，比如我试用时使用的 base URL 是 <code>http://o.xupeng.me</code>，迁移完毕换成正式的 URL <code>http://blog.xupeng.me</code> 后，重新生成的静态页面和 feed 内的 URL 会在新旧两个 URL 之间随机变化，导致 <a href="http://ifttt.com/">ifttt</a> 认为我 blog 上的文章全都发生了变化，向我的 twitter 上发了一大堆信息，我删除了 cache 目录之后这个问题没有再出现过。</p>

<p>不算是一个完美的系统，不过基本上能够满足我的需求，之后有空再补上缺少的东西吧。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[2.6.38 / 2.6.39 + XFS 的性能极差]]></title>
    <link href="https://blog.xupeng.me/2011/12/11/poor-performance-with-xfs-2-6-38-and-2-6-39/"/>
    <updated>2011-12-11T00:00:00+00:00</updated>
    <id>https://blog.xupeng.me/2011/12/11/poor-performance-with-xfs-2-6-38-and-2-6-39</id>
    <content type="html"><![CDATA[<p>线上的 MySQL 服务器一直都在使用 XFS 文件系统，性能和稳定性都表现良好，使用的内核版本是 2.6.29，经过时间和访问压力的验证，表现也不错。</p>

<p>前一段时间在测试几款 SSD 产品，考虑到 XFS 在内核 2.6.38 之后才加入了对 FITRIM 的支持（<a href="http://xfs.org/index.php/FITRIM/discard">ref1</a>  <a href="http://xfs.org/index.php/Support_discarding_of_unused_sectors">ref2</a>），就在 2.6.38 和 2.6.39 上对 SSD 做了测试，测试结果却让人大跌眼镜，XFS 在 2.6.38 和 2.6.39 之上的性能差到完全不能接受。</p>

<!-- more -->


<p>我在内核 2.6.29 和 2.6.39 下使用 <a href="http://freecode.com/projects/fio">fio</a> 分别对 raw device、XFS 和 ext4 做了一个简单的 IO 测试，测试参数是这样的：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
</pre></td><td class='code'><pre><code class='bash'><span class='line'>fio --filename<span class="o">=</span><span class="nv">$PATH</span>-TO-TEST-FILE --direct<span class="o">=</span>1 --rw<span class="o">=</span>randrw --bs<span class="o">=</span>16k <span class="se">\</span>
</span><span class='line'>    --size<span class="o">=</span>50G --numjobs<span class="o">=</span>16 --runtime<span class="o">=</span>120 --group_reporting <span class="se">\</span>
</span><span class='line'>    --name<span class="o">=</span><span class="nb">test</span> --rwmixread<span class="o">=</span>90 --thread --ioengine<span class="o">=</span>psync
</span></code></pre></td></tr></table></div></figure>


<p>大意是：使用 non-buffered I/O，16 个并发 IO 线程，做 16KB 块大小的随机读写混合测试，读写比为 9:1</p>

<p>下面是 2.6.29 下的测试结果：</p>

<ul>
<li><a href="https://blog.xupeng.me/downloads/ext4-xfs-perf/2.6.29-raw.txt">2.6.29 + raw device</a></li>
<li><a href="https://blog.xupeng.me/downloads/ext4-xfs-perf/2.6.29-xfs.txt">2.6.29 + XFS</a></li>
<li><a href="https://blog.xupeng.me/downloads/ext4-xfs-perf/2.6.29-ext4.txt">2.6.29 + ext4</a></li>
</ul>


<p>可以看到，ext4, XFS 和 raw device 的 IOPS 都在 12000 上下，考虑到测试之间会有误差，基本可以认为三者的性能一样。</p>

<p>下面是 2.6.39 下的测试结果：</p>

<ul>
<li><a href="https://blog.xupeng.me/downloads/ext4-xfs-perf/2.6.39-raw.txt">2.6.39 + raw device</a></li>
<li><a href="https://blog.xupeng.me/downloads/ext4-xfs-perf/2.6.39-xfs.txt">2.6.39 + XFS</a></li>
<li><a href="https://blog.xupeng.me/downloads/ext4-xfs-perf/2.6.39-ext4.txt">2.6.39 + ext4</a></li>
</ul>


<p>ext4 和 raw device 的 IOPS 约为 12900，比在 2.6.29 下略有提升，但是 XFS 的性能就有点惨不忍睹了，竟然只有不到 4000。</p>

<p>尝试了很多种不同的 XFS 格式化、mount 参数，IOPS 都只是在 4000 左右徘徊，还没有找到解决方案，目前在部分服务器上尝试使用了 ext4，进一步监测 ext4 的性能和稳定性，或许未来会在数据库服务器上全部使用 ext4。</p>

<p>更新：在 XFS 邮件列表咨询之后，确认了这是 XFS 的已知 bug：</p>

<blockquote><p>commit 686da49e5aa50117d8d824c579c3fd9e0318fbc6<br/>Author: Dave Chinner <dchinner @redhat.com><br/>Date:   Thu Dec 1 17:27:39 2011 -0600</p><p>    xfs: don&#8217;t serialise direct IO reads on page cache checks<br/>    <br/>    commit 0c38a2512df272b14ef4238b476a2e4f70da1479 upstream.<br/>    <br/>    There is no need to grab the i_mutex of the IO lock in exclusive<br/>    mode if we don&#8217;t need to invalidate the page cache. Taking these<br/>    locks on every direct IO effective serialises them as taking the IO<br/>    lock in exclusive mode has to wait for all shared holders to drop<br/>    the lock. That only happens when IO is complete, so effective it<br/>    prevents dispatch of concurrent direct IO reads to the same inode.<br/>    <br/>    Fix this by taking the IO lock shared to check the page cache state,<br/>    and only then drop it and take the IO lock exclusively if there is<br/>    work to be done. Hence for the normal direct IO case, no exclusive<br/>    locking will occur.<br/></dchinner></p><footer><strong>Dave Chinner</strong> <cite><a href='http://www.spinics.net/lists/xfs/msg08688.html'>www.spinics.net/lists/xfs/&hellip;</a></cite></footer></blockquote>


<p>3.0.11, 3.1.5 和 3.2-rc1 中已经修复。XFS 用户在升级内核时需要注意，升级到修复后的版本，并做充分的性能测试。</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[SCGI 与线程]]></title>
    <link href="https://blog.xupeng.me/2011/12/08/scgi-and-threading/"/>
    <updated>2011-12-08T00:00:00+00:00</updated>
    <id>https://blog.xupeng.me/2011/12/08/scgi-and-threading</id>
    <content type="html"><![CDATA[<p>最近在写一个配置推送客户端，结构如下图：</p>

<p><img src="https://blog.xupeng.me/downloads/2011/12/cfgreceiver.png" title="cfgreceiver architecture" alt="cfgreceiver architecture" /></p>

<p>每一个应用服务进程会起一个额外的线程，与 ZooKeeper 保持连接，需要变更配置时，将新配置更新到 ZooKeeper，ZooKeeper 将配置推送到所有的客户端，客户端收到配置之后，即时更新进程内的配置信息，并将更新配置成功与否、延时、错误等信息反馈到 redis，以这样的方式做到不重启服务更新配置。</p>

<!-- more -->


<p>同时也会有一个独立的客户端与 ZooKeeper 保持连接，收到 ZooKeeper 推送的配置之后，将配置写回并提交到 Puppet 配置仓库中，这样仍然保持只在一个地方修改配置的习惯。</p>

<p>这个推送客户端的第一个应用场景是更新线上的 MySQL 配置，线上服务是 Quixote + SCGI，由于在 SCGI server fork 子进程之前就已经 import 了部分自有库，其中包括这个配置推送客户端的使用者，因此，这个客户端必须是 lazy 的，只能在 fork 发生之后才能启动线程、建立与 ZooKeeper 和 redis 的连接。</p>

<p>将这个客户端与 Quixote + SCGI 服务进行联调时，奇怪的事情发生了，配置发生变更时，客户端并没有立刻收到 ZooKeeper 的推送，而是等到下一次用户请求到达时才会收到，起初以为是线程根本就没有在工作，后来发现，线程也并不是完全不工作，而是阻塞在了某个地方，当有用户请求到达时，线程才会接着执行。这就意味着，如果一个进程闲置了比较长的时间，ZooKeeper 会认为客户端已失去响应，从而断开连接，而客户端重连 ZooKeeper 也只会发生在下一次请求到来之后，这是个很别扭很诡异的问题。</p>

<p>去掉目前使用的 Quixote 包装，写了一个最简单的 Quixote app，发现同样的问题依然存在，这排除了自有 Quixote 包装的问题。接下来又写了一个裸的 SCGI app：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="c">#!/usr/bin/env python</span>
</span><span class='line'><span class="c"># t.py</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">sys</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">time</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">threading</span>
</span><span class='line'>
</span><span class='line'><span class="k">class</span> <span class="nc">T</span><span class="p">(</span><span class="n">threading</span><span class="o">.</span><span class="n">Thread</span><span class="p">):</span>
</span><span class='line'>    <span class="n">daemon</span> <span class="o">=</span> <span class="bp">True</span>
</span><span class='line'>    <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
</span><span class='line'>        <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
</span><span class='line'>            <span class="k">print</span> <span class="o">&gt;&gt;</span> <span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">,</span> <span class="s">&#39;sleeping&#39;</span>
</span><span class='line'>            <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</span><span class='line'>            <span class="k">print</span> <span class="o">&gt;&gt;</span> <span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">,</span> <span class="s">&#39;id:</span><span class="si">%s</span><span class="s"> time:</span><span class="si">%s</span><span class="se">\n</span><span class="s">&#39;</span> <span class="o">%</span> <span class="p">(</span><span class="nb">id</span><span class="p">(</span><span class="bp">self</span><span class="p">),</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">())</span>
</span><span class='line'>
</span><span class='line'><span class="n">t</span> <span class="o">=</span> <span class="n">T</span><span class="p">()</span>
</span></code></pre></td></tr></table></div></figure>




<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="c">#!/usr/bin/env python</span>
</span><span class='line'><span class="c"># s.py</span>
</span><span class='line'><span class="kn">import</span> <span class="nn">sys</span>
</span><span class='line'><span class="kn">from</span> <span class="nn">scgi</span> <span class="kn">import</span> <span class="n">scgi_server</span>
</span><span class='line'><span class="kn">from</span> <span class="nn">t</span> <span class="kn">import</span> <span class="n">t</span>
</span><span class='line'>
</span><span class='line'><span class="k">class</span> <span class="nc">MyHandler</span><span class="p">(</span><span class="n">scgi_server</span><span class="o">.</span><span class="n">SCGIHandler</span><span class="p">):</span>
</span><span class='line'><span class="k">def</span> <span class="nf">produce_cgilike</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">env</span><span class="p">,</span> <span class="n">bodysize</span><span class="p">):</span>
</span><span class='line'>    <span class="k">if</span> <span class="ow">not</span> <span class="n">t</span><span class="o">.</span><span class="n">is_alive</span><span class="p">():</span>
</span><span class='line'>        <span class="k">print</span> <span class="o">&gt;&gt;</span> <span class="n">sys</span><span class="o">.</span><span class="n">stderr</span><span class="p">,</span> <span class="s">&#39;start thread: </span><span class="si">%s</span><span class="s">&#39;</span> <span class="o">%</span> <span class="nb">id</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
</span><span class='line'>        <span class="n">t</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</span><span class='line'>    <span class="k">else</span><span class="p">:</span>
</span><span class='line'>        <span class="n">sys</span><span class="o">.</span><span class="n">stdout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s">&#39;Content-Type: text/plain</span><span class="se">\r\n\r\n</span><span class="s">&#39;</span><span class="p">)</span>
</span><span class='line'>        <span class="k">print</span> <span class="nb">id</span><span class="p">(</span><span class="n">t</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'><span class="n">scgi_server</span><span class="o">.</span><span class="n">SCGIServer</span><span class="p">(</span><span class="n">MyHandler</span><span class="p">,</span> <span class="n">host</span><span class="o">=</span><span class="s">&#39;0.0.0.0&#39;</span><span class="p">,</span><span class="n">port</span><span class="o">=</span><span class="mi">9002</span><span class="p">)</span><span class="o">.</span><span class="n">serve</span><span class="p">()</span>
</span></code></pre></td></tr></table></div></figure>


<p>这样的一个服务仍然有上面提到的问题，每有一次用户访问，就会输出一行 &#8220;id:xxx time:xxx&#8221;，然后输出一行 &#8220;sleeping&#8221;，之后就阻塞在了 time.sleep(1) 处（起码看起来是阻塞在了这里）。至此，也可以排除 Quixote 的问题，问题应该出在 SCGI 这里，向 <a href="http://www.douban.com/people/hongqn/">hongqn</a> 请教之后，最终定位到了问题所在。</p>

<p>在 SCGIHandler.serve 方法中：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class='python'><span class='line'><span class="n">os</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">parent_fd</span><span class="p">,</span> <span class="s">&quot;1&quot;</span><span class="p">)</span> <span class="c"># indicates that child is ready</span>
</span><span class='line'><span class="n">fd</span> <span class="o">=</span> <span class="n">passfd</span><span class="o">.</span><span class="n">recvfd</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">parent_fd</span><span class="p">)</span><span class="o">&lt;/</span><span class="n">pre</span><span class="o">&gt;</span>
</span></code></pre></td></tr></table></div></figure>


<p>passfd.recvfd 是一个阻塞读，它的代码是这样的：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
</pre></td><td class='code'><pre><code class='c'><span class='line'><span class="k">static</span> <span class="n">PyObject</span> <span class="o">*</span>
</span><span class='line'><span class="nf">passfd_recvfd</span><span class="p">(</span><span class="n">PyObject</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="n">PyObject</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="kt">int</span> <span class="n">sockfd</span><span class="p">,</span> <span class="n">fd</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">PyArg_ParseTuple</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="s">&quot;i:revcfd&quot;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sockfd</span><span class="p">))</span>
</span><span class='line'>        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">((</span><span class="n">fd</span> <span class="o">=</span> <span class="n">recv_fd</span><span class="p">(</span><span class="n">sockfd</span><span class="p">))</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="n">PyErr_SetFromErrno</span><span class="p">(</span><span class="n">PyExc_IOError</span><span class="p">);</span>
</span><span class='line'>        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">return</span> <span class="n">PyInt_FromLong</span><span class="p">((</span><span class="kt">long</span><span class="p">)</span> <span class="n">fd</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>fd = recv_fd(sockfd) 是一个阻塞读，在开始阻塞读之前没有释放 GIL ，于是就导致了整个解释器阻塞，这也与之前问题的症状吻合。将代码作如下修改，在开始阻塞读之前释放 GIL，可以解决这个问题：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
</pre></td><td class='code'><pre><code class='c'><span class='line'><span class="k">static</span> <span class="n">PyObject</span> <span class="o">*</span>
</span><span class='line'><span class="nf">passfd_recvfd</span><span class="p">(</span><span class="n">PyObject</span> <span class="o">*</span><span class="n">self</span><span class="p">,</span> <span class="n">PyObject</span> <span class="o">*</span><span class="n">args</span><span class="p">)</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>    <span class="kt">int</span> <span class="n">sockfd</span><span class="p">,</span> <span class="n">fd</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">PyArg_ParseTuple</span><span class="p">(</span><span class="n">args</span><span class="p">,</span> <span class="s">&quot;i:revcfd&quot;</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sockfd</span><span class="p">))</span>
</span><span class='line'>        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="n">Py_BEGIN_ALLOW_THREADS</span>
</span><span class='line'>    <span class="n">fd</span> <span class="o">=</span> <span class="n">recv_fd</span><span class="p">(</span><span class="n">sockfd</span><span class="p">);</span>
</span><span class='line'>    <span class="n">Py_END_ALLOW_THREADS</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="n">fd</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="n">PyErr_SetFromErrno</span><span class="p">(</span><span class="n">PyExc_IOError</span><span class="p">);</span>
</span><span class='line'>        <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span><span class='line'>    <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">return</span> <span class="n">PyInt_FromLong</span><span class="p">((</span><span class="kt">long</span><span class="p">)</span> <span class="n">fd</span><span class="p">);</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></td></tr></table></div></figure>


<p>当然了，还需要做进一步的测试和观察，看一下打这样的 patch 之后会不会有其他的副作用。</p>

<p>参考：<a href="http://docs.python.org/release/2.6.7/c-api/init.html#thread-state-and-the-global-interpreter-lock">http://docs.python.org/release/2.6.7/c-api/init.html#thread-state-and-the-global-interpreter-lock</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Lion下精确调整音量]]></title>
    <link href="https://blog.xupeng.me/2011/09/23/precisely-adjust-volume-in-lion/"/>
    <updated>2011-09-23T00:00:00+00:00</updated>
    <id>https://blog.xupeng.me/2011/09/23/precisely-adjust-volume-in-lion</id>
    <content type="html"><![CDATA[<p>升级到 Lion 后一直在忍受着很大的音量，戴着入耳一格音量也觉得很大，找了两句 applescript，写成了一个符合我使用习惯的脚本，这下可以精确地把音量调整为舒适的大小了：</p>

<!-- more -->




<figure class='code'><figcaption><span>Adjust volume  (adjust-volume)</span> <a href='https://blog.xupeng.me/downloads/code/adjust-volume'>download</a></figcaption>
 <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
</pre></td><td class='code'><pre><code class='applescript'><span class='line'><span class="c">#!/usr/bin/osascript</span>
</span><span class='line'>
</span><span class='line'><span class="k">on</span> <span class="nv">usage</span><span class="p">()</span>
</span><span class='line'>    <span class="k">set</span> <span class="err">_usage</span> <span class="k">to</span> <span class="s2">&quot;Usage: adjust-volume number\n&quot;</span>
</span><span class='line'>    <span class="k">set</span> <span class="err">_usage</span> <span class="k">to</span> <span class="err">_usage</span> <span class="o">&amp;</span> <span class="s2">&quot;For example:\n&quot;</span>
</span><span class='line'>    <span class="k">set</span> <span class="err">_usage</span> <span class="k">to</span> <span class="err">_usage</span> <span class="o">&amp;</span> <span class="s2">&quot;  adjust-volume 2\t - increase volume by 2\n&quot;</span>
</span><span class='line'>    <span class="k">set</span> <span class="err">_usage</span> <span class="k">to</span> <span class="err">_usage</span> <span class="o">&amp;</span> <span class="s2">&quot;  adjust-volume -2\t - decrease volume by 2&quot;</span>
</span><span class='line'>    <span class="no">return</span> <span class="err">_usage</span>
</span><span class='line'><span class="k">end</span> <span class="nv">usage</span>
</span><span class='line'>
</span><span class='line'><span class="k">on</span> <span class="nb">run</span> <span class="nv">argv</span>
</span><span class='line'>    <span class="k">if</span> <span class="p">(</span><span class="nb">count</span> <span class="nv">argv</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">1</span> <span class="k">then</span>
</span><span class='line'>        <span class="no">return</span> <span class="nv">usage</span><span class="p">()</span>
</span><span class='line'>    <span class="k">end</span> <span class="k">if</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">set</span> <span class="nv">currentVolume</span> <span class="k">to</span> <span class="nv">output</span> <span class="na">volume</span> <span class="k">of</span> <span class="p">(</span><span class="nb">get volume settings</span><span class="p">)</span>
</span><span class='line'>    <span class="nb">set volume</span> <span class="nv">output</span> <span class="na">volume</span> <span class="p">(</span><span class="nv">currentVolume</span> <span class="o">+</span> <span class="p">(</span><span class="nb">item</span> <span class="mi">1</span> <span class="k">of</span> <span class="nv">argv</span><span class="p">))</span>
</span><span class='line'><span class="k">end</span> <span class="nb">run</span>
</span></code></pre></td></tr></table></div></figure>


<p>使用方法：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>Usage: adjust-volume number
</span><span class='line'>For example:
</span><span class='line'>  adjust-volume 2  - increase volume by 2
</span><span class='line'>  adjust-volume -2     - decrease volume by 2</span></code></pre></td></tr></table></div></figure>



]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[MySQL collation 问题]]></title>
    <link href="https://blog.xupeng.me/2011/09/07/mysql-collation/"/>
    <updated>2011-09-07T00:00:00+00:00</updated>
    <id>https://blog.xupeng.me/2011/09/07/mysql-collation</id>
    <content type="html"><![CDATA[<p>在从 5.0.x 向 5.1.x 升级的过程中，使用 mysqldump 备份某张表之后，向 5.1.x 中导入时却遇到重复数据的错误：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>ERROR 1062 (23000) at line 65: Duplicate entry '1003-' for key 'uk_cat_name'</span></code></pre></td></tr></table></div></figure>




<!-- more -->


<p>这张表的schema是这样的：</p>

<figure class='code'> <div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='sql'><span class='line'><span class="k">CREATE</span> <span class="k">TABLE</span> <span class="o">`</span><span class="n">tag</span><span class="o">`</span> <span class="p">(</span>
</span><span class='line'>  <span class="o">`</span><span class="n">id</span><span class="o">`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="n">AUTO_INCREMENT</span><span class="p">,</span>
</span><span class='line'>  <span class="o">`</span><span class="n">name</span><span class="o">`</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">255</span><span class="p">)</span> <span class="nb">CHARACTER</span> <span class="k">SET</span> <span class="n">ucs2</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">DEFAULT</span> <span class="s1">&#39;&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="o">`</span><span class="k">count</span><span class="o">`</span> <span class="nb">int</span><span class="p">(</span><span class="mi">11</span><span class="p">)</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">DEFAULT</span> <span class="s1">&#39;0&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="o">`</span><span class="n">cat_id</span><span class="o">`</span> <span class="n">mediumint</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span> <span class="n">unsigned</span> <span class="k">NOT</span> <span class="k">NULL</span> <span class="k">DEFAULT</span> <span class="s1">&#39;0&#39;</span><span class="p">,</span>
</span><span class='line'>  <span class="k">PRIMARY</span> <span class="k">KEY</span> <span class="p">(</span><span class="o">`</span><span class="n">id</span><span class="o">`</span><span class="p">),</span>
</span><span class='line'>  <span class="k">UNIQUE</span> <span class="k">KEY</span> <span class="o">`</span><span class="n">uk_cat_name</span><span class="o">`</span> <span class="p">(</span><span class="o">`</span><span class="n">cat_id</span><span class="o">`</span><span class="p">,</span><span class="o">`</span><span class="n">name</span><span class="o">`</span><span class="p">),</span>
</span><span class='line'>  <span class="k">KEY</span> <span class="o">`</span><span class="n">idx_cat_count</span><span class="o">`</span> <span class="p">(</span><span class="o">`</span><span class="n">cat_id</span><span class="o">`</span><span class="p">,</span><span class="o">`</span><span class="k">count</span><span class="o">`</span><span class="p">)</span>
</span><span class='line'><span class="p">)</span> <span class="n">ENGINE</span><span class="o">=</span><span class="n">InnoDB</span> <span class="n">AUTO_INCREMENT</span><span class="o">=</span><span class="mi">2545965</span> <span class="k">DEFAULT</span> <span class="n">CHARSET</span><span class="o">=</span><span class="n">latin1</span>
</span></code></pre></td></tr></table></div></figure>


<p>分析 mysqldump 生成的 SQL 之后，发现数据本身并没有问题，并且向原有的 5.0.x 中导入也没有问题，那么问题应该是出在向 5.1.x 导入的过程中，但仍不明白其原因，于是去掉了 UNIQUE KEY constraint，导入数据后，发现的确有重复的数据：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mysql> select name, cat_id, count(1) as cnt from tag group by cat_id, name having cnt>1;
</span><span class='line'>+--------------+--------+-----+
</span><span class='line'>| name         | cat_id | cnt |
</span><span class='line'>+--------------+--------+-----+
</span><span class='line'>| Günter_Gras  |   1001 |   2 |
</span><span class='line'>| Strauß       |   1001 |   2 |
</span><span class='line'>| Suskind      |   1001 |   2 |
</span><span class='line'>| sas          |   1002 |   2 |
</span><span class='line'>| Weise        |   1002 |   2 |
</span><span class='line'>| R.Strauß     |   1003 |   2 |
</span><span class='line'>| Strauß       |   1003 |   2 |
</span><span class='line'>+--------------+--------+-----+
</span><span class='line'>7 rows in set (2.30 sec)</span></code></pre></td></tr></table></div></figure>


<p>那就很奇怪了，使用 mysqldump 导出之前，数据是满足 UNIQUE KEY 约束的，并没有重复数据，导出的 SQL 也是正确的，为什么再次导入时反倒出现了重复数据呢？翻了几篇文档之后有了一些眉目，问题应该是出在 5.1.x 的 collation 上，有两个 &#8220;bug&#8221; tickets 描述了我遇到的情况：</p>

<ul>
<li><a href="http://bugs.mysql.com/bug.php?id=27877">http://bugs.mysql.com/bug.php?id=27877</a></li>
<li><a href="http://bugs.mysql.com/bug.php?id=43306">http://bugs.mysql.com/bug.php?id=43306</a></li>
</ul>


<p>那么 collation 是什么呢？collation 是在特定字符集内用于比较（排序）字符的规则，比如A和B谁在前谁在后，要不要区分大小写等等，不同的字符集有多种不同的比较（排序）规则，比如我们常见的 utf8_general_ci 就是一种大小写不敏感的规则，<a href="http://en.wikipedia.org/wiki/Collation">Wikipedia</a> 和 <a href="http://dev.mysql.com/doc/refman/5.1/en/charset-general.html">MySQL 文档</a> 有详细的描述。</p>

<p>在我遇到问题的这个场景下，相关字段的定义是 `name` varchar(255) CHARACTER SET ucs2 NOT NULL DEFAULT &#8221;，而 ucs2 这个字符集的默认 collation 是 ucs2_general_ci：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mysql> show character set like 'ucs2';
</span><span class='line'>+---------+---------------+-------------------+--------+
</span><span class='line'>| Charset | Description   | Default collation | Maxlen |
</span><span class='line'>+---------+---------------+-------------------+--------+
</span><span class='line'>| ucs2    | UCS-2 Unicode | ucs2_general_ci   |      2 |
</span><span class='line'>+---------+---------------+-------------------+--------+
</span><span class='line'>1 row in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>5.0.x 时没有问题，而 5.1.x 下有上面的 &#8220;bug&#8221; 中提到的问题，亦即导致我遇到的 duplicate key 问题的原因。</p>

<p>那么 ucs2 这个字符集的collation有哪些呢？</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mysql> show collation like 'ucs2%';
</span><span class='line'>+--------------------+---------+-----+---------+----------+---------+
</span><span class='line'>| Collation          | Charset | Id  | Default | Compiled | Sortlen |
</span><span class='line'>+--------------------+---------+-----+---------+----------+---------+
</span><span class='line'>| ucs2_general_ci    | ucs2    |  35 | Yes     | Yes      |       1 |
</span><span class='line'>| ucs2_bin           | ucs2    |  90 |         | Yes      |       1 |
</span><span class='line'>| ucs2_unicode_ci    | ucs2    | 128 |         | Yes      |       8 |
</span><span class='line'>| ucs2_icelandic_ci  | ucs2    | 129 |         | Yes      |       8 |
</span><span class='line'>| ucs2_latvian_ci    | ucs2    | 130 |         | Yes      |       8 |
</span><span class='line'>| ucs2_romanian_ci   | ucs2    | 131 |         | Yes      |       8 |
</span><span class='line'>| ucs2_slovenian_ci  | ucs2    | 132 |         | Yes      |       8 |
</span><span class='line'>| ucs2_polish_ci     | ucs2    | 133 |         | Yes      |       8 |
</span><span class='line'>| ucs2_estonian_ci   | ucs2    | 134 |         | Yes      |       8 |
</span><span class='line'>| ucs2_spanish_ci    | ucs2    | 135 |         | Yes      |       8 |
</span><span class='line'>| ucs2_swedish_ci    | ucs2    | 136 |         | Yes      |       8 |
</span><span class='line'>| ucs2_turkish_ci    | ucs2    | 137 |         | Yes      |       8 |
</span><span class='line'>| ucs2_czech_ci      | ucs2    | 138 |         | Yes      |       8 |
</span><span class='line'>| ucs2_danish_ci     | ucs2    | 139 |         | Yes      |       8 |
</span><span class='line'>| ucs2_lithuanian_ci | ucs2    | 140 |         | Yes      |       8 |
</span><span class='line'>| ucs2_slovak_ci     | ucs2    | 141 |         | Yes      |       8 |
</span><span class='line'>| ucs2_spanish2_ci   | ucs2    | 142 |         | Yes      |       8 |
</span><span class='line'>| ucs2_roman_ci      | ucs2    | 143 |         | Yes      |       8 |
</span><span class='line'>| ucs2_persian_ci    | ucs2    | 144 |         | Yes      |       8 |
</span><span class='line'>| ucs2_esperanto_ci  | ucs2    | 145 |         | Yes      |       8 |
</span><span class='line'>| ucs2_hungarian_ci  | ucs2    | 146 |         | Yes      |       8 |
</span><span class='line'>+--------------------+---------+-----+---------+----------+---------+
</span><span class='line'>21 rows in set (0.00 sec)</span></code></pre></td></tr></table></div></figure>


<p>上面可以看到，有针对不同语言的排序规则，在我的场景下，并没有此需求，那么我尝试一下 ucs2_bin，按照二进制排序：</p>

<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>mysql> alter table tag modify `name` varchar(255) CHARACTER SET ucs2 collate ucs2_bin not null default '';
</span><span class='line'>Query OK, 2436674 rows affected (56.40 sec)
</span><span class='line'>Records: 2436674  Duplicates: 0  Warnings: 0
</span><span class='line'>
</span><span class='line'>mysql> show create table tag\G
</span><span class='line'>*************************** 1. row ***************************
</span><span class='line'>       Table: tag
</span><span class='line'>Create Table: CREATE TABLE `tag` (
</span><span class='line'>  `id` int(11) NOT NULL AUTO_INCREMENT,
</span><span class='line'>  `name` varchar(255) CHARACTER SET ucs2 COLLATE ucs2_bin NOT NULL DEFAULT '',
</span><span class='line'>  `count` int(11) NOT NULL DEFAULT '0',
</span><span class='line'>  `cat_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
</span><span class='line'>  PRIMARY KEY (`id`),
</span><span class='line'>  KEY `uk_cat_name` (`cat_id`,`name`),
</span><span class='line'>  KEY `idx_cat_count` (`cat_id`,`count`)
</span><span class='line'>) ENGINE=InnoDB AUTO_INCREMENT=2545965 DEFAULT CHARSET=latin1
</span><span class='line'>1 row in set (0.00 sec)
</span><span class='line'>
</span><span class='line'>mysql> select name, cat_id, count(1) as cnt from tag group by cat_id, name having cnt>1;
</span><span class='line'>Empty set (2.22 sec)</span></code></pre></td></tr></table></div></figure>


<p>可以看到，使用 ucs2_bin collation 之后，如预期的结果一样，原本不同的字符串被正确地区分开了。</p>

<p>对于如何选择合适的 collation，官方文档有描述，也有一些讨论，比如 <a href="http://stackoverflow.com/questions/367711/what-is-the-best-collation-to-use-for-mysql-with-php">What is the best collation to use for MySQL (with PHP)</a>。</p>
]]></content>
  </entry>
  
</feed>
