Commit 99f28694 authored by Oleg Smirnov's avatar Oleg Smirnov

MDEV-34860 Implement MAX_EXECUTION_TIME hint

It places a limit N (a timeout value in milliseconds) on how long
a statement is permitted to execute before the server terminates it.

Syntax:
SELECT /*+ MAX_EXECUTION_TIME(milliseconds) */ ...

Only top-level SELECT statements support the hint.
parent 824c20ae
#
# MAX_EXECUTION_TIME hint testing
#
CREATE TABLE t1 (a INT, b VARCHAR(300));
INSERT INTO t1 VALUES (1, 'string');
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
# Correct hint usage
SELECT /*+ MAX_EXECUTION_TIME(10) */* FROM t1 a, t1 b;
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
EXPLAIN EXTENDED SELECT /*+ MAX_EXECUTION_TIME(000149) */* FROM t1;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 512 100.00
Warnings:
Note 1003 select /*+ MAX_EXECUTION_TIME(000149) */ `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b` from `test`.`t1`
SELECT /*+ MAX_EXECUTION_TIME(20) */ *, SLEEP(1) FROM t1 UNION SELECT 1, 2, 3;
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
(SELECT /*+ MAX_EXECUTION_TIME(30) */ *, SLEEP(1) FROM t1) UNION (SELECT 1, 2, 3);
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
((SELECT /*+ MAX_EXECUTION_TIME(50) */ *, SLEEP(1) FROM t1));
ERROR 70100: Query execution was interrupted (max_statement_time exceeded)
# Hint duplication
SELECT /*+ MAX_EXECUTION_TIME(10) MAX_EXECUTION_TIME(100) */ count(*) FROM t1;
count(*)
512
Warnings:
Warning 4202 Hint MAX_EXECUTION_TIME(100) is ignored as conflicting/duplicated
# Wrong values
SELECT /*+ MAX_EXECUTION_TIME(0) */ count(*) FROM t1;
count(*)
512
Warnings:
Warning 1912 Incorrect value '0' for option 'MAX_EXECUTION_TIME'
SELECT /*+ MAX_EXECUTION_TIME(-1) */ count(*) FROM t1;
count(*)
512
Warnings:
Warning 1064 Optimizer hint syntax error near '-1) */ count(*) FROM t1' at line 1
SELECT /*+ MAX_EXECUTION_TIME(4294967296) */ count(*) FROM t1;
count(*)
512
Warnings:
Warning 1912 Incorrect value '4294967296' for option 'MAX_EXECUTION_TIME'
# Conflicting max_statement_time and hint (must issue a warning)
SET STATEMENT max_statement_time=1 FOR
SELECT /*+ MAX_EXECUTION_TIME(500) */ count(*) FROM t1 a;
count(*)
512
Warnings:
Warning 4202 Hint MAX_EXECUTION_TIME(500) is ignored as conflicting/duplicated
# only SELECT statements supports the MAX_EXECUTION_TIME hint (warning):
CREATE TABLE t2 (i INT);
INSERT /*+ MAX_EXECUTION_TIME(10) */ INTO t2 SELECT 1;
Warnings:
Warning 4172 'MAX_EXECUTION_TIME(10)' is not allowed in this context
REPLACE /*+ MAX_EXECUTION_TIME(15) */ INTO t2 SELECT 1;
Warnings:
Warning 4172 'MAX_EXECUTION_TIME(15)' is not allowed in this context
UPDATE /*+ MAX_EXECUTION_TIME(23) */ t2 SET i = 1;
Warnings:
Warning 4172 'MAX_EXECUTION_TIME(23)' is not allowed in this context
DELETE /*+ MAX_EXECUTION_TIME(5000) */ FROM t2 WHERE i = 1;
Warnings:
Warning 4172 'MAX_EXECUTION_TIME(5000)' is not allowed in this context
# Not supported inside stored procedures/functions
CREATE PROCEDURE p1() BEGIN SELECT /*+ MAX_EXECUTION_TIME(10) */ count(*) FROM t1 a, t1 b
INTO @a; END|
CALL p1();
Warnings:
Warning 4172 'MAX_EXECUTION_TIME(10)' is not allowed in this context
DROP PROCEDURE p1;
# Hint in a subquery is not allowed (warning):
SELECT 1 FROM (SELECT /*+ MAX_EXECUTION_TIME(10) */ 1) a;
1
1
Warnings:
Warning 4172 'MAX_EXECUTION_TIME(10)' is not allowed in this context
DROP TABLE t1, t2;
--echo #
--echo # MAX_EXECUTION_TIME hint testing
--echo #
CREATE TABLE t1 (a INT, b VARCHAR(300));
INSERT INTO t1 VALUES (1, 'string');
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
INSERT INTO t1 SELECT * FROM t1;
-- disable_query_log
-- disable_result_log
analyze table t1;
-- enable_result_log
-- enable_query_log
--echo # Correct hint usage
--error ER_STATEMENT_TIMEOUT
SELECT /*+ MAX_EXECUTION_TIME(10) */* FROM t1 a, t1 b;
EXPLAIN EXTENDED SELECT /*+ MAX_EXECUTION_TIME(000149) */* FROM t1;
--error ER_STATEMENT_TIMEOUT
SELECT /*+ MAX_EXECUTION_TIME(20) */ *, SLEEP(1) FROM t1 UNION SELECT 1, 2, 3;
--error ER_STATEMENT_TIMEOUT
(SELECT /*+ MAX_EXECUTION_TIME(30) */ *, SLEEP(1) FROM t1) UNION (SELECT 1, 2, 3);
--error ER_STATEMENT_TIMEOUT
((SELECT /*+ MAX_EXECUTION_TIME(50) */ *, SLEEP(1) FROM t1));
--echo # Hint duplication
SELECT /*+ MAX_EXECUTION_TIME(10) MAX_EXECUTION_TIME(100) */ count(*) FROM t1;
--echo # Wrong values
SELECT /*+ MAX_EXECUTION_TIME(0) */ count(*) FROM t1;
SELECT /*+ MAX_EXECUTION_TIME(-1) */ count(*) FROM t1;
SELECT /*+ MAX_EXECUTION_TIME(4294967296) */ count(*) FROM t1;
--echo # Conflicting max_statement_time and hint (must issue a warning)
SET STATEMENT max_statement_time=1 FOR
SELECT /*+ MAX_EXECUTION_TIME(500) */ count(*) FROM t1 a;
--echo
--echo # only SELECT statements supports the MAX_EXECUTION_TIME hint (warning):
--echo
CREATE TABLE t2 (i INT);
INSERT /*+ MAX_EXECUTION_TIME(10) */ INTO t2 SELECT 1;
REPLACE /*+ MAX_EXECUTION_TIME(15) */ INTO t2 SELECT 1;
UPDATE /*+ MAX_EXECUTION_TIME(23) */ t2 SET i = 1;
DELETE /*+ MAX_EXECUTION_TIME(5000) */ FROM t2 WHERE i = 1;
--echo # Not supported inside stored procedures/functions
DELIMITER |;
CREATE PROCEDURE p1() BEGIN SELECT /*+ MAX_EXECUTION_TIME(10) */ count(*) FROM t1 a, t1 b
INTO @a; END|
DELIMITER ;|
CALL p1();
DROP PROCEDURE p1;
--echo # Hint in a subquery is not allowed (warning):
SELECT 1 FROM (SELECT /*+ MAX_EXECUTION_TIME(10) */ 1) a;
DROP TABLE t1, t2;
......@@ -1799,6 +1799,14 @@ id select_type table type possible_keys key key_len ref rows filtered Extra
Warnings:
Note 1003 select /*+ QB_NAME(`a
b`) */ 1 AS `1`
# Identifiers starting with digits must be supported:
CREATE OR REPLACE TABLE 0a (8a INT, KEY 6a(8a));
EXPLAIN EXTENDED SELECT /*+ NO_MRR(0a 6a) BKA(0a)*/ 8a FROM 0a;
id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE 0a system NULL NULL NULL NULL 0 0.00 Const row not found
Warnings:
Note 1003 select /*+ BKA(`0a`@`select#1`) NO_MRR(`0a`@`select#1` `6a`) */ NULL AS `8a` from `test`.`0a`
DROP TABLE 0a;
# hint syntax error: empty quoted identifier
EXPLAIN EXTENDED SELECT /*+ QB_NAME(``) */ 1;
id select_type table type possible_keys key key_len ref rows filtered Extra
......@@ -1876,7 +1884,7 @@ SELECT /*+ NO_ICP(10) */ 1;
1
1
Warnings:
Warning 4204 Unresolved table name `10`@`select#1` for NO_ICP hint
Warning 1064 Optimizer hint syntax error near '10) */ 1' at line 1
SELECT /*+ NO_ICP( */ 1;
1
1
......
......@@ -914,6 +914,11 @@ EXPLAIN EXTENDED SELECT /*+ QB_NAME(`*b`) */ 1;
EXPLAIN EXTENDED SELECT /*+ QB_NAME(`a
b`) */ 1;
--echo # Identifiers starting with digits must be supported:
CREATE OR REPLACE TABLE 0a (8a INT, KEY 6a(8a));
EXPLAIN EXTENDED SELECT /*+ NO_MRR(0a 6a) BKA(0a)*/ 8a FROM 0a;
DROP TABLE 0a;
--echo # hint syntax error: empty quoted identifier
EXPLAIN EXTENDED SELECT /*+ QB_NAME(``) */ 1;
......
......@@ -4804,7 +4804,7 @@ int Rows_log_event::do_apply_event(rpl_group_info *rgi)
set/reset the slave thread's timer; a Rows_log_event update needs to set
the timer itself
*/
thd->set_query_timer();
thd->set_query_timer_if_needed();
/*
If there are no tables open, this must be the first row event seen
......
This diff is collapsed.
......@@ -73,7 +73,7 @@
#include "sql_bitmap.h"
#include "sql_show.h"
#include "mysqld_error.h"
#include "opt_hints_parser.h"
struct LEX;
struct TABLE;
......@@ -91,6 +91,7 @@ enum opt_hints_enum
MRR_HINT_ENUM,
NO_RANGE_HINT_ENUM,
QB_NAME_HINT_ENUM,
MAX_EXEC_TIME_HINT_ENUM,
MAX_HINT_ENUM
};
......@@ -304,6 +305,13 @@ class Opt_hints : public Sql_alloc
void check_unresolved(THD *thd);
virtual void append_name(THD *thd, String *str)= 0;
/**
Append additional hint arguments to the printed string if they exist.
For example, SEMIJOIN and SUBQUERY hints may have a list of strategies
as additional arguments
*/
virtual void append_args(THD *thd, String *str) const {}
virtual ~Opt_hints() {}
private:
......@@ -336,13 +344,29 @@ class Opt_hints : public Sql_alloc
class Opt_hints_global : public Opt_hints
{
public:
const Optimizer_hint_parser::Max_execution_time_hint *max_exec_time_hint= nullptr;
/*
If MAX_EXECUTION_TIME() hint was provided, this pointer is set to
the SELECT_LEX which the hint is attached to.
NULL if MAX_EXECUTION_TIME() hint is missing.
*/
st_select_lex *max_exec_time_select_lex= nullptr;
Opt_hints_global(MEM_ROOT *mem_root_arg)
: Opt_hints(Lex_ident_sys(), NULL, mem_root_arg)
{}
virtual void append_name(THD *thd, String *str) override {}
virtual void append_args(THD *thd, String *str) const override
{
if (max_exec_time_hint)
max_exec_time_hint->append_args(thd, str);
}
bool resolve(THD *thd);
};
......
......@@ -57,11 +57,30 @@ Optimizer_hint_tokenizer::find_keyword(const LEX_CSTRING &str)
return TokenID::keyword_QB_NAME;
break;
case 18:
if ("MAX_EXECUTION_TIME"_Lex_ident_column.streq(str))
return TokenID::keyword_MAX_EXECUTION_TIME;
break;
case 21:
if ("NO_RANGE_OPTIMIZATION"_Lex_ident_column.streq(str))
return TokenID::keyword_NO_RANGE_OPTIMIZATION;
break;
}
if (str.length > 0 && (str.str[0] >= '0' && str.str[0] <= '9'))
{
/*
If all characters are digits, qualify the token as a number,
otherwise as an identifier
*/
for(size_t i = 1; i < str.length; i++)
{
if (str.str[i] < '0' || str.str[i] > '9')
return TokenID::tIDENT;
}
return TokenID::tUNSIGNED_NUMBER;
}
return TokenID::tIDENT;
}
......
......@@ -22,6 +22,7 @@
#include "simple_tokenizer.h"
#include "sql_list.h"
#include "sql_string.h"
#include "sql_type_int.h"
#include "simple_parser.h"
class st_select_lex;
......@@ -72,9 +73,11 @@ class Optimizer_hint_tokenizer: public Extended_string_tokenizer
keyword_NO_RANGE_OPTIMIZATION,
keyword_MRR,
keyword_QB_NAME,
keyword_MAX_EXECUTION_TIME,
// Other token types
tIDENT
tIDENT,
tUNSIGNED_NUMBER
};
class Token: public Lex_cstring
......@@ -240,6 +243,13 @@ class Optimizer_hint_parser: public Optimizer_hint_tokenizer,
using TOKEN::TOKEN;
};
class Keyword_MAX_EXECUTION_TIME:
public TOKEN<Parser, TokenID::keyword_MAX_EXECUTION_TIME>
{
public:
using TOKEN::TOKEN;
};
class Identifier: public TOKEN<Parser, TokenID::tIDENT>
{
public:
......@@ -258,6 +268,28 @@ class Optimizer_hint_parser: public Optimizer_hint_tokenizer,
}
};
class Unsigned_Number: public TOKEN<Parser, TokenID::tUNSIGNED_NUMBER>
{
public:
using TOKEN::TOKEN;
/*
Converts token string to a non-negative number ( >=0 ).
Returns the converted number if the conversion succeeds.
Returns non-NULL ULonglong_null value on successful string conversion and
NULL ULonglong_null if the conversion failed or the number is negative
*/
ULonglong_null get_ulonglong() const
{
int error;
char *end= const_cast<char *>(str + length);
longlong n= my_strtoll10(str, &end, &error);
if (error != 0 || end != str + length || n < 0)
return ULonglong_null(0, true);
return ULonglong_null(n, false);
}
};
class LParen: public TOKEN<Parser, TokenID::tLPAREN>
{
public:
......@@ -554,21 +586,40 @@ class Optimizer_hint_parser: public Optimizer_hint_tokenizer,
};
public:
// max_execution_time_hint ::= MAX_EXECUTION_TIME ( milliseconds )
class Max_execution_time_hint: public AND4<Parser,
Keyword_MAX_EXECUTION_TIME,
LParen,
Unsigned_Number,
RParen>
{
public:
using AND4::AND4;
bool resolve(Parse_context *pc) const;
void append_args(THD *thd, String *str) const;
ulong get_milliseconds() const;
};
/*
hint ::= index_level_hint
| table_level_hint
| qb_name_hint
| statement_level_hint
*/
class Hint: public OR3<Parser,
class Hint: public OR4<Parser,
Index_level_hint,
Table_level_hint,
Qb_name_hint>
Qb_name_hint,
Max_execution_time_hint>
{
public:
using OR3::OR3;
using OR4::OR4;
};
private:
// hint_list ::= hint [ hint... ]
class Hint_list_container: public List<Hint>
{
......
......@@ -12292,3 +12292,7 @@ ER_UNRESOLVED_INDEX_HINT_NAME
eng "Unresolved index name %s for %s hint"
ER_HINTS_INSIDE_VIEWS_NOT_SUPPORTED
eng "Optimizer hints are not supported inside view definitions"
ER_WARN_BAD_MAX_EXECUTION_TIME
eng "Unsupported MAX_EXECUTION_TIME"
ER_WARN_UNSUPPORTED_MAX_EXECUTION_TIME
eng "MAX_EXECUTION_TIME hint is supported by top-level standalone SELECT statements only"
......@@ -380,7 +380,7 @@ class Parser_templates
/*
A rule consisting of a choice of thee rules:
A rule consisting of a choice of three rules:
rule ::= rule1 | rule2 | rule3
For the case when the three branches have incompatible storage
......@@ -478,7 +478,50 @@ class Parser_templates
/*
A list with at least MIN_COUNT elements (typlically 0 or 1),
A rule consisting of a choice of four rules:
rule ::= rule1 | rule2 | rule3 | rule4
For the case when the three branches have incompatible storage
*/
template<class PARSER, class A, class B, class C, class D>
class OR4: public A, public B, public C, public D
{
public:
OR4()
{ }
OR4(OR4 &&rhs)
:A(std::move(static_cast<A&&>(rhs))),
B(std::move(static_cast<B&&>(rhs))),
C(std::move(static_cast<C&&>(rhs))),
D(std::move(static_cast<D&&>(rhs)))
{ }
OR4 & operator=(OR4 &&rhs)
{
A::operator=(std::move(static_cast<A&&>(rhs)));
B::operator=(std::move(static_cast<B&&>(rhs)));
C::operator=(std::move(static_cast<C&&>(rhs)));
D::operator=(std::move(static_cast<D&&>(rhs)));
return *this;
}
OR4(PARSER *p)
:A(p),
B(A::operator bool() ? B() : B(p)),
C(A::operator bool() || B::operator bool() ? C() : C(p)),
D(A::operator bool() || B::operator bool() || C::operator bool() ?
D() : D(p))
{
DBUG_ASSERT(!operator bool() || !p->is_error());
}
operator bool() const
{
return A::operator bool() || B::operator bool() || C::operator bool() ||
D::operator bool();
}
};
/*
A list with at least MIN_COUNT elements (typically 0 or 1),
with or without a token separator between elements:
list ::= element [ {, element }... ] // with a separator
......
......@@ -8431,6 +8431,8 @@ bool setup_tables(THD *thd, Name_resolution_context *context,
if (setup_natural_join_row_types(thd, from_clause, context))
DBUG_RETURN(1);
if (thd->lex->opt_hints_global && select_lex->select_number == 1)
thd->lex->opt_hints_global->resolve(thd);
if (qb_hints)
qb_hints->check_unresolved(thd);
DBUG_RETURN(0);
......
......@@ -5826,7 +5826,7 @@ class THD: public THD_count, /* this must be first */
thr_timer_t query_timer;
public:
void set_query_timer()
void set_query_timer_if_needed()
{
#ifndef EMBEDDED_LIBRARY
/*
......@@ -5845,9 +5845,13 @@ class THD: public THD_count, /* this must be first */
*/
if (!timeout_val || spcont || in_sub_stmt || query_timer.expired == 0)
return;
thr_timer_settime(&query_timer, timeout_val);
set_query_timer_force(timeout_val);
#endif
}
void set_query_timer_force(ulonglong timeout_val)
{
thr_timer_settime(&query_timer, timeout_val);
}
void reset_query_timer()
{
#ifndef EMBEDDED_LIBRARY
......
......@@ -3822,7 +3822,7 @@ mysql_execute_command(THD *thd, bool is_called_from_prepared_stmt)
thd->query_plan_flags|= QPLAN_ADMIN;
/* Start timeouts */
thd->set_query_timer();
thd->set_query_timer_if_needed();
#ifdef WITH_WSREP
/* Check wsrep_mode rules before command execution. */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment