mysql_ping()以及MYSQL_OPT_RECONNECT MySQL server has gone away”錯誤

來源:互聯網
上載者:User

標籤:blog   http   io   ar   os   for   sp   strong   div   

來源:http://www.felix021.com/blog/read.php?2102
昨天@Zind同學找到我之前的一篇blog(已經修改),裡面提到了mysql_ping和MYSQL_OPT_RECONNECT的一些事情。

之所以寫那篇blog,是因為去年寫的一些代碼遇到了“2006:MySQL server has gone away”錯誤。這個問題是因為wait_timeout這個參數的預設值是28800,也就是說,如果一個串連連續8個小時沒有任何請求,那麼Server端就會把它斷開。在測試環境中一個晚上沒有請求很正常……於是第二天早上來的時候就發現這個錯誤了。

其實我有考慮這個問題的,真的……因為我知道php裡面有個函數叫做mysql_ping(),PHP手冊上說:“mysql_ping() 檢查到伺服器的串連是否正常。如果斷開,則自動嘗試串連。本函數可用於空閑很久的指令碼來檢查伺服器是否關閉了串連,如果有必要則重新串連上。”

回想起來,以前真是很傻很天真。根據MySQL官方C API裡mysql_ping()的文檔:"Checks whether the connection to the server is working. If the connection has gone down and auto-reconnect is enabled an attempt to reconnect is made. ... Auto-reconnect is disabled by default. To enable it, call mysql_options() with the MYSQL_OPT_RECONNECT option",也就是說,它實際上還依賴於MYSQL_OPT_RECONNECT這個配置,而這個配置預設(自5.0.3開始)是關閉的!

雖然想起來很憤怒很蛋疼,不過看到 libmysql/client.c: mysql_init() 裡的注釋就淡定了:
引用By default we don‘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()).  This is a change: < 5.0.3 mysql->reconnect was set to 1 by default. 

好吧,既然有問題,那就正視它。解決辦法是調用 mysql_options ,將MYSQL_OPT_RECONNECT設定為1:
char value = 1;
mysql_options(mysql, MYSQL_OPT_RECONNECT, &value);

但是!! 在mysql 5.0.19 之前,mysql->reconnect = 0 這一句是放在 mysql_real_connect()裡面的!也就是說,如果你不能像處理其他選項一樣,而是必須在mysql_real_connect()之前設定MYSQL_OPT_RECONNECT,坑爹啊!

好吧好吧,總之,關於坑的問題暫告一段落,結論就是,不管是哪個版本,如果你想要啟用自動重連,最好都是在mysql_real_connect()之後,反正不會錯。

然後這篇的重點來了(前面似乎太羅嗦了點):MYSQL_OPT_RECONNECT的文檔裡頭說了,這個選項是用來啟用/禁用(當發現串連斷開時的)自動重連,那麼,MYSQL什麼時候會發現連結斷開呢?

這個問題可能太大了,不過不妨先去追一下,mysql_ping()做了啥。

下載源碼 http://cdn.mysql.com/Downloads/MySQL-5.1/mysql-5.1.67.tar.gz ,解壓以後ctags -R,再vim -t mysql_ping ,馬上就定位到了,似乎太簡單了點:int STDCALL
mysql_ping(MYSQL *mysql)
{
  int res;
  DBUG_ENTER("mysql_ping");
  res= simple_command(mysql,COM_PING,0,0,0);        //試著向伺服器發送一個ping包
  if (res == CR_SERVER_LOST && mysql->reconnect)    //如果server掛了,而mysql->reconnect為true
    res= simple_command(mysql,COM_PING,0,0,0);      //再ping一次??
  DBUG_RETURN(res);
}

好吧,看來關鍵在於這個simple_command了。ctrl+],原來是這樣:
#define simple_command(mysql, command, arg, length, skip_check) \
  (*(mysql)->methods->advanced_command)(mysql, command, 0, 0, arg, length, skip_check,NULL)

