Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

Commit 298aee6

Browse files
lthHerman Lee
authored andcommitted
Limit memory usage used by quick ranges during range analysis
Summary: During range optimization, a tree of SEL_ARG objects are built, representing the set of intervals that we need to examine for the query. A description of the tree representation exists here (https://github.com/facebook/mysql-5.6/blob/c2a7d7274b0d9c22a8e7affc41fb53f6ef7083f3/sql/opt_range.cc#L188). To prevent the tree from consuming too much memory (eg. from very large IN-lists), a variable called range_optimizer_max_mem_size exists to limit memory usage. However, once range analysis is finished, the tree is then flattened into a list of ranges, and this can grow large very quickly. For example, a query like `SELECT .. WHERE a in (1, 2, ... 1000) and b in (1, 2, ... 1000)` will contain 1000000 ranges. There is no memory limit for these allocations, and this can cause the server to OOM. To address this, we will also apply the same range_optimizer_max_mem_size limit on the list of ranges. We could have also had a separate limit for quick ranges, but we would need the error handler to be aware of which MEM_ROOT object was used. Also, it seemed simpler for the end user to not have to distinguish between the cases, since the tree gets freed after building the ranges anyway. This change also includes a feature to optionally error out the query when memory limits are reached. The problem today is that when range analysis runs out of memory, we fall back to full index scans. It may be better just to error out. Some implementation notes: - QUICK_* objects (eg. QUICK_RANGE_SELECT) contain the QUICK_RANGEs needed for execution. Each of these QUICK objects contain a MEM_ROOT object already. Presumably the intention is that all objects belonging to the QUICK object should be backed by its MEM_ROOT, and this is what this fix does. We are making sure that all allocations for QUICK_RANGEs belonging to a QUICK object come from its associated MEM_ROOT. - Some QUICK objects contain other children QUICK objects (eg. QUICK_GROUP_MIN_MAX_SELECT may contain a QUICK_RANGE_SELECT object for some bookkeeping). In these cases, the children QUICK objects do not use their own MEM_ROOTs (and it is zero'd out) and will use their parent's MEM_ROOT instead. Therefore, we should always be using parent_alloc for allocations if possible. - A limit checking MEM_ROOT will still allocate memory even when it has reached its limit. This means that the caller is responsible for checking for errors, and for ending safely whatever recursion/loop creating these allocations. Filed an upstream bug: https://bugs.mysql.com/100595 Reference patch: 41b2311 In the future, we may consider dropping this patch in favour of a more generic solution like mysql/mysql-server@dfdd36c Reviewed By: hermanlee Differential Revision: D34256842 ---------------------------------------------------------------------------------------------------- Fix range mtr tests Summary: During the rebase to 8.0.28, the MEM_ROOT for quick range allocation has changed from using its own MEM_ROOT to using the main MEM_ROOT, which makes accounting trickier. While normal builds will allocate memory in large chunks and satisfy requests for memory from those chunks, asan/valgrind will convert all requests for memory into explicit allocations. This means that we allocate memory more frequently in asan/valgrind which means that we perform more frequent checks to see if we've exceeded the limit. This leads to 2 issues: - The main MEM_ROOT will already have memory allocated on it, which may already exceed the limit. The next allocation will always fail in that case. The fix is to set the limit to the already allocated amount plus the range max mem limit. There's some additional code in the error handler to ensure that the correct error message is displayed. - It's generally non-deterministic whether a query will fail or not. This is because there is still unused allocated memory on the main MEM_ROOT, and depending on how much we have left and how much the range optimizer takes, we may or may not hit an error. To fix this, I just disabled the `main.range_with_memory_limit_error` test in asan/valgrind. Reviewed By: hermanlee Differential Revision: D42949953
1 parent ce94ea1 commit 298aee6

12 files changed

Lines changed: 3287 additions & 15 deletions

‎mysql-test/r/mysqld--help-notwin.result‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1494,6 +1494,9 @@ The following options may be given as the first argument:
14941494
--range-alloc-block-size=#
14951495
Allocation block size for storing ranges during
14961496
optimization
1497+
--range-optimizer-fail-mode[=name]
1498+
Determines the behavior when range analysis fails due to
1499+
memory limits.
14971500
--range-optimizer-max-mem-size=#
14981501
Maximum amount of memory used by the range optimizer to
14991502
allocate predicates during range analysis. The larger the
@@ -3377,6 +3380,7 @@ raft-high-priority-read-only TRUE
33773380
raft-send-replica-statistics FALSE
33783381
raft-signal-async-dump-threads AFTER_CONSENSUS
33793382
range-alloc-block-size 4096
3383+
range-optimizer-fail-mode WARN
33803384
range-optimizer-max-mem-size 8388608
33813385
rbr-column-type-mismatch-whitelist
33823386
read-buffer-size 131072

‎mysql-test/r/range_with_memory_limit_error.result‎

Lines changed: 2936 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
create table t (i varchar(100), j varchar(100), k varchar(100), l varchar(100), m varchar(100), n varchar(100), o varchar(100), p varchar(100), q varchar(100), primary key(i, j, k, l, m, n, o, p, q)) engine=innodb character set latin1;
2+
set optimizer_trace='enabled=on';
3+
set optimizer_trace_max_mem_size = 1000000000;
4+
set optimizer_force_index_for_range = true;
5+
set range_optimizer_max_mem_size = 10000000;
6+
explain select * from t force index (primary) where i in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
7+
j in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
8+
k in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
9+
l in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
10+
m in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
11+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
12+
1 SIMPLE t NULL range PRIMARY PRIMARY 510 NULL 100000 100.00 Using where; Using index
13+
Warnings:
14+
Warning 3170 Memory capacity of 10000000 bytes for 'range_optimizer_max_mem_size' exceeded. Range optimization was not done for this query.
15+
Note 1003 /* select#1 */ select `test`.`t`.`i` AS `i`,`test`.`t`.`j` AS `j`,`test`.`t`.`k` AS `k`,`test`.`t`.`l` AS `l`,`test`.`t`.`m` AS `m`,`test`.`t`.`n` AS `n`,`test`.`t`.`o` AS `o`,`test`.`t`.`p` AS `p`,`test`.`t`.`q` AS `q` from `test`.`t` FORCE INDEX (PRIMARY) where ((`test`.`t`.`i` in ('1','2','3','4','5','6','7','8','9','10')) and (`test`.`t`.`j` in ('1','2','3','4','5','6','7','8','9','10')) and (`test`.`t`.`k` in ('1','2','3','4','5','6','7','8','9','10')) and (`test`.`t`.`l` in ('1','2','3','4','5','6','7','8','9','10')) and (`test`.`t`.`m` in ('1','2','3','4','5','6','7','8','9','10')))
16+
select count(*) from information_schema.optimizer_trace where trace like '%reached_tree_mem_limit%';
17+
count(*)
18+
0
19+
select count(*) from information_schema.optimizer_trace where trace like '%reached_quick_ranges_mem_limit%';
20+
count(*)
21+
1
22+
set range_optimizer_max_mem_size = 10;
23+
explain select * from t force index (primary) where i in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
24+
j in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
25+
k in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
26+
l in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
27+
m in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
28+
id select_type table partitions type possible_keys key key_len ref rows filtered Extra
29+
1 SIMPLE t NULL index PRIMARY PRIMARY 918 NULL 1 100.00 Using where; Using index
30+
Warnings:
31+
Warning 3170 Memory capacity of 10 bytes for 'range_optimizer_max_mem_size' exceeded. Range optimization was not done for this query.
32+
Note 1003 /* select#1 */ select `test`.`t`.`i` AS `i`,`test`.`t`.`j` AS `j`,`test`.`t`.`k` AS `k`,`test`.`t`.`l` AS `l`,`test`.`t`.`m` AS `m`,`test`.`t`.`n` AS `n`,`test`.`t`.`o` AS `o`,`test`.`t`.`p` AS `p`,`test`.`t`.`q` AS `q` from `test`.`t` FORCE INDEX (PRIMARY) where ((`test`.`t`.`i` in ('1','2','3','4','5','6','7','8','9','10')) and (`test`.`t`.`j` in ('1','2','3','4','5','6','7','8','9','10')) and (`test`.`t`.`k` in ('1','2','3','4','5','6','7','8','9','10')) and (`test`.`t`.`l` in ('1','2','3','4','5','6','7','8','9','10')) and (`test`.`t`.`m` in ('1','2','3','4','5','6','7','8','9','10')))
33+
select count(*) from information_schema.optimizer_trace where trace like '%reached_tree_mem_limit%';
34+
count(*)
35+
1
36+
select count(*) from information_schema.optimizer_trace where trace like '%reached_quick_ranges_mem_limit%';
37+
count(*)
38+
0
39+
set range_optimizer_max_mem_size = DEFAULT;
40+
set optimizer_force_index_for_range = DEFAULT;
41+
set optimizer_trace_max_mem_size = DEFAULT;
42+
set optimizer_trace=DEFAULT;
43+
drop table t;
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#
2+
# Basic testing of the range_optimizer_fail_mode variable.
3+
#
4+
5+
# Save default value.
6+
7+
SET @global_range_optimizer_fail_mode_value = @@GLOBAL.range_optimizer_fail_mode;
8+
SET @session_range_optimizer_fail_mode_value = @@SESSION.range_optimizer_fail_mode;
9+
10+
# Default values.
11+
12+
SELECT @global_range_optimizer_fail_mode_value, @session_range_optimizer_fail_mode_value;
13+
@global_range_optimizer_fail_mode_value @session_range_optimizer_fail_mode_value
14+
WARN WARN
15+
16+
# Invalid values.
17+
18+
SET GLOBAL range_optimizer_fail_mode = NULL;
19+
ERROR 42000: Variable 'range_optimizer_fail_mode' can't be set to the value of 'NULL'
20+
SET GLOBAL range_optimizer_fail_mode = -1;
21+
ERROR 42000: Variable 'range_optimizer_fail_mode' can't be set to the value of '-1'
22+
SET GLOBAL range_optimizer_fail_mode = 1000;
23+
ERROR 42000: Variable 'range_optimizer_fail_mode' can't be set to the value of '1000'
24+
SET GLOBAL range_optimizer_fail_mode = 'INVALID';
25+
ERROR 42000: Variable 'range_optimizer_fail_mode' can't be set to the value of 'INVALID'
26+
SET GLOBAL range_optimizer_fail_mode = 'WA';
27+
ERROR 42000: Variable 'range_optimizer_fail_mode' can't be set to the value of 'WA'
28+
29+
# Valid values.
30+
31+
SET GLOBAL range_optimizer_fail_mode = 1;
32+
SELECT @@GLOBAL.range_optimizer_fail_mode;
33+
@@GLOBAL.range_optimizer_fail_mode
34+
ERROR
35+
SET GLOBAL range_optimizer_fail_mode = 0;
36+
SELECT @@GLOBAL.range_optimizer_fail_mode;
37+
@@GLOBAL.range_optimizer_fail_mode
38+
WARN
39+
SET GLOBAL range_optimizer_fail_mode = 'WARN';
40+
SELECT @@GLOBAL.range_optimizer_fail_mode;
41+
@@GLOBAL.range_optimizer_fail_mode
42+
WARN
43+
SET SESSION range_optimizer_fail_mode = 1;
44+
SELECT @@SESSION.range_optimizer_fail_mode;
45+
@@SESSION.range_optimizer_fail_mode
46+
ERROR
47+
SET SESSION range_optimizer_fail_mode = 0;
48+
SELECT @@SESSION.range_optimizer_fail_mode;
49+
@@SESSION.range_optimizer_fail_mode
50+
WARN
51+
SET SESSION range_optimizer_fail_mode = 'WARN';
52+
SELECT @@SESSION.range_optimizer_fail_mode;
53+
@@SESSION.range_optimizer_fail_mode
54+
WARN
55+
56+
# Information schema global/session variables tables.
57+
58+
SET GLOBAL range_optimizer_fail_mode = 0;
59+
SET SESSION range_optimizer_fail_mode = 0;
60+
SELECT @@GLOBAL.range_optimizer_fail_mode = VARIABLE_VALUE
61+
FROM performance_schema.global_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
62+
@@GLOBAL.range_optimizer_fail_mode = VARIABLE_VALUE
63+
1
64+
SELECT @@SESSION.range_optimizer_fail_mode = VARIABLE_VALUE
65+
FROM performance_schema.session_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
66+
@@SESSION.range_optimizer_fail_mode = VARIABLE_VALUE
67+
1
68+
SET GLOBAL range_optimizer_fail_mode = 1;
69+
SET SESSION range_optimizer_fail_mode = 0;
70+
SELECT VARIABLE_VALUE
71+
FROM performance_schema.global_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
72+
VARIABLE_VALUE
73+
ERROR
74+
SELECT VARIABLE_VALUE
75+
FROM performance_schema.session_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
76+
VARIABLE_VALUE
77+
WARN
78+
SET GLOBAL range_optimizer_fail_mode = 0;
79+
SET SESSION range_optimizer_fail_mode = 1;
80+
SELECT VARIABLE_VALUE
81+
FROM performance_schema.global_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
82+
VARIABLE_VALUE
83+
WARN
84+
SELECT VARIABLE_VALUE
85+
FROM performance_schema.session_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
86+
VARIABLE_VALUE
87+
ERROR
88+
89+
# Restore default value.
90+
91+
SET GLOBAL range_optimizer_fail_mode = @global_range_optimizer_fail_mode_value;
92+
SET SESSION range_optimizer_fail_mode = @session_range_optimizer_fail_mode_value;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
--echo #
2+
--echo # Basic testing of the range_optimizer_fail_mode variable.
3+
--echo #
4+
5+
--echo
6+
--echo # Save default value.
7+
--echo
8+
9+
SET @global_range_optimizer_fail_mode_value = @@GLOBAL.range_optimizer_fail_mode;
10+
SET @session_range_optimizer_fail_mode_value = @@SESSION.range_optimizer_fail_mode;
11+
12+
--echo
13+
--echo # Default values.
14+
--echo
15+
16+
SELECT @global_range_optimizer_fail_mode_value, @session_range_optimizer_fail_mode_value;
17+
18+
--echo
19+
--echo # Invalid values.
20+
--echo
21+
22+
--error ER_WRONG_VALUE_FOR_VAR
23+
SET GLOBAL range_optimizer_fail_mode = NULL;
24+
--error ER_WRONG_VALUE_FOR_VAR
25+
SET GLOBAL range_optimizer_fail_mode = -1;
26+
--error ER_WRONG_VALUE_FOR_VAR
27+
SET GLOBAL range_optimizer_fail_mode = 1000;
28+
--error ER_WRONG_VALUE_FOR_VAR
29+
SET GLOBAL range_optimizer_fail_mode = 'INVALID';
30+
--error ER_WRONG_VALUE_FOR_VAR
31+
SET GLOBAL range_optimizer_fail_mode = 'WA';
32+
33+
--echo
34+
--echo # Valid values.
35+
--echo
36+
37+
SET GLOBAL range_optimizer_fail_mode = 1;
38+
SELECT @@GLOBAL.range_optimizer_fail_mode;
39+
40+
SET GLOBAL range_optimizer_fail_mode = 0;
41+
SELECT @@GLOBAL.range_optimizer_fail_mode;
42+
43+
SET GLOBAL range_optimizer_fail_mode = 'WARN';
44+
SELECT @@GLOBAL.range_optimizer_fail_mode;
45+
46+
SET SESSION range_optimizer_fail_mode = 1;
47+
SELECT @@SESSION.range_optimizer_fail_mode;
48+
49+
SET SESSION range_optimizer_fail_mode = 0;
50+
SELECT @@SESSION.range_optimizer_fail_mode;
51+
52+
SET SESSION range_optimizer_fail_mode = 'WARN';
53+
SELECT @@SESSION.range_optimizer_fail_mode;
54+
55+
--echo
56+
--echo # Information schema global/session variables tables.
57+
--echo
58+
59+
SET GLOBAL range_optimizer_fail_mode = 0;
60+
SET SESSION range_optimizer_fail_mode = 0;
61+
62+
SELECT @@GLOBAL.range_optimizer_fail_mode = VARIABLE_VALUE
63+
FROM performance_schema.global_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
64+
65+
SELECT @@SESSION.range_optimizer_fail_mode = VARIABLE_VALUE
66+
FROM performance_schema.session_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
67+
68+
SET GLOBAL range_optimizer_fail_mode = 1;
69+
SET SESSION range_optimizer_fail_mode = 0;
70+
71+
SELECT VARIABLE_VALUE
72+
FROM performance_schema.global_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
73+
74+
SELECT VARIABLE_VALUE
75+
FROM performance_schema.session_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
76+
77+
SET GLOBAL range_optimizer_fail_mode = 0;
78+
SET SESSION range_optimizer_fail_mode = 1;
79+
80+
SELECT VARIABLE_VALUE
81+
FROM performance_schema.global_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
82+
83+
SELECT VARIABLE_VALUE
84+
FROM performance_schema.session_variables WHERE VARIABLE_NAME='range_optimizer_fail_mode';
85+
86+
--echo
87+
--echo # Restore default value.
88+
--echo
89+
90+
SET GLOBAL range_optimizer_fail_mode = @global_range_optimizer_fail_mode_value;
91+
SET SESSION range_optimizer_fail_mode = @session_range_optimizer_fail_mode_value;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Run through a variety of range plans. Disable errors since include/range.inc
2+
# will not be expecting errors. The error code will still be recorded in the
3+
# results file though.
4+
5+
# MEM_ROOT behaves differently under valgrind/asan, so more queries will hit max mem error leading to results mismatch.
6+
--source include/not_valgrind.inc
7+
--source include/not_asan.inc
8+
9+
--disable_abort_on_error
10+
set range_optimizer_max_mem_size = 10;
11+
set range_optimizer_fail_mode = ERROR;
12+
13+
--source include/range.inc
14+
15+
set range_optimizer_fail_mode = DEFAULT;
16+
set range_optimizer_max_mem_size = DEFAULT;
17+
--enable_abort_on_error
18+
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
create table t (i varchar(100), j varchar(100), k varchar(100), l varchar(100), m varchar(100), n varchar(100), o varchar(100), p varchar(100), q varchar(100), primary key(i, j, k, l, m, n, o, p, q)) engine=innodb character set latin1;
3+
4+
set optimizer_trace='enabled=on';
5+
set optimizer_trace_max_mem_size = 1000000000;
6+
set optimizer_force_index_for_range = true;
7+
8+
# Verify that we hit quick ranges memory limit with 10000000
9+
set range_optimizer_max_mem_size = 10000000;
10+
explain select * from t force index (primary) where i in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
11+
j in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
12+
k in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
13+
l in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
14+
m in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
15+
select count(*) from information_schema.optimizer_trace where trace like '%reached_tree_mem_limit%';
16+
select count(*) from information_schema.optimizer_trace where trace like '%reached_quick_ranges_mem_limit%';
17+
18+
# Verify that we hit SEL_ARGs tree memory limit with 10
19+
set range_optimizer_max_mem_size = 10;
20+
explain select * from t force index (primary) where i in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
21+
j in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
22+
k in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
23+
l in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") and
24+
m in ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
25+
select count(*) from information_schema.optimizer_trace where trace like '%reached_tree_mem_limit%';
26+
select count(*) from information_schema.optimizer_trace where trace like '%reached_quick_ranges_mem_limit%';
27+
28+
set range_optimizer_max_mem_size = DEFAULT;
29+
set optimizer_force_index_for_range = DEFAULT;
30+
set optimizer_trace_max_mem_size = DEFAULT;
31+
set optimizer_trace=DEFAULT;
32+
33+
drop table t;

‎sql/range_optimizer/internal.h‎

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ namespace opt_range {
6464
extern SEL_ARG *null_element;
6565
}
6666

67+
enum enum_range_optimizer_mem_mode { RANGE_OPT_MEM_WARN, RANGE_OPT_MEM_ERROR };
68+
6769
/**
6870
Error handling class for range optimizer. We handle only out of memory
6971
error here. This is to give a hint to the user to
@@ -74,25 +76,42 @@ extern SEL_ARG *null_element;
7476
class Range_optimizer_error_handler : public Internal_error_handler {
7577
public:
7678
Range_optimizer_error_handler()
77-
: m_has_errors(false), m_is_mem_error(false) {}
79+
: m_has_errors(false), m_is_mem_error(false), m_passthrough(false) {}
7880

7981
bool handle_condition(THD *thd, uint sql_errno, const char *,
8082
Sql_condition::enum_severity_level *level,
8183
const char *) override {
84+
// Do not handle any conditions if passthrough is set.
85+
if (m_passthrough) return false;
86+
8287
if (*level == Sql_condition::SL_ERROR) {
8388
m_has_errors = true;
84-
/* Out of memory error is reported only once. Return as handled */
85-
if (m_is_mem_error && sql_errno == EE_CAPACITY_EXCEEDED) return true;
86-
if (sql_errno == EE_CAPACITY_EXCEEDED) {
87-
m_is_mem_error = true;
88-
/* Convert the error into a warning. */
89-
*level = Sql_condition::SL_WARNING;
90-
push_warning_printf(
91-
thd, Sql_condition::SL_WARNING, ER_CAPACITY_EXCEEDED,
92-
ER_THD(thd, ER_CAPACITY_EXCEEDED),
93-
(ulonglong)thd->variables.range_optimizer_max_mem_size,
94-
"range_optimizer_max_mem_size",
95-
ER_THD(thd, ER_CAPACITY_EXCEEDED_IN_RANGE_OPTIMIZER));
89+
if (thd->variables.range_optimizer_fail_mode == RANGE_OPT_MEM_WARN) {
90+
/* Out of memory error is reported only once. Return as handled */
91+
if (m_is_mem_error && sql_errno == EE_CAPACITY_EXCEEDED) return true;
92+
if (sql_errno == EE_CAPACITY_EXCEEDED) {
93+
m_is_mem_error = true;
94+
/* Convert the error into a warning. */
95+
*level = Sql_condition::SL_WARNING;
96+
push_warning_printf(
97+
thd, Sql_condition::SL_WARNING, ER_CAPACITY_EXCEEDED,
98+
ER_THD(thd, ER_CAPACITY_EXCEEDED),
99+
(ulonglong)thd->variables.range_optimizer_max_mem_size,
100+
"range_optimizer_max_mem_size",
101+
ER_THD(thd, ER_CAPACITY_EXCEEDED_IN_RANGE_OPTIMIZER));
102+
return true;
103+
}
104+
} else {
105+
// Call my_error with the correct range_optimizer_max_mem_size value.
106+
// The default error message will contain the MEMROOT limit, which may
107+
// be different from range_optimizer_max_mem_size in certain cases. To
108+
// avoid infinite recursion (since we are calling my_error in the error
109+
// handler), set m_passthrough to true.
110+
m_passthrough = true;
111+
my_error(EE_CAPACITY_EXCEEDED, MYF(0),
112+
static_cast<ulonglong>(
113+
thd->variables.range_optimizer_max_mem_size));
114+
m_passthrough = false;
96115
return true;
97116
}
98117
}
@@ -104,6 +123,7 @@ class Range_optimizer_error_handler : public Internal_error_handler {
104123
private:
105124
bool m_has_errors;
106125
bool m_is_mem_error;
126+
bool m_passthrough;
107127
};
108128

109129
int index_next_different(bool is_index_scan, handler *file,

0 commit comments

Comments
 (0)