9

I need to apply a query timeout at a global level in my application. The query: SET SESSION max_execution_time=1 does this with MySQL 5.7. I am using MySQL 5.6 and cannot upgrade at the moment. Any solution with SQL Alchemy would also help.

Ben
  • 51,770
  • 36
  • 127
  • 149
Swati Sinha
  • 91
  • 1
  • 2
  • According to https://stackoverflow.com/questions/4794747/mysql-can-i-limit-the-maximum-time-allowed-for-a-query-to-run and https://stackoverflow.com/questions/415905/how-to-set-a-maximum-execution-time-for-a-mysql-query you pretty much have to implement your own "watchdog" that periodically checks if queries have exceeded the timeout and kills them. – Ilja Everilä Jan 02 '19 at 12:07

1 Answers1

7

It seems there is no equivalent to max_execution_time in MySQL prior to versions 5.7.4 and 5.7.8 (the setting changed its name). What you can do is create your own periodic job that checks if queries have exceeded timeout and manually kill them. Unfortunately that is not quite the same as what the newer MySQL versions do: without inspecting the command info you'll end up killing all queries, not just read only SELECT, and it is nigh impossible to control at session level.

One way to do that would be to create a stored procedure that queries the process list and kills as required. Such stored procedure could look like:

DELIMITER //
CREATE PROCEDURE stmt_timeout_killer (timeout INT)
BEGIN
    DECLARE query_id INT;
    DECLARE done INT DEFAULT FALSE;

    DECLARE curs CURSOR FOR
    SELECT id
    FROM information_schema.processlist
    WHERE command = 'Query' AND time >= timeout;

    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

    -- Ignore ER_NO_SUCH_THREAD, in case the query finished between
    -- checking the process list and actually killing threads
    DECLARE CONTINUE HANDLER FOR 1094 BEGIN END;

    OPEN curs;

    read_loop: LOOP
        FETCH curs INTO query_id;

        IF done THEN
            LEAVE read_loop;
        END IF;

        -- Prevent suicide
        IF query_id != CONNECTION_ID() THEN
            KILL QUERY query_id;
        END IF;
    END LOOP;

    CLOSE curs;
END//
DELIMITER ;

Alternatively you could implement all that in your application logic, but it would require separate round trips to the database for each query to be killed. What's left then is to call this periodically:

# Somewhere suitable
engine.execute(text("CALL stmt_timeout_killer(:timeout)"), timeout=30)

How and where exactly depends heavily on your actual application.

Ilja Everilä
  • 50,538
  • 7
  • 126
  • 127
  • How is the performance of DB impacted if this procedure is run, let's say, every 20 seconds? Will there be a significant impact on performance? – Dilip Jan 03 '19 at 09:33
  • 1
    I've little actual experience using MySQL, but from rudimentary testing inspecting the process list is quite fast. It's a bit harder to produce a test set that covers the killing well, so I cannot say if it might block for extended periods. Given that variations on this theme are the recommended practice in the other Q/A covering this subject, I'd suppose it should not cause too much issues (except for the ones mentioned, such as killing all statements blindly, from all sessions). – Ilja Everilä Jan 03 '19 at 10:03