记录一次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()),至此问题完美解决。
