Commit 95c286ce authored by Sergei Petrunia's avatar Sergei Petrunia

MDEV-10324: Server crash in get_sel_arg_for_keypart or Assertion

The crash was caused by this problem:
get_best_group_min_max() tries to construct query plans for keys that
are not processed by the range optimizer. This wasn't a problem as long
as SEL_TREE::keys was an array of MAX_KEY elements.
However, now it is a Mem_root_array and only has elements for the used
keys, and get_best_group_min_max attempts to address beyond the end of
the array.

The obvious way to fix the crash was to port (and improve) a part of
96fcfcbd7b5120e8f64fd45985001eca8d36fbfb from mysql-5.7. This makes
get_best_group_min_max not to consider indexes that Mem_root_arrays
have no element for.

After that, I got non-sensical query plans (see MDEV-10325 for details).
Fixed that by making get_best_group_min_max to check if the index is in
table->keys_in_use_for_group_by bitmap.
parent d1b25890
...@@ -2692,3 +2692,37 @@ select distinct a from t1 group by 'a'; ...@@ -2692,3 +2692,37 @@ select distinct a from t1 group by 'a';
a a
2001-02-02 2001-02-02
drop table t1; drop table t1;
#
# MDEV-10324: Server crash in get_sel_arg_for_keypart or Assertion `n < size()' failed in Mem_root_array
#
CREATE TABLE t1 (
job_id int(10) unsigned NOT NULL AUTO_INCREMENT,
job_cmd varbinary(60) NOT NULL DEFAULT '',
job_namespace int(11) NOT NULL,
job_title varbinary(255) NOT NULL,
job_params blob NOT NULL,
job_timestamp varbinary(14) DEFAULT NULL,
job_random int(10) unsigned NOT NULL DEFAULT '0',
job_token varbinary(32) NOT NULL DEFAULT '',
job_token_timestamp varbinary(14) DEFAULT NULL,
job_sha1 varbinary(32) NOT NULL DEFAULT '',
job_attempts int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (job_id),
KEY job_cmd (job_cmd,job_namespace,job_title,job_params(128)),
KEY job_timestamp (job_timestamp),
KEY job_sha1 (job_sha1),
KEY job_cmd_token (job_cmd,job_token,job_random),
KEY job_cmd_token_id (job_cmd,job_token,job_id)
);
INSERT INTO t1 VALUES
(NULL, 'foo', 1, 'foo', 'foo', 'foo', 1, 'foo', 'foo', 'foo', 1),
(NULL, 'bar', 2, 'bar', 'bar', 'bar', 2, 'bar', 'bar', 'bar', 2);
SELECT DISTINCT job_cmd FROM t1 WHERE job_cmd IN ('foobar','null');
job_cmd
drop table t1;
CREATE TABLE t1 (f1 INT NOT NULL, f2 VARCHAR(3) NOT NULL, KEY(f1), KEY(f2, f1));
INSERT INTO t1 VALUES (0,'foo'),(1,'bar');
SELECT 1 IN ( SELECT COUNT( DISTINCT f2 ) FROM t1 WHERE f1 <= 4 );
1 IN ( SELECT COUNT( DISTINCT f2 ) FROM t1 WHERE f1 <= 4 )
0
drop table t1;
...@@ -123,4 +123,39 @@ id xtext optionen ...@@ -123,4 +123,39 @@ id xtext optionen
2 number 22,25 2 number 22,25
1 select Kabel mit Stecker 5-polig,Kabel ohne Stecker 1 select Kabel mit Stecker 5-polig,Kabel ohne Stecker
DROP TABLE t1, t2; DROP TABLE t1, t2;
# Port of testcase:
#
# Bug#20819199 ASSERTION FAILED IN TEST_IF_SKIP_SORT_ORDER
#
CREATE TABLE t0 ( a INT );
INSERT INTO t0 VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);
CREATE TABLE t1 (
pk INT NOT NULL AUTO_INCREMENT,
a INT,
b INT,
PRIMARY KEY (pk),
KEY idx1 (a),
KEY idx2 (b, a),
KEY idx3 (a, b)
) ENGINE = InnoDB;
INSERT INTO t1 (a, b) SELECT t01.a, t02.a FROM t0 t01, t0 t02;
ANALYZE TABLE t1;
Table Op Msg_type Msg_text
test.t1 analyze status OK
EXPLAIN SELECT DISTINCT a, MAX(b) FROM t1 WHERE a >= 0 GROUP BY a,a;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range idx1,idx3 idx3 5 NULL 100 Using where; Using index
SELECT DISTINCT a, MAX(b) FROM t1 WHERE a >= 0 GROUP BY a,a;
a MAX(b)
1 10
2 10
3 10
4 10
5 10
6 10
7 10
8 10
9 10
10 10
DROP TABLE t0, t1;
# End of tests # End of tests
...@@ -1163,7 +1163,7 @@ INSERT INTO t1 SELECT a +32, b +32 FROM t1; ...@@ -1163,7 +1163,7 @@ INSERT INTO t1 SELECT a +32, b +32 FROM t1;
INSERT INTO t1 SELECT a +64, b +64 FROM t1; INSERT INTO t1 SELECT a +64, b +64 FROM t1;
EXPLAIN SELECT a FROM t1 IGNORE INDEX FOR GROUP BY (a, ab) GROUP BY a; EXPLAIN SELECT a FROM t1 IGNORE INDEX FOR GROUP BY (a, ab) GROUP BY a;
id select_type table type possible_keys key key_len ref rows Extra id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range NULL ab 4 NULL 10 Using index for group-by 1 SIMPLE t1 index NULL PRIMARY 4 NULL 128 Using index
SELECT a FROM t1 IGNORE INDEX FOR GROUP BY (a, ab) GROUP BY a; SELECT a FROM t1 IGNORE INDEX FOR GROUP BY (a, ab) GROUP BY a;
a a
1 1
......
...@@ -1803,3 +1803,39 @@ select distinct a from t1 group by 'a'; ...@@ -1803,3 +1803,39 @@ select distinct a from t1 group by 'a';
insert into t1 values("2001-02-02"),("2001-02-03"); insert into t1 values("2001-02-02"),("2001-02-03");
select distinct a from t1 group by 'a'; select distinct a from t1 group by 'a';
drop table t1; drop table t1;
--echo #
--echo # MDEV-10324: Server crash in get_sel_arg_for_keypart or Assertion `n < size()' failed in Mem_root_array
--echo #
CREATE TABLE t1 (
job_id int(10) unsigned NOT NULL AUTO_INCREMENT,
job_cmd varbinary(60) NOT NULL DEFAULT '',
job_namespace int(11) NOT NULL,
job_title varbinary(255) NOT NULL,
job_params blob NOT NULL,
job_timestamp varbinary(14) DEFAULT NULL,
job_random int(10) unsigned NOT NULL DEFAULT '0',
job_token varbinary(32) NOT NULL DEFAULT '',
job_token_timestamp varbinary(14) DEFAULT NULL,
job_sha1 varbinary(32) NOT NULL DEFAULT '',
job_attempts int(10) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (job_id),
KEY job_cmd (job_cmd,job_namespace,job_title,job_params(128)),
KEY job_timestamp (job_timestamp),
KEY job_sha1 (job_sha1),
KEY job_cmd_token (job_cmd,job_token,job_random),
KEY job_cmd_token_id (job_cmd,job_token,job_id)
);
INSERT INTO t1 VALUES
(NULL, 'foo', 1, 'foo', 'foo', 'foo', 1, 'foo', 'foo', 'foo', 1),
(NULL, 'bar', 2, 'bar', 'bar', 'bar', 2, 'bar', 'bar', 'bar', 2);
SELECT DISTINCT job_cmd FROM t1 WHERE job_cmd IN ('foobar','null');
drop table t1;
CREATE TABLE t1 (f1 INT NOT NULL, f2 VARCHAR(3) NOT NULL, KEY(f1), KEY(f2, f1));
INSERT INTO t1 VALUES (0,'foo'),(1,'bar');
SELECT 1 IN ( SELECT COUNT( DISTINCT f2 ) FROM t1 WHERE f1 <= 4 );
drop table t1;
...@@ -125,4 +125,34 @@ ORDER BY id DESC; ...@@ -125,4 +125,34 @@ ORDER BY id DESC;
DROP TABLE t1, t2; DROP TABLE t1, t2;
--echo # Port of testcase:
--echo #
--echo # Bug#20819199 ASSERTION FAILED IN TEST_IF_SKIP_SORT_ORDER
--echo #
CREATE TABLE t0 ( a INT );
INSERT INTO t0 VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);
CREATE TABLE t1 (
pk INT NOT NULL AUTO_INCREMENT,
a INT,
b INT,
PRIMARY KEY (pk),
KEY idx1 (a),
KEY idx2 (b, a),
KEY idx3 (a, b)
) ENGINE = InnoDB;
INSERT INTO t1 (a, b) SELECT t01.a, t02.a FROM t0 t01, t0 t02;
ANALYZE TABLE t1;
let $query=
SELECT DISTINCT a, MAX(b) FROM t1 WHERE a >= 0 GROUP BY a,a;
eval EXPLAIN $query;
eval $query;
DROP TABLE t0, t1;
--echo # End of tests --echo # End of tests
...@@ -11902,8 +11902,6 @@ void QUICK_ROR_UNION_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set) ...@@ -11902,8 +11902,6 @@ void QUICK_ROR_UNION_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set)
*******************************************************************************/ *******************************************************************************/
static inline uint get_field_keypart(KEY *index, Field *field); static inline uint get_field_keypart(KEY *index, Field *field);
static inline SEL_ARG * get_index_range_tree(uint index, SEL_TREE* range_tree,
PARAM *param, uint *param_idx);
static bool get_sel_arg_for_keypart(Field *field, SEL_ARG *index_range_tree, static bool get_sel_arg_for_keypart(Field *field, SEL_ARG *index_range_tree,
SEL_ARG **cur_range); SEL_ARG **cur_range);
static bool get_constant_key_infix(KEY *index_info, SEL_ARG *index_range_tree, static bool get_constant_key_infix(KEY *index_info, SEL_ARG *index_range_tree,
...@@ -12180,8 +12178,6 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) ...@@ -12180,8 +12178,6 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time)
(GA1,GA2) are all TRUE. If there is more than one such index, select the (GA1,GA2) are all TRUE. If there is more than one such index, select the
first one. Here we set the variables: group_prefix_len and index_info. first one. Here we set the variables: group_prefix_len and index_info.
*/ */
KEY *cur_index_info= table->key_info;
KEY *cur_index_info_end= cur_index_info + table->s->keys;
/* Cost-related variables for the best index so far. */ /* Cost-related variables for the best index so far. */
double best_read_cost= DBL_MAX; double best_read_cost= DBL_MAX;
ha_rows best_records= 0; ha_rows best_records= 0;
...@@ -12193,11 +12189,12 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) ...@@ -12193,11 +12189,12 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time)
uint max_key_part; uint max_key_part;
SEL_ARG *cur_index_tree= NULL; SEL_ARG *cur_index_tree= NULL;
ha_rows cur_quick_prefix_records= 0; ha_rows cur_quick_prefix_records= 0;
uint cur_param_idx=MAX_KEY;
for (uint cur_index= 0 ; cur_index_info != cur_index_info_end ; // We go through allowed indexes
cur_index_info++, cur_index++) for (uint cur_param_idx= 0; cur_param_idx < param->keys ; ++cur_param_idx)
{ {
const uint cur_index= param->real_keynr[cur_param_idx];
KEY *const cur_index_info= &table->key_info[cur_index];
KEY_PART_INFO *cur_part; KEY_PART_INFO *cur_part;
KEY_PART_INFO *end_part; /* Last part for loops. */ KEY_PART_INFO *end_part; /* Last part for loops. */
/* Last index part. */ /* Last index part. */
...@@ -12220,7 +12217,8 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) ...@@ -12220,7 +12217,8 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time)
(was also: "Exclude UNIQUE indexes ..." but this was removed because (was also: "Exclude UNIQUE indexes ..." but this was removed because
there are cases Loose Scan over a multi-part index is useful). there are cases Loose Scan over a multi-part index is useful).
*/ */
if (!table->covering_keys.is_set(cur_index)) if (!table->covering_keys.is_set(cur_index) ||
!table->keys_in_use_for_group_by.is_set(cur_index))
continue; continue;
/* /*
...@@ -12399,9 +12397,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) ...@@ -12399,9 +12397,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time)
{ {
if (tree) if (tree)
{ {
uint dummy; SEL_ARG *index_range_tree= tree->keys[cur_param_idx];
SEL_ARG *index_range_tree= get_index_range_tree(cur_index, tree, param,
&dummy);
if (!get_constant_key_infix(cur_index_info, index_range_tree, if (!get_constant_key_infix(cur_index_info, index_range_tree,
first_non_group_part, min_max_arg_part, first_non_group_part, min_max_arg_part,
last_part, thd, cur_key_infix, last_part, thd, cur_key_infix,
...@@ -12465,9 +12461,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) ...@@ -12465,9 +12461,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time)
*/ */
if (tree && min_max_arg_item) if (tree && min_max_arg_item)
{ {
uint dummy; SEL_ARG *index_range_tree= tree->keys[cur_param_idx];
SEL_ARG *index_range_tree= get_index_range_tree(cur_index, tree, param,
&dummy);
SEL_ARG *cur_range= NULL; SEL_ARG *cur_range= NULL;
if (get_sel_arg_for_keypart(min_max_arg_part->field, if (get_sel_arg_for_keypart(min_max_arg_part->field,
index_range_tree, &cur_range) || index_range_tree, &cur_range) ||
...@@ -12485,9 +12479,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time) ...@@ -12485,9 +12479,7 @@ get_best_group_min_max(PARAM *param, SEL_TREE *tree, double read_time)
/* Compute the cost of using this index. */ /* Compute the cost of using this index. */
if (tree) if (tree)
{ {
/* Find the SEL_ARG sub-tree that corresponds to the chosen index. */ cur_index_tree= tree->keys[cur_param_idx];
cur_index_tree= get_index_range_tree(cur_index, tree, param,
&cur_param_idx);
/* Check if this range tree can be used for prefix retrieval. */ /* Check if this range tree can be used for prefix retrieval. */
Cost_estimate dummy_cost; Cost_estimate dummy_cost;
uint mrr_flags= HA_MRR_USE_DEFAULT_IMPL; uint mrr_flags= HA_MRR_USE_DEFAULT_IMPL;
...@@ -13020,44 +13012,6 @@ get_field_keypart(KEY *index, Field *field) ...@@ -13020,44 +13012,6 @@ get_field_keypart(KEY *index, Field *field)
} }
/*
Find the SEL_ARG sub-tree that corresponds to the chosen index.
SYNOPSIS
get_index_range_tree()
index [in] The ID of the index being looked for
range_tree[in] Tree of ranges being searched
param [in] PARAM from SQL_SELECT::test_quick_select
param_idx [out] Index in the array PARAM::key that corresponds to 'index'
DESCRIPTION
A SEL_TREE contains range trees for all usable indexes. This procedure
finds the SEL_ARG sub-tree for 'index'. The members of a SEL_TREE are
ordered in the same way as the members of PARAM::key, thus we first find
the corresponding index in the array PARAM::key. This index is returned
through the variable param_idx, to be used later as argument of
check_quick_select().
RETURN
Pointer to the SEL_ARG subtree that corresponds to index.
*/
SEL_ARG * get_index_range_tree(uint index, SEL_TREE* range_tree, PARAM *param,
uint *param_idx)
{
uint idx= 0; /* Index nr in param->key_parts */
while (idx < param->keys)
{
if (index == param->real_keynr[idx])
break;
idx++;
}
*param_idx= idx;
return(range_tree->keys[idx]);
}
/* /*
Compute the cost of a quick_group_min_max_select for a particular index. Compute the cost of a quick_group_min_max_select for a particular index.
......
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