i007.cc

i007.cc

优先队列-降维打击

记录一次MySQL连接断开的问题排查(原创)

首先是客户那边在腾讯云上的服务报告了“mysql_errno:2013 Lost connection to MySQL during query”的错误。这是在程序运行到了第三四天的时候出现的,考虑到前面几天一直运行得好好的,并没有报错,所以我觉得可能是长时间没有操作数据库,然后连接被断开了,但是我的程序是有设置重连选项的:

char opt_val = 1;
mysql_options(mysql_, MYSQL_OPT_RECONNECT, &opt_val);

 

于是看了下mysql关于重连的一些文档:

首先关于超时连接断开,官方给出的解决方案是这样的

The solution to this is to either do a mysql_ping() on the connection if there has been a long time since the last query (this is what Connector/ODBC does) or set wait_timeout on the mysqld server so high that it in practice never times out.

 

那就按照官方给出的方法定时ping()一下好了,写了一个更新定时器的函数,在程序启动的时候执行一下,在有新的查询操作也会执行一下

void DbConnection::UpdatePingTimer() {
    if (timer_ping_ != nullptr) {
        timer_ping_->Cancel();
        timer_ping_ = nullptr;
    }

    timer_ping_ = cron_timer_mgr_.AddDelayTimer(
        3600 * 1000,
        [this]() {
            auto ping_ret = mysql_ping(mysql_);

            std::ostringstream oss;
            oss << std::this_thread::get_id();
            std::string thread_id = oss.str();
            LOG_WARN("timer mysql_ping:{}, this_thread_id:{}", ping_ret, thread_id);

            if (ping_ret != 0) {
                pair_error_.first = mysql_errno(mysql_);
                pair_error_.second = mysql_error(mysql_);
                LOG_ERROR("mysql_errno:{}, mysql_error:{}", pair_error_.first, pair_error_.second);
            }
        },
        cron_timer::TimerMgr::RUN_FOREVER);
}

 

这样也不会因为mysql_ping()的操作影响到查询结果。

 

昨天想重现一下bug,然后确认下是否完美解决

于是找了一台mariadb数据库(手边没有mysql),登陆上去查询了下参数:

MariaDB [(none)]> show variables like '%timeout%';
+---------------------------------------+----------+
| Variable_name                         | Value    |
+---------------------------------------+----------+
| connect_timeout                       | 10       |
| deadlock_timeout_long                 | 50000000 |
| deadlock_timeout_short                | 10000    |
| delayed_insert_timeout                | 300      |
| idle_readonly_transaction_timeout     | 0        |
| idle_transaction_timeout              | 0        |
| idle_write_transaction_timeout        | 0        |
| innodb_flush_log_at_timeout           | 1        |
| innodb_lock_wait_timeout              | 50       |
| innodb_rollback_on_timeout            | OFF      |
| interactive_timeout                   | 28800    |
| lock_wait_timeout                     | 86400    |
| net_read_timeout                      | 30       |
| net_write_timeout                     | 60       |
| rpl_semi_sync_master_timeout          | 10000    |
| rpl_semi_sync_slave_kill_conn_timeout | 5        |
| slave_net_timeout                     | 60       |
| thread_pool_idle_timeout              | 60       |
| wait_timeout                          | 28800    |
+---------------------------------------+----------+
19 rows in set (0.002 sec)

 

其中有两个参数interactive_timeout和wait_timeout,这两个参数用来控制超时时间的,默认是8小时(28800秒)

动手修改

cd /etc/mysql/mariadb.conf.d
vim 50-server.cnf

增加下面两项,缺省没有的
wait_timeout=60
interactive_timeout=60

:wq

重启mysql服务
sudo systemctl restart mysqld

 

然后确认下已经生效:

MariaDB [mysql]> show variables like '%timeout%';
+---------------------------------------+----------+
| Variable_name                         | Value    |
+---------------------------------------+----------+
| connect_timeout                       | 10       |
| deadlock_timeout_long                 | 50000000 |
| deadlock_timeout_short                | 10000    |
| delayed_insert_timeout                | 300      |
| idle_readonly_transaction_timeout     | 0        |
| idle_transaction_timeout              | 0        |
| idle_write_transaction_timeout        | 0        |
| innodb_flush_log_at_timeout           | 1        |
| innodb_lock_wait_timeout              | 50       |
| innodb_rollback_on_timeout            | OFF      |
| interactive_timeout                   | 60       |
| lock_wait_timeout                     | 86400    |
| net_read_timeout                      | 30       |
| net_write_timeout                     | 60       |
| rpl_semi_sync_master_timeout          | 10000    |
| rpl_semi_sync_slave_kill_conn_timeout | 5        |
| slave_net_timeout                     | 60       |
| thread_pool_idle_timeout              | 60       |
| wait_timeout                          | 60       |
+---------------------------------------+----------+
19 rows in set (0.002 sec)

 

然后开始部署应用上去验证:

首先来一个没有设置重连选项的,90秒钟之后执行mysql_ping(),果然出现了连接断开的情况

[2021-06-11 10:44:01.987][warning] [mysql_db.cpp:95 operator()] timer mysql_ping:-1, this_thread_id:139682938480384
[2021-06-11 10:44:01.987][error] [mysql_db.cpp:100 operator()] mysql_errno:2006, mysql_error:MySQL server has gone away

不过这次却是2006的错误,并不是2013。立刻想到不是超时断开的问题,联系腾讯云的运维,他们很爽快的接了这个bug,并说最近几天这不是个案,他需要一点时间调下参数。貌似这问题解决了。不过我还想看下mysql的重连逻辑,于是继续。

 

如果不设置重连选项,在60秒内不断的调用mysql_ping()

char opt_val = 1; 
mysql_options(mysql_, MYSQL_OPT_RECONNECT, &opt_val);

实测能够保持连接,当然在90秒钟的时候调用就会发现连接断了。

 

如果设置了重连选项,mysql_ping()不管是60秒以内执行,还是90秒意外执行都是没问题的(自动重连功能已经生效)

实测不调用mysql_ping(),调用mysql_real_query()也能成功重连上。

所以,如果你想获得重连功能,设置好重连选项就好了(可以不调用mysql_ping()),至此问题完美解决。

发表回复