<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title><![CDATA[Felix021]]></title> 
<link>https://www.felix021.com/blog/index.php</link> 
<description><![CDATA[So far so good]]></description> 
<language>zh-cn</language> 
<copyright><![CDATA[Felix021]]></copyright>
<ttl>10</ttl>
<item>
    <title><![CDATA[蛋疼的mysql_ping()以及MYSQL_OPT_RECONNECT]]></title> 
    <link>https://www.felix021.com/blog/read.php?2102</link>
    <description><![CDATA[昨天@Zind同学找到我之前的<a href="/blog/read.php?2061" target="_blank">一篇blog</a>(已经修改)，里面提到了mysql_ping和MYSQL_OPT_RECONNECT的一些事情。<br/><br/>之所以写那篇blog，是因为去年写的一些代码遇到了“2006:MySQL server has gone away”错误。这个问题是因为wait_timeout这个参数的默认值是28800，也就是说，如果一个连接连续8个小时没有任何请求，那么Server端就会把它断开。在测试环境中一个晚上没有请求很正常……于是第二天早上来的时候就发现这个错误了。<br/><br/>其实我有考虑这个问题的，真的……因为我知道php里面有个函数叫做mysql_ping()，PHP手册上说：“mysql_ping() 检查到服务器的连接是否正常。如果断开，则自动尝试连接。本函数可用于空闲很久的脚本来检查服务器是否关闭了连接，如果有必要则重新连接上。”<br/><br/>回想起来，以前真是很傻很天真。根据MySQL官方C API里<a href="http://dev.mysql.com/doc/refman/5.1/en/mysql-ping.html" target="_blank">mysql_ping()的文档</a>：&quot;Checks whether the connection to the server is working. If the connection has gone down <strong>and auto-reconnect is enabled</strong> an attempt to reconnect is made. ... Auto-reconnect is disabled by default. To enable it, call mysql_options() with the MYSQL_OPT_RECONNECT option&quot;，也就是说，它实际上还依赖于MYSQL_OPT_RECONNECT这个配置，而这个配置默认（自5.0.3开始）是关闭的！<br/><br/>虽然想起来很愤怒很蛋疼，不过看到 libmysql/client.c: mysql_init() 里的注释就淡定了：<br/><div class="quote"><div class="quote-title">引用</div><div class="quote-content">By default we don&#039;t reconnect because it could silently corrupt data (after reconnection you potentially lose table locks, user variables, session variables (transactions but they are specifically dealt with in mysql_reconnect()).&nbsp; This is a change: &lt; 5.0.3 mysql-&gt;reconnect was set to 1 by default.&nbsp; </div></div><br/><br/>好吧，既然有问题，那就正视它。解决办法是调用 mysql_options ，将MYSQL_OPT_RECONNECT设置为1:<br/><div class="code">char value = 1;<br/>mysql_options(mysql, MYSQL_OPT_RECONNECT, &amp;value);</div><br/><br/>但是!! 在mysql 5.0.19 之前，mysql-&gt;reconnect = 0 这一句是放在 mysql_real_connect() 里面的！也就是说，如果你不能像处理其他选项一样，而是必须在mysql_real_connect()之前设置MYSQL_OPT_RECONNECT，坑爹啊！<br/><br/>好吧好吧，总之，关于坑的问题暂告一段落，结论就是，不管是哪个版本，如果你想要启用自动重连，最好都是在mysql_real_connect()之后，反正不会错。<br/><br/>然后这篇的重点来了（前面似乎太罗嗦了点）：MYSQL_OPT_RECONNECT的文档里头说了，这个选项是用来启用/禁用（当发现连接断开时的）自动重连，那么，MYSQL什么时候会发现链接断开呢？<br/><br/>这个问题可能太大了，不过不妨先去追一下，mysql_ping()做了啥。<br/><br/>下载源码 <a href="http://cdn.mysql.com/Downloads/MySQL-5.1/mysql-5.1.67.tar.gz" target="_blank">http://cdn.mysql.com/Downloads/MySQL-5.1/mysql-5.1.67.tar.gz</a> ，解压以后ctags -R，再vim -t mysql_ping ，马上就定位到了，似乎太简单了点：<div class="code">int STDCALL<br/>mysql_ping(MYSQL *mysql)<br/>&#123;<br/>&nbsp; int res; <br/>&nbsp; DBUG_ENTER(&quot;mysql_ping&quot;);<br/>&nbsp; res= simple_command(mysql,COM_PING,0,0,0);&nbsp; &nbsp; &nbsp; &nbsp; //试着向服务器发送一个ping包<br/>&nbsp; if (res == CR_SERVER_LOST &amp;&amp; mysql-&gt;reconnect)&nbsp; &nbsp; //如果server挂了，而mysql-&gt;reconnect为true<br/>&nbsp; &nbsp; res= simple_command(mysql,COM_PING,0,0,0);&nbsp; &nbsp; &nbsp; //再ping一次？？<br/>&nbsp; DBUG_RETURN(res);<br/>&#125;</div><br/><br/>好吧，看来关键在于这个simple_command了。ctrl+]，原来是这样：<div class="code">#define simple_command(mysql, command, arg, length, skip_check) &#92;<br/>&nbsp; (*(mysql)-&gt;methods-&gt;advanced_command)(mysql, command, 0, 0, arg, length, skip_check, NULL)</div><br/><br/>好吧，先去追一下MYSQL，里头有个 const struct st_mysql_methods *methods ，再追一下 st_mysql_methods ....<br/><div class="code">typedef struct st_mysql_methods<br/>&#123;<br/>&nbsp; my_bool (*read_query_result)(MYSQL *mysql);<br/>&nbsp; my_bool (*advanced_command)(MYSQL *mysql, enum enum_server_command command,<br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const unsigned char *header, unsigned long header_length,<br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; const unsigned char *arg, unsigned long arg_length,<br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; my_bool skip_check, MYSQL_STMT *stmt);<br/>&nbsp; ......</div><br/>坑爹啊！又是这种鸟代码！蛋疼的C语言！struct只有属性没有方法！没办法，只能暴力了：<br/><div class="quote"><div class="quote-title">引用</div><div class="quote-content">find -name &#039;*.c&#039; -exec /bin/grep &#039;&#123;&#125;&#039; -Hne &#039;mysql-&gt;methods *=&#039; &#039;;&#039;<br/>./libmysql_r/client.c:1907:&nbsp; mysql-&gt;methods= &amp;client_methods;<br/>./sql-common/client.c:1907:&nbsp; mysql-&gt;methods= &amp;client_methods;<br/>./libmysql/client.c:1907:&nbsp; mysql-&gt;methods= &amp;client_methods;<br/>./libmysqld/libmysqld.c:120:&nbsp; mysql-&gt;methods= &amp;embedded_methods;<br/>./sql/client.c:1907:&nbsp; mysql-&gt;methods= &amp;client_methods;</div></div><br/><br/>果断追到client_methods:<div class="code">static MYSQL_METHODS client_methods=<br/>&#123;<br/>&nbsp; cli_read_query_result,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;  /* read_query_result */<br/>&nbsp; cli_advanced_command,&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; /* advanced_command */<br/>&nbsp; ...</div><br/>也就是说simple_command最后调用了cli_advanced_command这个函数。前面的 simple_command(mysql,COM_PING,0,0,0) 相当于是调用了 cli_advanced_command(mysql, COM_PING, 0, 0, 0, 0, 0, NULL) 。<br/><br/>这个函数做了啥呢。。。其实也不复杂：<br/>1. 设置默认返回值为1 （意外出错goto时被返回）<br/>2. 设置sigpipe的handler（以便忽略它）<br/>3. 如果 mysql-&gt;net.vio == 0 ，那么调用mysql_reconnect重连，失败的话就返回1<br/>4. mysql没准备好，返回1<br/>5. 清除之前的信息（错误码、缓冲区、affected_rows）等等<br/>6. 调用net_write_command将命令发送给server，如果失败：<br/>&nbsp; &nbsp; 6.1 检查错误信息，如果是因为发送包太大，goto end<br/>&nbsp; &nbsp; 6.2 调用end_server(mysql)关闭连接<br/>&nbsp; &nbsp; 6.3 调用mysql_reconnect尝试重连，如果失败goto end<br/>&nbsp; &nbsp; 6.4 再次调用net_write_command将命令发送给server，失败则goto end<br/>7. 设置result = 0（发送成功）<br/>8. 如果参数中要求检查server的返回，则读取一个packet进行检查（失败的话就result=1）<br/>9. (end标签) <br/>10. 恢复sigpipe<br/>11. 返回result<br/><br/>可以看到，这里两次调用了mysql_reconnect，但都是有条件的：第一次是在mysql-&gt;net.vio == 0的情况下，第二次是net_write_command失败且不是因为包太大的情况。vio相关的代码看得一头雾水，实在找不出头绪，于是决定暴力一点：直接修改这个函数，加入一堆fprintf(stderr, ...)（具体加在哪里就不说了，反正使劲塞就是了），然后写了一个C代码：<div class="code">#include &lt;stdio.h&gt;<br/>#include &lt;stdlib.h&gt;<br/>#include &lt;mysql/mysql.h&gt;<br/><br/>void do_err(MYSQL *mysql) &#123;<br/>&nbsp; &nbsp; if (mysql_errno(mysql)) &#123;<br/>&nbsp; &nbsp; &nbsp; &nbsp; fprintf(stderr, &quot;%d:%s&#92;n&quot;, mysql_errno(mysql), mysql_error(mysql));<br/>&nbsp; &nbsp; &nbsp; &nbsp; exit(mysql_errno(mysql));<br/>&nbsp; &nbsp; &#125;<br/>&#125;<br/><br/>int main()<br/>&#123;<br/>&nbsp; &nbsp; MYSQL * mysql = mysql_init(NULL);<br/>&nbsp; &nbsp; do_err(mysql);<br/><br/>&nbsp; &nbsp; mysql_real_connect(mysql, &quot;127.0.0.1&quot;, &quot;root&quot;, &quot;123456&quot;, &quot;test&quot;, 3306, NULL, 0);<br/>&nbsp; &nbsp; do_err(mysql);<br/><br/>&nbsp; &nbsp; char value = 1;<br/>&nbsp; &nbsp; mysql_options(mysql, MYSQL_OPT_RECONNECT, &amp;value);<br/>&nbsp; &nbsp; <br/>&nbsp; &nbsp; char cmd&#91;1024&#93; = &quot;SELECT * FROM t&quot;;<br/>&nbsp; &nbsp; while (1) &#123;<br/>&nbsp; &nbsp; &nbsp; &nbsp; mysql_query(mysql, cmd);<br/>&nbsp; &nbsp; &nbsp; &nbsp; do_err(mysql);<br/><br/>&nbsp; &nbsp; &nbsp; &nbsp; MYSQL_RES *result = mysql_store_result(mysql);<br/><br/>&nbsp; &nbsp; &nbsp; &nbsp; MYSQL_ROW&nbsp; row;<br/>&nbsp; &nbsp; &nbsp; &nbsp; while ((row = mysql_fetch_row(result)) != NULL) &#123;<br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; int i, num_fields = mysql_num_fields(result);<br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; for (i = 0; i &lt; num_fields; i++) <br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf(&quot;%s&#92;t&quot;, row&#91;i&#93; ? row&#91;i&#93; : &quot;NULL&quot;); <br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //注意上一句是不是二进制安全的，因为row里头可能包含&#92;0，也可能末尾没有&#92;0<br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; printf(&quot;&#92;n&quot;);<br/>&nbsp; &nbsp; &nbsp; &nbsp; &#125;<br/><br/>&nbsp; &nbsp; &nbsp; &nbsp; mysql_free_result(result);<br/>&nbsp; &nbsp; &nbsp; &nbsp; printf(&quot;press enter...&quot;); getchar();<br/>&nbsp; &nbsp; &#125;<br/>&nbsp; &nbsp; mysql_close(mysql);<br/>&nbsp; &nbsp; return 0;<br/>&#125;</div><br/><br/>运行输出：<div class="quote"><div class="quote-title">引用</div><div class="quote-content">inside mysql_real_query<br/>mysql-&gt;net.vio = 0x90e760<br/>mysql-&gt;status = 0<br/>net write_command<br/>after send_query<br/>---<br/>1<br/>2<br/>press enter...//按回车之前先重启一下mysql server，下面这几句按照函数调用层次进行手动缩进了……<br/>inside mysql_real_query<br/>&nbsp; &nbsp; mysql-&gt;net.vio = 0x90e760 //进入cli_advanced_command<br/>&nbsp; &nbsp; mysql-&gt;status = 0<br/>&nbsp; &nbsp; net_write_command<br/>&nbsp; &nbsp; end_server //说明net_write_command失败了<br/>&nbsp; &nbsp; &nbsp; &nbsp; inside mysql_reconnect //它会调用mysql_real_query<br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; inside mysql_real_query<br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mysql-&gt;net.vio = 0x919990 //于是又回到了cli_advanced_command<br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; mysql-&gt;status = 0<br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; net_write_command //这次成功了<br/>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; after send_query&nbsp; //这句我是写在mysql_real_query里面的<br/>&nbsp; &nbsp; &nbsp; &nbsp; reconnect succeded<br/>&nbsp; &nbsp; after reconnect: mysql-&gt;status = 0<br/>after send_query //所以又来一次。。</div></div><br/><br/>根据fprintf的输出，发现在正常情况下，mysql-&gt;net.vio这个指针并不等于0，所以第一个mysql_reconnect不会被调用。而net_write_command也是正确执行，第二个reconnect也没被调用。<br/><br/>而在执行完一个query，然后重启mysql server再执行query (mysql_query =&gt; mysql_real_query =&gt; mysql_send_query =&gt; cli_advanced_command)，就会发现，mysql-&gt;net.vio仍然不等于0，但是net_write_command失败了，于是先调用了end_server()（这里面会将mysql-&gt;net.vio设置为0，不过不影响后面的流程...），然后调用了第二个reconnect，这个reconnect会调用mysql_init()以及mysql_real_query()执行一些初始化的命令，于是又回到cli_advanced_command，再一步一步回溯。。。<br/><br/>综上可知，如果设置了MYSQL_OPT_RECONNECT()，那么mysql_query()是可以完成自动重连的。实际上，由于cli_advanced_command会在必要情况下调用mysql_reconnect（实际上这个函数也只在这里被调用），因此，所有用到了cli_read_query_result的地方（或者simple_command），也都可以完成自动重连。<br/><br/>完结。<br/><br/>//混蛋，这篇纯粹是为了凑一月至少一篇这个目标啊！]]></description>
    <pubDate>Sun, 30 Dec 2012 18:56:48 +0000</pubDate> 
    <category><![CDATA[数据库]]></category>
    <author>felix021 &lt;i[#at]felix021.com&gt;</author>
    <guid>https://www.felix021.com/blog/read.php?2102</guid> 
</item>
<item>
    <title><![CDATA[[评论] 蛋疼的mysql_ping()以及MYSQL_OPT_RECONNECT]]></title> 
    <link>https://www.felix021.com/blog/read.php?2102#blogcomment2492</link>
    <description><![CDATA[Mysql 的自动断开连接是个后台开发的老问题了...解决的方法有大有小，小的方法直接定时写一点冗余就行了，中等的方法就是捕获异常然后自己手动重连，另外还有修改配置。。不过修改配置以后会有更蛋疼的，就是防火墙，那货断开连接的时间更短。]]></description>
    <pubDate>Mon, 31 Dec 2012 03:52:06 +0000</pubDate> 
    <category><![CDATA[评论]]></category>
    <author>ctqmumu &lt;user@domain.com&gt;</author>
    <guid>https://www.felix021.com/blog/read.php?2102#blogcomment2492</guid> 
</item>
<item>
    <title><![CDATA[[评论] 蛋疼的mysql_ping()以及MYSQL_OPT_RECONNECT]]></title> 
    <link>https://www.felix021.com/blog/read.php?2102#blogcomment2599</link>
    <description><![CDATA[lz调试是怎么调试的呢？]]></description>
    <pubDate>Tue, 03 May 2016 07:58:55 +0000</pubDate> 
    <category><![CDATA[评论]]></category>
    <author>foggy32 &lt;user@domain.com&gt;</author>
    <guid>https://www.felix021.com/blog/read.php?2102#blogcomment2599</guid> 
</item>
<item>
    <title><![CDATA[[评论] 蛋疼的mysql_ping()以及MYSQL_OPT_RECONNECT]]></title> 
    <link>https://www.felix021.com/blog/read.php?2102#blogcomment2720</link>
    <description><![CDATA[搞MySql终有一天会遇到的问题就是这个]]></description>
    <pubDate>Mon, 02 Nov 2020 07:36:33 +0000</pubDate> 
    <category><![CDATA[评论]]></category>
    <author>Smalldy &lt;user@domain.com&gt;</author>
    <guid>https://www.felix021.com/blog/read.php?2102#blogcomment2720</guid> 
</item>

</channel>
</rss>