好吧,先去追一下MYSQL,裡頭有個 const struct st_mysql_methods *methods ,再追一下 st_mysql_methods ....
typedef struct st_mysql_methods
{
  my_bool (*read_query_result)(MYSQL *mysql);
  my_bool (*advanced_command)(MYSQL *mysql, enum enum_server_command command,
                  const unsigned char *header, unsigned long header_length,
                  const unsigned char *arg, unsigned long arg_length,
                  my_bool skip_check, MYSQL_STMT *stmt);
  ......
坑爹啊!又是這種鳥代碼!蛋疼的C語言!struct只有屬性沒有方法!沒辦法,只能暴力了:
引用find -name ‘*.c‘ -exec /bin/grep ‘{}‘ -Hne ‘mysql->methods *=‘ ‘;‘
./libmysql_r/client.c:1907:  mysql->methods= &client_methods;
./sql-common/client.c:1907:  mysql->methods= &client_methods;
./libmysql/client.c:1907:  mysql->methods= &client_methods;
./libmysqld/libmysqld.c:120:  mysql->methods= &embedded_methods;
./sql/client.c:1907:  mysql->methods= &client_methods;

果斷追到client_methods:static MYSQL_METHODS client_methods=
{
  cli_read_query_result,                      /* read_query_result */
  cli_advanced_command,                        /* advanced_command */
  ...

也就是說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) 。

這個函數做了啥呢。。。其實也不複雜:
1. 設定預設返回值為1 (意外出錯goto時被返回)
2. 設定sigpipe的handler(以便忽略它)
3. 如果 mysql->net.vio == 0 ,那麼調用mysql_reconnect重連,失敗的話就返回1
4. mysql沒準備好,返回1
5. 清除之前的資訊(錯誤碼、緩衝區、affected_rows)等等
6. 調用net_write_command將命令發送給server,如果失敗:
    6.1 檢查錯誤資訊,如果是因為發送包太大,goto end
    6.2 調用end_server(mysql)關閉串連
    6.3 調用mysql_reconnect嘗試重連,如果失敗goto end
    6.4 再次調用net_write_command將命令發送給server,失敗則goto end
7. 設定result = 0(發送成功)
8. 如果參數中要求檢查server的返回,則讀取一個packet進行檢查(失敗的話就result=1)
9. (end標籤)
10. 恢複sigpipe
11. 返回result

可以看到,這裡兩次調用了mysql_reconnect,但都是有條件的:第一次是在mysql->net.vio == 0的情況下,第二次是net_write_command失敗且不是因為包太大的情況。vio相關的代碼看得一頭霧水,實在找不出頭緒,於是決定暴力一點:直接修改這個函數,加入一堆fprintf(stderr, ...)(具體加在哪裡就不說了,反正使勁塞就是了),然後寫了一個C代碼:#include <stdio.h>
#include <stdlib.h>
#include <mysql/mysql.h>

void do_err(MYSQL *mysql) {
    if (mysql_errno(mysql)) {
        fprintf(stderr, "%d:%s\n", mysql_errno(mysql), mysql_error(mysql));
        exit(mysql_errno(mysql));
    }
}

int main()
{
    MYSQL * mysql = mysql_init(NULL);
    do_err(mysql);

    mysql_real_connect(mysql, "127.0.0.1", "root", "123456", "test", 3306, NULL, 0);
    do_err(mysql);

    char value = 1;
    mysql_options(mysql, MYSQL_OPT_RECONNECT, &value);
   
    char cmd[1024] = "SELECT * FROM t";
    while (1) {
        mysql_query(mysql, cmd);
        do_err(mysql);

        MYSQL_RES *result = mysql_store_result(mysql);

        MYSQL_ROW  row;
        while ((row = mysql_fetch_row(result)) != NULL) {
            int i, num_fields = mysql_num_fields(result);
            for (i = 0; i < num_fields; i++)
                printf("%s\t", row[i] ? row[i] : "NULL");
            //注意上一句是不是二進位安全的,因為row裡頭可能包含\0,也可能末尾沒有\0
            printf("\n");
        }

        mysql_free_result(result);
        printf("press enter..."); getchar();
    }
    mysql_close(mysql);
    return 0;
}

運行輸出:引用inside mysql_real_query
mysql->net.vio = 0x90e760
mysql->status = 0
net write_command
after send_query
---
1
2
press enter...//按斷行符號之前先重啟一下mysql server,下面這幾句按照函數調用層次進行手動縮排了……
inside mysql_real_query
    mysql->net.vio = 0x90e760 //進入cli_advanced_command
    mysql->status = 0
    net_write_command
    end_server //說明net_write_command失敗了
        inside mysql_reconnect //它會調用mysql_real_query
            inside mysql_real_query
                mysql->net.vio = 0x919990 //於是又回到了cli_advanced_command
                mysql->status = 0
                net_write_command //這次成功了
            after send_query  //這句我是寫在mysql_real_query裡面的
        reconnect succeded
    after reconnect: mysql->status = 0
after send_query //所以又來一次。。

根據fprintf的輸出,發現在正常情況下,mysql->net.vio這個指標並不等於0,所以第一個mysql_reconnect不會被調用。而net_write_command也是正確執行,第二個reconnect也沒被調用。

而在執行完一個query,然後重啟mysql server再執行query (mysql_query => mysql_real_query => mysql_send_query => cli_advanced_command),就會發現,mysql->net.vio仍然不等於0,但是net_write_command失敗了,於是先調用了end_server()(這裡面會將mysql->net.vio設定為0,不過不影響後面的流程...),然後調用了第二個reconnect,這個reconnect會調用mysql_init()以及mysql_real_query()執行一些初始化的命令,於是又回到cli_advanced_command,再一步一步回溯。。。

綜上可知,如果設定了MYSQL_OPT_RECONNECT(),那麼mysql_query()是可以完成自動重連的。實際上,由於cli_advanced_command會在必要情況下調用mysql_reconnect(實際上這個函數也只在這裡被調用),因此,所有用到了cli_read_query_result的地方(或者simple_command),也都可以完成自動重連。

完結。

//混蛋,這篇純粹是為了湊一月至少一篇這個目標啊!

--


轉轉 http://blog.chinaunix.net/uid-22957904-id-3594136.html

轉載請註明出自 http://www.felix021.com/blog/read.php?2102 ,如是轉載文則註明原出處,謝謝:)
Google Reader 訂閱 點擊這裡,RSS地址: http://www.felix021.com/blog/feed.php

mysql_ping()以及MYSQL_OPT_RECONNECT MySQL server has gone away”錯誤

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.