首先,看看线上的一个高频需求,即杀掉执行时间超过30秒的慢查询。具体命令如下。
pt-kill h=192.168.244.10,P=3306,u=pt_user,p=pt_pass --busy-time 30 --interval 10 --print –kill –match-info "(?i-xsm:select)"
命令行中的 --busy-time定义了慢查询的阈值,--interval指的是检测时间间隔,这里pt-kill会每隔10秒执行一次SHOW FULL PROCESSLIST操作,看看是否有执行时间超过30秒的查询。如果有,则执行KILL操作(由 --kill参数决定),并将执行的KILL操作及被杀掉的SQL语句打印出来(--print)。
注意,--busy-time针对的是Command列为Query的操作,而SHOW PROCESSLIST中Command列为Query的操作不仅仅包括SELECT,同样也包括DELECT、INSERT、UPDATE和ALTER操作。所以为了保证杀掉的一定是SELECT操作,这里使用了--match-info进行过滤。--match-info匹配的是SHOW PROCESSLIST中Info列的内容。?i-xsm:^select是正则表达式,匹配以select开头的操作,不区分大小写。
看看该命令的输出及对应的general log。
2022-01-05T21:28:47.348592+08:00 106 Query SHOW FULL PROCESSLIST2022-01-05T21:28:57.349148+08:00 106 Query SHOW FULL PROCESSLIST2022-01-05T21:28:57.349763+08:00 106 Query KILL '103'2022-01-05T21:29:07.350167+08:00 106 Query SHOW
FULL PROCESSLIST2022-01-05T21:29:07.350651+08:00 106 Query KILL '105'2022-01-05T21:29:17.352402+08:00 106 Query SHOW FULL PROCESSLIST
可以看到,在杀掉第一个查询的时候,第二个查询其实也满足条件,但没被杀掉,而是等到下一轮检测才被杀掉。这个行为实际上是由 --victims参数控制的,--victims取值如下。
(1)oldest:每次只会杀掉执行时间最长的那个查询,是默认值。
(2)all:杀掉所有符合条件的查询。
(3)all-but-oldest:杀掉所有符合条件的查询,除了执行时间最长的那个。
既然是基于SHOW PROCESSLIST的输出,pt-kill就可从多个维度进行过滤,具体的过滤参数如下。
基于USER列的输出进行过滤。
基于HOST列的输出进行过滤。
基于db列的输出进行过滤。
基于command列的输出进行过滤。
基于State列的输出进行过滤。
基于Info列的输出进行过滤。
以上过滤参数均支持正则匹配。
需要注意的是,如果同时指定了 --busy-time和过滤参数,对于Command列不为Query的操作,此时起作用的将只有过滤参数,没有--busy-time。看下面这个示例。
# mysql -h 192.168.244.10 -uu1 -p123456mysql> select connection_id();+-----------------+| connection_id() |+-----------------+| 113 |+-----------------+1 row in set (0.00 sec)
mysql> begin;Query OK, 0 rows affected (0.00 sec)
mysql> delete from slowtech.t1 limit 1;Query OK, 1
row affected (0.00 sec)
执行pt-kill操作。
pt-kill h=192.168.244.10,P=3306,u=pt_user,p=pt_pass --busy-time 30 --interval 10 --print --kill --match-user u1
这本来是要将来自u1的执行时间超过30秒的操作杀掉,却意外地杀掉了一个未提交的事务。
究其原因,是 --busy-time只对Command列为Query的操作才有效果,而这个事务对应的Command列是Sleep。
下面从源码的角度分析pt-kill的过滤逻辑,这样我们才能更加清晰地知道--busy-time和过滤参数之间的关系。
sub find { my ( $self, $proclist, %find_spec ) = @_; PTDEBUG && _d('find specs:', Dumper(\%find_spec)); my $ms = $self->{MasterSlave}; my @matches; $self->{_reasons_for_matching} = undef; QUERY: foreach my $query ( @$proclist ) { PTDEBUG && _d('Checking query', Dumper($query)); my $matched = 0; if ( !$find_spec{replication_threads} && $ms->is_replication_thread($query) ) { PTDEBUG && _d('Skipping replication thread'); next QUERY; } if ( $find_spec{busy_time} && exists($self->{kill_busy_commands}->{$query->{Command} || ''}) ) { next QUERY unless defined($query->{Time}); if ( $query->{Time} < $find_spec{busy_time} ) { PTDEBUG && _d("Query isn't running long enough"); next QUERY; } my $reason = 'Exceeds busy time'; PTDEBUG && _d($reason); push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason; $matched++; } if ( $find_spec{idle_time} && ($query->{Command} || '') eq 'Sleep' ) { next QUERY unless defined($query->{Time}); if ( $query->{Time} < $find_spec{idle_time} ) { PTDEBUG && _d("Query isn't idle long enough"); next QUERY;
} my $reason = 'Exceeds idle time'; PTDEBUG && _d($reason); push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason; $matched++; } PROPERTY: foreach my $property ( qw(Id User Host db State Command Info) ) { my $filter = "_find_match_$property"; if ( defined $find_spec{ignore}->{$property} && $self->$filter($query, $find_spec{ignore}->{$property}) ) { PTDEBUG && _d('Query matches ignore', $property, 'spec'); next QUERY; } if ( defined $find_spec{match}->{$property} ) { if ( !$self->$filter($query, $find_spec{match}->{$property}) ) { PTDEBUG && _d('Query does not match', $property, 'spec'); next QUERY; } my $reason = 'Query matches ' . $property . ' spec'; PTDEBUG && _d($reason); push @{$self->{_reasons_for_matching}->{$query} ||= []}, $reason; $matched++; } } if ( $matched || $find_spec{all} ) { PTDEBUG && _d("Query matched one or more specs, adding"); push @matches, $query; next QUERY; } PTDEBUG && _d('Query does not match any specs, ignoring'); } return @matches;}
从源码中可以得出以下几点:
(1)--busy-time只适用于Command列为Query的操作。
(2)--idle-time只适用于Command列为Sleep的操作。
(3)--idle-time和--busy-time的处理逻辑相同。
(4)对于Command列不为Query的操作,只能通过 --ignore-user、--match-user之类的参数进行过滤。
(5)对于Command列为Query的操作,当执行时长超过 --busy-time时,将进一步通过 --ignore-user、--match-user之类的参数进行过滤。
(6)--match-all参数用来匹配所有未被忽略的操作,可用来实现否定匹配的功能。
具体命令如下。
pt-kill h=192.168.244.10,P=3306,u=pt_user,p=pt_pass --busy-time 30
--interval 10 --print --kill --log-dsn h=192.168.244.10,P=3306,u=pt_user,p=pt_pass,D=percona,t=kill_log --create-log-table
KILL操作会记录在 --log-dsn指定的实例中,如果表不存在,可指定 --create-log-table创建。表中记录如下。
mysql> select * from percona.kill_log limit 1\G*************************** 1. row *************************** kill_id: 1 server_id: 1 timestamp: 2022-01-05 22:00:11 reason: Exceeds busy timekill_error: Id: 128 User: root Host: localhost db: NULL Command: Query Time: 35 State: User sleep Info: select sleep(120) Time_ms: NULL1 row in set (0.00 sec)
具体命令如下:
pt-kill h=192.168.244.10,P=3306,u=pt_user,p=pt_pass --busy-time 30 --interval 10 --print --kill --log /tmp/pt-kill.log --daemonize
执行的kill操作会记录在 --log指定的文件中。
默认情况下,pt-kill不会杀掉复制相关的连接。
上述命令都指定了 --kill,此时会杀掉连接。如果只想杀掉查询,而不是连接,可指定 --kill-query。如果只是打印,而不是实际执行KILL操作,只需指定 --print。