В силу различных обстоятельсв иногда возникает необходимость в том, что-бы прервать выполнение слишком долго работающего запросы к СУБД.

Так вот что-бы не делать этого в 3 часа ночи, лучше бы это дело автоматизировать. В MySQL для этого есть неплохая штука под названием Events. Воспользуемся ею, создадим табличку в которую будем складывать долго выполняющиеся запросы:

CREATE TABLE `bad_query_list` (
 `id` bigint(20) NOT NULL AUTO_INCREMENT,
 `date` datetime NOT NULL,
 `tid` bigint(20) NOT NULL,
 `time` int(11) NOT NULL,
 `host` varchar(512) NOT NULL,
 `user` varchar(255) NOT NULL,
 `command` varchar(255) NOT NULL,
 `state` varchar(255) NOT NULL,
 `query` varchar(4000) NOT NULL,
PRIMARY KEY (`id`),
KEY `date` (`date`),
KEY `tid` (`tid`)
) ENGINE=InnoDB AUTO_INCREMENT=4122 DEFAULT CHARSET=utf8

Затем создадим процедуру, которая будет убивать запросы выполняющиеся от пользователей без права SUPER больше определённого интервала времени (код quick and dirty):

CREATE DEFINER = `root`
@`127.0.0.1`
PROCEDURE `proc_kill_by_runtime`(IN `runtime` tinyint unsigned)
begin

	declare done int default 0;
	declare connid int unsigned;
	declare ips varchar(2000);
	declare str1 varchar(255);
	declare cur1 cursor for
		select replace(substring_index(`user_privileges`.`grantee`, '%', 1), "'", "") as `userhost`
		from `information_schema`.`user_privileges`
		where not(`user_privileges`.`grantee` in
		(select grantee from `information_schema`.`user_privileges`
		where `user_privileges`.`privilege_type` = 'SUPER'
		group by `user_privileges`.`grantee`))
		group by userhost;
	declare cur2 cursor for
		select id from information_schema.processlist
		where concat(user, '@', substring_index(host,':',1)) regexp ips
		and command = 'Query'
		and time >= runtime;
	declare continue handler for sqlstate '02000' set done = 1;

	open cur1;
	repeat
		fetch cur1 into str1;
		if not done then
			if str1 = '' then
				set ips = concat_ws("|", ips, "%");
			else
				set ips = concat_ws("|", ips, str1);
			end if;
		end if;
	until done end repeat;
	close cur1;

	set done = 0;
	open cur2;
	repeat
		fetch cur2 into connid;
		if not done then
			insert into `dbstats`.`bad_query_list` (`date`, `tid`, `time`, `host`, `user`, `command`, `state`, `query`)
			select now(), `id`, `time`, `host`, `user`, `command`, `state`, `info`
			from information_schema.processlist
			where `id` = connid;
			kill connid;
		end if;
	until done end repeat;
	close cur2;

end

Теперь создадим событие для планировщика MySQL:

CREATE EVENT `run_proc_kill_by_runtime`
ON SCHEDULE EVERY 2 SECOND STARTS '2011-10-13 18:50:55'
ON COMPLETION PRESERVE ENABLE
DO call proc_kill_by_runtime(100)

После этих действий все запросы, которые работают более 99 секунд будут складываться в табличку dbstats.bad_query_list и прибиваться.

Стоит добавить, что для безопасного отката транзакции лучше бы настроить переменные InnoDB:

innodb_rollback_on_timeout = 1
innodb_lock_wait_timeout = 50

Have a fun and good nights!


blog comments powered by Disqus

Published

18 October 2011

Category

howto

Tags

SHARE THIS ARTICLE