From 07c0b1d8d075ee5c95ceca98c88d914fff10a8ee Mon Sep 17 00:00:00 2001 From: Sergey Petrunya Date: Tue, 22 Jul 2014 15:52:49 +0400 Subject: [PATCH] MDEV-6434: Wrong result (extra rows) with ORDER BY, multiple-column index, InnoDB - Filesort has an optmization where it reads only columns that are needed before the sorting is done. - When ref(_or_null) is picked by the join optimizer, it may remove parts of WHERE clause that are guaranteed to be true. - However, if we use quick select, we must put all of the range columns into the read set. Not doing so will may cause us to fail to detect the end of the range. --- mysql-test/r/order_by_innodb.result | 13 +++++++ mysql-test/t/order_by_innodb.test | 23 ++++++++++++ sql/filesort.cc | 3 ++ sql/opt_range.cc | 57 ++++++++++++++++++++++++++++- sql/opt_range.h | 15 ++++++++ 5 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 mysql-test/r/order_by_innodb.result create mode 100644 mysql-test/t/order_by_innodb.test diff --git a/mysql-test/r/order_by_innodb.result b/mysql-test/r/order_by_innodb.result new file mode 100644 index 00000000000..3c6c4053741 --- /dev/null +++ b/mysql-test/r/order_by_innodb.result @@ -0,0 +1,13 @@ +drop table if exists t0,t1,t2,t3; +# +# MDEV-6434: Wrong result (extra rows) with ORDER BY, multiple-column index, InnoDB +# +CREATE TABLE t1 (a INT, b INT, c INT, d TEXT, KEY idx(a,b,c)) ENGINE=InnoDB; +INSERT INTO t1 (a,c) VALUES +(8, 9),(8, 10),(13, 15),(16, 17),(16, 18),(16, 19),(20, 21), +(20, 22),(20, 24),(20, 25),(20, 26),(20, 27),(20, 28); +SELECT * FROM t1 WHERE a = 8 AND (b = 1 OR b IS NULL) ORDER BY c; +a b c d +8 NULL 9 NULL +8 NULL 10 NULL +DROP TABLE t1; diff --git a/mysql-test/t/order_by_innodb.test b/mysql-test/t/order_by_innodb.test new file mode 100644 index 00000000000..c20eaceb053 --- /dev/null +++ b/mysql-test/t/order_by_innodb.test @@ -0,0 +1,23 @@ +# +# ORDER BY handling (e.g. filesort) tests that require innodb +# +-- source include/have_innodb.inc + +--disable_warnings +drop table if exists t0,t1,t2,t3; +--enable_warnings + +--echo # +--echo # MDEV-6434: Wrong result (extra rows) with ORDER BY, multiple-column index, InnoDB +--echo # + +CREATE TABLE t1 (a INT, b INT, c INT, d TEXT, KEY idx(a,b,c)) ENGINE=InnoDB; + +INSERT INTO t1 (a,c) VALUES +(8, 9),(8, 10),(13, 15),(16, 17),(16, 18),(16, 19),(20, 21), +(20, 22),(20, 24),(20, 25),(20, 26),(20, 27),(20, 28); + +SELECT * FROM t1 WHERE a = 8 AND (b = 1 OR b IS NULL) ORDER BY c; + +DROP TABLE t1; + diff --git a/sql/filesort.cc b/sql/filesort.cc index 34cd11cbcf3..d6cb22ebe2c 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -642,6 +642,9 @@ static ha_rows find_all_keys(SORTPARAM *param, SQL_SELECT *select, /* Temporary set for register_used_fields and register_field_in_read_map */ sort_form->read_set= &sort_form->tmp_set; register_used_fields(param); + if (quick_select) + select->quick->add_used_key_part_to_set(sort_form->read_set); + Item *sort_cond= !select ? 0 : !select->pre_idx_push_select_cond ? select->cond : select->pre_idx_push_select_cond; diff --git a/sql/opt_range.cc b/sql/opt_range.cc index 5db080394eb..80807c01fd0 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -10538,6 +10538,7 @@ QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, if (quick->init()) goto err; quick->records= records; + quick->max_used_key_length= ref->key_parts; if ((cp_buffer_from_ref(thd, table, ref) && thd->is_fatal_error) || !(range= new(alloc) QUICK_RANGE())) @@ -10547,7 +10548,7 @@ QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, range->min_length= range->max_length= ref->key_length; range->min_keypart_map= range->max_keypart_map= make_prev_keypart_map(ref->key_parts); - range->flag= (ref->key_length == key_info->key_length ? EQ_RANGE : 0); + range->flag= EQ_RANGE; if (!(quick->key_parts=key_part=(KEY_PART *) alloc_root(&quick->alloc,sizeof(KEY_PART)*ref->key_parts))) @@ -11756,6 +11757,60 @@ void QUICK_ROR_UNION_SELECT::add_keys_and_lengths(String *key_names, } +void QUICK_RANGE_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set) +{ + for (uint i=0; i < max_used_key_length; i++) + { + bitmap_set_bit(col_set, key_parts[i].field->field_index); + } +} + + +void QUICK_GROUP_MIN_MAX_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set) +{ + for (uint i=0; i < max_used_key_length; i++) + { + bitmap_set_bit(col_set, index_info->key_part[i].field->field_index); + } +} + + +void QUICK_ROR_INTERSECT_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set) +{ + List_iterator_fast it(quick_selects); + QUICK_SELECT_WITH_RECORD *quick; + while ((quick= it++)) + { + quick->quick->add_used_key_part_to_set(col_set); + } +} + + +void QUICK_INDEX_SORT_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set) +{ + QUICK_RANGE_SELECT *quick; + List_iterator_fast it(quick_selects); + while ((quick= it++)) + { + quick->add_used_key_part_to_set(col_set); + } + if (pk_quick_select) + pk_quick_select->add_used_key_part_to_set(col_set); +} + + +void QUICK_ROR_UNION_SELECT::add_used_key_part_to_set(MY_BITMAP *col_set) +{ + QUICK_SELECT_I *quick; + List_iterator_fast it(quick_selects); + + while ((quick= it++)) + { + quick->add_used_key_part_to_set(col_set); + } +} + + /******************************************************************************* * Implementation of QUICK_GROUP_MIN_MAX_SELECT *******************************************************************************/ diff --git a/sql/opt_range.h b/sql/opt_range.h index fff6e414ad9..b8b46ae5ab1 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -394,6 +394,13 @@ public: Returns a QUICK_SELECT with reverse order of to the index. */ virtual QUICK_SELECT_I *make_reverse(uint used_key_parts_arg) { return NULL; } + + /* + Add the key columns used by the quick select into table's read set. + + This is used by an optimization in filesort. + */ + virtual void add_used_key_part_to_set(MY_BITMAP *col_set)=0; }; @@ -484,6 +491,9 @@ public: #endif virtual void replace_handler(handler *new_file) { file= new_file; } QUICK_SELECT_I *make_reverse(uint used_key_parts_arg); + + virtual void add_used_key_part_to_set(MY_BITMAP *col_set); + private: /* Default copy ctor used by QUICK_SELECT_DESC */ friend class TRP_ROR_INTERSECT; @@ -644,6 +654,8 @@ public: virtual int read_keys_and_merge()= 0; /* used to get rows collected in Unique */ READ_RECORD read_record; + + virtual void add_used_key_part_to_set(MY_BITMAP *col_set); }; @@ -719,6 +731,7 @@ public: void add_keys_and_lengths(String *key_names, String *used_lengths); void add_info_string(String *str); bool is_keys_used(const MY_BITMAP *fields); + void add_used_key_part_to_set(MY_BITMAP *col_set); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif @@ -798,6 +811,7 @@ public: void add_keys_and_lengths(String *key_names, String *used_lengths); void add_info_string(String *str); bool is_keys_used(const MY_BITMAP *fields); + void add_used_key_part_to_set(MY_BITMAP *col_set); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif @@ -940,6 +954,7 @@ public: bool unique_key_range() { return false; } int get_type() { return QS_TYPE_GROUP_MIN_MAX; } void add_keys_and_lengths(String *key_names, String *used_lengths); + void add_used_key_part_to_set(MY_BITMAP *col_set); #ifndef DBUG_OFF void dbug_dump(int indent, bool verbose); #endif