当前位置: 首页 > 科技观察

如何使MySQL连接保持活动状态?

时间:2023-03-17 15:17:43 科技观察

多年前开发了一个异步发送订单短信和邮件通知的守护程序。程序每次启动都会创建一个数据库连接,后续的读写数据库操作总是会重用这个连接。某日,用户反映下单后收不到通知。我们登录到服务器,看到程序还在运行。经排查确认,问题发生当天,距离用户最后一次下单已经过去8个多小时,MySQL服务器自动断开连接。这个问题的解决方法比较简单。只要程序定时向MySQL发送请求表明它还活着,MySQL就不会触发断开连接操作。这是数据库连接保活的应用场景。今天我们就来说说数据库连接保活的原理和方法。本文内容基于MySQL8.0.29源码。正文1.概述MySQL系统变量wait_timeout,默认值为28800秒(8小时),用于控制客户端多长时间不向MySQL发送请求,MySQL会自动断开连接。如果我们的业务系统不是那么空闲,我们可以每隔三五次向MySQL发送一些请求,数据库连接就会一直处于活动状态,就不需要keepalive了。在某些业务系统中,在非高峰期可能会长时间没有读写请求。一旦间隔超过wait_timeout,数据库连接就会断开,连接保活自然是不可避免的。下面说一下这两种连接保活方式以及它们之间的区别。在此之前,我们先看看wait_timeout是如何控制超时逻辑的。2.wait_timeout超时逻辑客户端与MySQL建立连接后,每次MySQL开始等待客户端发送数据前,MySQL都会根据系统变量wait_timeout的值设置最长等待时间:booldo_command(THD*thd){......net=thd->get_protocol_classic()->get_net();my_net_set_read_timeout(net,thd->variables.net_wait_timeout);...}上面代码中的net_wait_timeout就是系统变量wait_timeout的化身。设置好最长等待时间后,接下来就是静静等待了。执行等待操作的方法是vio_io_wait():intvio_socket_io_wait(Vio*vio,enumenum_vio_io_eventevent){inttimeout,ret;...超时=vio->read_timeout;...switch(vio_io_wait(vio,event,timeout)){...case0:/*等待超时。*/ret=-1;休息;...}returnret;}如果达到最大等待时间,客户端还没有发送数据,vio_io_wait()将返回0表示超时。然后,程序会沿着调用栈返回net_read_raw_loop()方法,并设置返回给客户端的错误码ER_CLIENT_INTERACTION_TIMEOUT(4031)。相应的错误消息是:客户端因不活动而被服务器断开连接。请参阅wait_timeout和interactive_timeout以配置此行为。准备好返回给客户端的错误码和错误信息后,会进行一系列与断开连接相关的操作,最后将错误码和错误信息发送给客户端。如果我们使用MySQL自带的交互式客户端mysql,发生超时后,下次执行SQL语句时,会看到这样的错误:mysql>SETwait_timeout=10;10秒后......mysql>SELECT*FROMt1LIMIT1;ERROR4031(HY000):客户端因不活动而被服务器断开连接。请参阅wait_timeout和interactive_timeout以配置此行为。无连接。Tryingtoreconnect...onMySQLservice大致介绍了终端主动断开连接的过程之后,我们来看一下两种保持连接的方式。3、从客户端的角度,ping命令用于判断MySQL服务器是否还活着。换个角度,从MySQL服务器的角度来看,客户端向它发送ping命令,说明客户端连接还活着,它不会关闭客户端连接。因此,ping命令不仅可以用于数据库连接检测,还可以用于保活。MySQL不提供ping语句。如果要测试发送ping命令,可以使用mysqladmin:#发送ping命令mysqladmin-h127.0.0.1-P3307-urootping#收到的结果(说明MySQL服务器还活着)mysqldisalive在数据库连接池或者业务系统中,程序提供的API也可以方便的向MySQL服务器发送ping命令。在非高峰期,客户端定时向MySQL服务器发送ping命令,保持连接存活。4.Select另一种保持连接的方式是执行SQL语句,一般是select语句,可以有多种模式:SELECT1;选择版本();SELECT@@version;...执行select语句保活,和正常执行业务SQL没有区别,这里就不展开了。5、两种保活方式的比较既然ping和select都可以实现数据库连接保活,那它们有什么区别呢?在MySQL源码的实现上,有两点不同:不同点1:ping是一个命令,我们只能通过MySQL提供的API,或者mysqladmin等工具向MySQL服务器发送ping命令。select是一个SQL语句,可以通过MySQLAPI或者mysql交互式客户端来执行。区别二:ping的执行过程比select更短,效率更高。通过对比两者的调用栈,我们可以更直观的看出这一点。这两个方法都会响应客户端的请求,后面给出的调用栈中省略了这部分。ping命令的主要调用栈如下:|>pfs_spawn_thread(void*)||>handle_connection(void*)|||>do_command(THD*)||||>dispatch_command(THD*,COM_DATAconst*,enum_server_command)pingcommand调用栈非常简单,甚至不需要词法分析和语法分析。进入dispatch_command()方法后,如果判断为ping命令,则直接向客户端返回OK状态,整个过程结束:booldispatch_command(THD*thd,constCOM_DATA*com_data,enumenum_server_commandcommand){......switch(command){......caseCOM_PING:thd->status_var.com_other++;我的好(thd);//告诉客户我们还活着break;......}......}接下来是select的调用栈,以最简单的SELECT1为例,主要的调用栈如下:SELECT1的调用栈比较长,maincallstack都列出来,方便大家更直观的了解SELECT1的执行过程。|>pfs_spawn_thread(void*)||>handle_connection(void*)|||>do_command(THD*)||||>dispatch_command(THD*,COM_DATAconst*,enum_server_command)|||||>dispatch_sql_command(THD*,Parser_state*)||||||>parse_sql(THD*,Parser_state*,Object_creation_ctx*)||||||>mysql_execute_command(THD*,布尔)|||||||>Sql_cmd_dml::执行(THD*)||||||||>Sql_cmd_dml::准备(THD*)|||||||||>open_tables_for_query(THD*,TABLE_LIST*,unsignedint)||||||||||>open_tables(...)|||||||||||>锁定表名(...)||||||||||>open_secondary_engine_tables(THD*,unsignedint)|||||||||>Sql_cmd_select::prepare_inner(THD*)||||||||||>Query_block::prepare(THD*,mem_root_deque<项目*>*)|||||||||||>Query_block::setup_tables(THD*,TABLE_LIST*,bool)|||||||||||>setup_fields(...)|||||||||||>曲ery_block::setup_conds(THD*)|||||||||||>Query_block::resolve_limits(THD*)|||||||||||>Query_block::apply_local_transforms(THD*,bool)||||||||||||>Query_block::simplify_joins(...)||||||||>lock_tables(THD*,TABLE_LIST*,unsignedint,unsignedint)||||||||>Sql_cmd_dml::execute_inner(THD*)|||||||||>Query_expression::optimize(THD*,TABLE*,bool,bool)||||||||||>Query_block::optimize(THD*,bool)|||||||||||>加入::优化(布尔)||||||||||||>加入::make_tmp_tables_info()||||||||||||>count_field_types(...)||||||||||||>JOIN::create_access_paths()|||||||||||||>加入::create_root_access_path_for_join()|||||||||||||>JOIN::attach_access_paths_for_having_and_limit(AccessPath*)|||||||||||||>JOIN::attach_access_path_for_delete(访问路径*)|||||||||>optimize_secondary_engine(THD*)|||||||||>查询表达式::执行(THD*)||||||||||>查询表达式::ExecuteIteratorQuery(THD*)|||||||||||>Query_result_send::send_result_set_metadata(...)||||||||||>查询表达式::ExecuteIteratorQuery(THD*)|||||||||||>FakeSingleRowIterator::Read()|||||||||||>查询结果发送::发送_eof(THD*)|||||||>trans_commit_stmt(THD*,布尔)||||||||>MYSQL_BIN_LOG::提交(THD*,布尔)|||||||||>ha_commit_low(THD*,bool,bool)|||||>log_slow_statement(THD*,System_status_var*)从上面的调用栈可以看出,SELECT1虽然不需要从表中查询数据,但是词法分析、语法分析、查询准备、查询等过程都没有优化,查询执行,事务提交,慢SQL记录都被抛在脑后,虽然很多方法不需要复杂的执行操作,但是各种if...else判断是必不可少的,执行SELECT1是最简单的select语句形式,如果使用其他select语句保活,调用栈只会更长。通过上面的ping命令和SELECT1的调用栈的对比,相信大家对这两种保活方式的执行效率有了一个直观的认识。6.总结写这篇文章的初衷是比较ping和select两种数据库连接保活方式的执行效率。经过前面的介绍,我们可以得出结论,ping命令的执行效率要高于select语句。对于追求极致性能的应用,使用ping命令保持数据库连接是更好的方法。本文转载自微信公众号“一树一溪”,可通过以下二维码关注。转载本文请联系艺书艺熙公众号。