在生产环境中,DB服务器经常会被并发的慢查询压挂,因此事前进行sql审核避免烂SQL很重要。万一不小心慢sql还是跑到线上,并且并发还不小,这是dba肯定会收到告警。dba上线处理第一时间是定位并kill慢查询,避免慢查询其他正常的事务。本文主要围绕kill展开,并附带介绍几种相关的timeout参数和实现机制。
kill指令
kill的语法如下:kill [connection|query] thread_id,通过kill命令可以kill一个查询或kill一个连接。一般而言,每个用户只能查看和终止自己用户的连接和查询,若用户具有process权限,则可以查看所有线程,具有super权限,可以查看和终止所有用户的连接和查询。假设有两个会话A和B,A的thread_id为xxx,A会话执行查询,B会话分别执行kill query xxx和kill xxx,会话A会分别收到1317和2013错误。
mysql> select count(*) from test_slow where 1=1;
ERROR 1317 (70100): Query execution was interrupted
mysql> select count(*) from test_slow where 1=1;
ERROR 2013 (HY000): Lost connection to MySQL server during query
通过mysql客户端进行操作,除了发送kill指令,还可以通过ctrl+c来终结自己。这里的原理很简单,mysql客户端里面有一个信号捕获线程,监听到SIGINT后,创建一个连接,然后发送kill命令到服务端,将自己终结。通过gdb调试时,为了避免gdb对信号的影响,通过命令设置即可,命令如下:
handle SIGINT nostop print pass
kill实现原理
mysqld收到kill命令后,会将对应的线程实例thd设置为kill状态,若为kill_connection,会主动关闭socket。无论是kill_connection还是kill query,查询可能不能立即结束,因为查询可能正在运行过程中。因此,在mysqld的代码中的关键节点都会调用trx_is_interrupted函数判断自身的状态是否为killed,若是,则报错返回,终止执行。到底mysqld在哪些地方会进行检查,这里列出官方的文档,说明如下:
• In SELECT, ORDER BY and GROUP BY loops, the flag is checked after reading a block of rows. If the kill flag is set, the statement is aborted.
• During ALTER TABLE,
the kill flag is checked before each block of rows are read from the original
table. If the kill flag was set, the statement is aborted and the temporary
table is deleted.
• During UPDATE
or DELETE operations, the kill flag is checked after each block read and after
each updated or deleted row. If the kill flag is set, the statement is aborted.
Note that if you are not using transactions, the changes are not rolled
back.
如果空闲连接被kill,那么这个连接何时被关闭呢?执行kill connection xxx时,会首先将thd的状态打标,然后会调用对应thd的vio_cancel来关闭socket连接,进而产生一个网路事件,监听线程捕获到对应连接的event,交给worker线程处理,worker线程检查thd的状态为killed,则结束连接,将其从global_thread_list中摘除。另外,如果被kill的连接正在等一个锁(行锁,表锁),则调用引擎层的kill接口,对于innodb是innobase_kill_connection,将请求等待的锁释放,并将其唤醒,被唤醒的线程发现自己的状态我killed,则退出。这里有点类似于死锁的处理,加锁时进行死锁检查,如果发现导致循环依赖,则会根据权重(undo数+上锁记录等)来找一个事务进行回滚,如果需要回滚的是当前事务,则当前事务释放锁,并回滚;如果需要回滚的是其它事务,释放持有的锁,设置其错误码为deadlock,并将其唤醒,被唤醒的线程发现自己错误码,则出错返回。
kill自动化
如果每次查询都需要dba人工去触发,那么遇到问题时,可能处理没那么及时,如果能自动化的kill慢查询则能将影响降到最低。在jdbc中通过接口Statement.setQueryTimeout(int) 即可实现。这个原理其实与mysql客户端通过ctrl+c
kill查询类似,只不过这种方式是通过超时实现。设置一个定时器,有一个线程定时不停地判断定时器事件有没有触发,达到触发点后,再发送kill查询到服务端,实现kill查询的目的。这种方式虽然能自动化,但这个接口调用控制权还是在开发手中,而且一般情况下,开发一定都认为自己的查询时没有问题的,肯定不会超时,所以这个接口是否被调用无法控制。那么遇到问题后,DB还是一样会挂,还是需要dba人工处理。因此在alimysql内核也做了一套类似的功能,通过参数max_statement_time可以控制当前会话和所有会话的超时时间。只要超时,mysql内部会调用接口awake接口将对应的thd状态设置为killed,达到kill query或kill connection的目的。
几种超时参数
除了我们上面提到的statement
timeout,jdbc中常见的超时参数还有connectTimeout和socket timeout。
connectTimeout:和数据库服务器建立socket连接时的超时,单位:毫秒。
socket timeout: socket操作(读写)超时
具体而言,jdbc通过设置socket的属性来实现超时目的,不同的JDBC驱动其配置方式会有所不同。 socket连接时的timeout:通过Socket.connect(SocketAddress
endpoint, int timeout)设置;socket读写时的timeout:通过Socket.setSoTimeout(int timeout)设置,采用阻塞IO模型,如果不设置socket
timeout或connect timeout,应用多数情况下是无法发现网络错误的。因此,当网络错误发生后,在连接重新连接成功或成功接收到数据之前,应用会无限制地等下去。当然,操作系统层面,也会有socket timeout设置。相关的配置参数如下:
tcp_keepalive_time:TCP连接在idle指定时间后,内核才发起probe
tcp_keepalive_probes:TCP发送keepalive探测以确定该连接已经断开的次数
tcp_keepalive_intvl:探测消息发送的频率
因此,探测(tcp_keepalive_time + tcp_keepalive_intvl * tcp_keepalive_probes)时间后,若还不能连上,则断开连接。
这些参数可以修改:
/proc/sys/net/ipv4/tcp_keepalive_time
/proc/sys/net/ipv4/tcp_keepalive_intvl
/proc/sys/net/ipv4/tcp_keepalive_probes
参考文献
http://astar.baidu.com/forum/forum.php?mod=viewthread&tid=363
http://dev.mysql.com/doc/refman/5.1/en/kill.html