From: Jorgen Loland Date: April 1 2011 2:04pm Subject: bzr commit into mysql-trunk branch (jorgen.loland:3351) Bug#11882131 List-Archive: http://lists.mysql.com/commits/134476 X-Bug: 11882131 Message-Id: <20110401140456.6AFA47A2@atum21.norway.sun.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============9053889988086994912==" --===============9053889988086994912== MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Content-Disposition: inline #At file:///export/home/jl208045/mysql/mysql-trunk-11882131/ based on revid:mattias.jonsson@stripped 3351 Jorgen Loland 2011-04-01 BUG#11882131: OPTIMIZER CHOOSES FILESORT WHEN REVERSE INDEX SCAN COULD BE USED Consider the following case: CREATE TABLE t1 (a INT,KEY (a)); INSERT INTO t1 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10); SELECT DISTINCT a,1 FROM t1 WHERE a <> 1 ORDER BY a DESC; This query could have been resolved by GROUP range access if it hadn't been for the descending ordering [1]. To access this table, covering index scan is first chosen. Later an attempt to avoid sorting is made by calling test_if_skip_sort_order(). Range analysis now decides that GROUP range access is the most efficient access method, but since this access method cannot produce records in descending order, it is scrapped by test_if_skip_sort_order() before concluding that filesort is required after all. In this case, test_if_skip_sort_order() fails to check if the descending ordering can be resolved by scanning the covering index in reverse order instead. Because of this, the resulting execution plan is to 1) scan the index and 2) sort the result instead of simply do 1) scan the index in reverse order. This patch adds an interesting_order parameter to test_quick_select(). This parameter ensures that only range access plans that can produce rows in requested order are considered. The gains from this change include: 1) Optimizer will not spend time to calculate whether or not an unusable range access plan is cheap. 2) Before, if two range access plans P1 and P2 were considered, and P1 could produce the requested ordering but P2 could not, P2 would still be returned from test_quick_select() if it was cheaper than P1. test_if_skip_sort_order() would then discard the range access plan as not usable. With this patch, P2 will not be considered, so test_quick_select() will instead return the best *usable* plan P1. 3) Due to #2, the aforementioned deficiency in test_if_skip_sort_order() is no longer an issue: If test_quick_select() returns a range access plan, that plan will be able to resolve the requested ordering. @ mysql-test/r/order_by_icp_mrr.result BUG#11882131: Changed test output @ mysql-test/r/order_by_none.result BUG#11882131: Changed test output @ sql/item_sum.cc Struct ORDER variable "bool asc" replaced with "enum_order order" @ sql/opt_range.cc Add parameter "interesting_order" to test_quick_select() and make the method only consider range access plans that are able to produce rows in requested order. Also add variable PARAM::order, defining whether the range access plan needs to produce ASC/DESC ordering (or no order) @ sql/opt_range.h Add method QUICK_SELECT_I::reverse_sort_possible(). @ sql/sql_lex.cc Struct ORDER variable "bool asc" replaced with "enum_order order" @ sql/sql_parse.cc Struct ORDER variable "bool asc" replaced with "enum_order order" @ sql/sql_select.cc When calling test_quick_select(): define whether ascending/descending ordering will be required. @ sql/sql_update.cc Struct ORDER variable "bool asc" replaced with "enum_order order" @ sql/table.h Struct ORDER variable "bool asc" replaced with "enum_order order" modified: mysql-test/r/order_by_icp_mrr.result mysql-test/r/order_by_none.result sql/item_sum.cc sql/opt_range.cc sql/opt_range.h sql/sql_lex.cc sql/sql_parse.cc sql/sql_select.cc sql/sql_update.cc sql/table.h === modified file 'mysql-test/r/order_by_icp_mrr.result' --- a/mysql-test/r/order_by_icp_mrr.result 2011-02-07 09:46:53 +0000 +++ b/mysql-test/r/order_by_icp_mrr.result 2011-04-01 14:04:52 +0000 @@ -2536,7 +2536,7 @@ CREATE TABLE t1 (a INT,KEY (a)); INSERT INTO t1 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10); EXPLAIN SELECT DISTINCT a,1 FROM t1 WHERE a <> 1 ORDER BY a DESC; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 index a a 5 NULL 10 Using where; Using index; Using filesort +1 SIMPLE t1 index a a 5 NULL 10 Using where; Using index SELECT DISTINCT a,1 FROM t1 WHERE a <> 1 ORDER BY a DESC; a 1 10 1 === modified file 'mysql-test/r/order_by_none.result' --- a/mysql-test/r/order_by_none.result 2011-02-07 09:46:53 +0000 +++ b/mysql-test/r/order_by_none.result 2011-04-01 14:04:52 +0000 @@ -2535,7 +2535,7 @@ CREATE TABLE t1 (a INT,KEY (a)); INSERT INTO t1 VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10); EXPLAIN SELECT DISTINCT a,1 FROM t1 WHERE a <> 1 ORDER BY a DESC; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 index a a 5 NULL 10 Using where; Using index; Using filesort +1 SIMPLE t1 index a a 5 NULL 10 Using where; Using index SELECT DISTINCT a,1 FROM t1 WHERE a <> 1 ORDER BY a DESC; a 1 10 1 === modified file 'sql/item_sum.cc' --- a/sql/item_sum.cc 2011-03-22 11:44:40 +0000 +++ b/sql/item_sum.cc 2011-04-01 14:04:52 +0000 @@ -2891,7 +2891,8 @@ int group_concat_key_cmp_with_order(cons uint offset= (field->offset(field->table->record[0]) - table->s->null_bytes); if ((res= field->cmp((uchar*)key1 + offset, (uchar*)key2 + offset))) - return (*order_item)->asc ? res : -res; + return ((*order_item)->direction == ORDER::ORDER_ASC) ? res : -res; + } } /* @@ -3432,7 +3433,7 @@ void Item_func_group_concat::print(Strin if (i) str->append(','); orig_args[i + arg_count_field]->print(str, query_type); - if (order[i]->asc) + if (order[i]->direction == ORDER::ORDER_ASC) str->append(STRING_WITH_LEN(" ASC")); else str->append(STRING_WITH_LEN(" DESC")); === modified file 'sql/opt_range.cc' --- a/sql/opt_range.cc 2011-03-22 11:44:40 +0000 +++ b/sql/opt_range.cc 2011-04-01 14:04:52 +0000 @@ -742,6 +742,12 @@ public: bool is_ror_scan; /* Number of ranges in the last checked tree->key */ uint n_ranges; + + /* + The sort order the range access method must be able + to provide. Three-value logic: asc/desc/don't care + */ + ORDER::enum_order order_direction; }; class TABLE_READ_PLAN; @@ -2142,6 +2148,8 @@ static int fill_used_fields_bitmap(PARAM limit Query limit force_quick_range Prefer to use range (instead of full table scan) even if it is more expensive. + interesting_order The sort order the range access method must be able + to provide. Three-value logic: asc/desc/don't care NOTES Updates the following in the select parameter: @@ -2197,9 +2205,9 @@ static int fill_used_fields_bitmap(PARAM */ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use, - table_map prev_tables, - ha_rows limit, bool force_quick_range, - bool ordered_output) + table_map prev_tables, + ha_rows limit, bool force_quick_range, + const ORDER::enum_order interesting_order) { uint idx; double scan_time; @@ -2260,7 +2268,8 @@ int SQL_SELECT::test_quick_select(THD *t param.imerge_cost_buff_size= 0; param.using_real_indexes= TRUE; param.remove_jump_scans= TRUE; - param.force_default_mrr= ordered_output; + param.force_default_mrr= (interesting_order != ORDER::ORDER_NOT_RELEVANT); + param.order_direction= interesting_order; thd->no_errors=1; // Don't warn about NULL init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0); @@ -2386,10 +2395,12 @@ int SQL_SELECT::test_quick_select(THD *t /* Simultaneous key scans and row deletes on several handler objects are not allowed so don't use ROR-intersection for - table deletes. + table deletes. Also, ROR-intersection cannot return rows in + descending order */ if ((thd->lex->sql_command != SQLCOM_DELETE) && - thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE)) + thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE) && + interesting_order != ORDER::ORDER_DESC) { /* Get best non-covering ROR-intersection plan and prepare data for @@ -2413,7 +2424,9 @@ int SQL_SELECT::test_quick_select(THD *t } else { - if (thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE)) + // Cannot return rows in descending order. + if (thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE) && + interesting_order != ORDER::ORDER_DESC) { /* Try creating index_merge/ROR-union scan. */ SEL_IMERGE *imerge; @@ -4576,6 +4589,9 @@ TRP_ROR_INTERSECT *get_best_ror_intersec !param->thd->optimizer_switch_flag(OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT)) DBUG_RETURN(NULL); + if (param->order_direction == ORDER::ORDER_DESC) + DBUG_RETURN(NULL); + /* Step1: Collect ROR-able SEL_ARGs and create ROR_SCAN_INFO for each of them. Also find and save clustered PK scan if there is one. @@ -9653,6 +9669,9 @@ get_best_group_min_max(PARAM *param, SEL DBUG_RETURN(NULL); if (table->s->keys == 0) /* There are no indexes to use. */ DBUG_RETURN(NULL); + /* Cannot do reverse ordering */ + if (param->order_direction == ORDER::ORDER_DESC) + DBUG_RETURN(NULL); /* Check (SA1,SA4) and store the only MIN/MAX argument - the C attribute.*/ if (join->make_sum_func_list(join->all_fields, join->fields_list, 1)) === modified file 'sql/opt_range.h' --- a/sql/opt_range.h 2011-03-22 11:44:40 +0000 +++ b/sql/opt_range.h 2011-04-01 14:04:52 +0000 @@ -276,7 +276,15 @@ public: /* Range end should be called when we have looped over the whole index */ virtual void range_end() {} - virtual bool reverse_sorted() = 0; + /** + Whether the range access method returns records in reverse order. + */ + virtual bool reverse_sorted() const = 0; + /** + Whether the range access method is capable of returning records + in reverse order. + */ + virtual bool reverse_sort_possible() const = 0; virtual bool unique_key_range() { return false; } virtual bool clustered_pk_range() { return false; } @@ -472,7 +480,8 @@ public: void range_end(); int get_next_prefix(uint prefix_length, uint group_key_parts, uchar *cur_prefix); - bool reverse_sorted() { return 0; } + bool reverse_sorted() const { return false; } + bool reverse_sort_possible() const { return true; } bool unique_key_range(); int init_ror_merged_scan(bool reuse_handler); void save_last_pos() @@ -572,7 +581,8 @@ public: void need_sorted_output(bool sort) { DBUG_ASSERT(!sort); /* Can't do it */ } int reset(void); int get_next(); - bool reverse_sorted() { return false; } + bool reverse_sorted() const { return false; } + bool reverse_sort_possible() const { return false; } bool unique_key_range() { return false; } int get_type() { return QS_TYPE_INDEX_MERGE; } void add_keys_and_lengths(String *key_names, String *used_lengths); @@ -650,7 +660,8 @@ public: void need_sorted_output(bool sort) { DBUG_ASSERT(!sort); /* Can't do it */ } int reset(void); int get_next(); - bool reverse_sorted() { return false; } + bool reverse_sorted() const { return false; } + bool reverse_sort_possible() const { return false; } bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_INTERSECT; } void add_keys_and_lengths(String *key_names, String *used_lengths); @@ -721,7 +732,8 @@ public: void need_sorted_output(bool sort) { DBUG_ASSERT(!sort); /* Can't do it */ } int reset(void); int get_next(); - bool reverse_sorted() { return false; } + bool reverse_sorted() const { return false; } + bool reverse_sort_possible() const { return false; } bool unique_key_range() { return false; } int get_type() { return QS_TYPE_ROR_UNION; } void add_keys_and_lengths(String *key_names, String *used_lengths); @@ -863,7 +875,8 @@ public: void need_sorted_output(bool sort) { /* always do it */ } int reset(); int get_next(); - bool reverse_sorted() { return false; } + bool reverse_sorted() const { return false; } + bool reverse_sort_possible() const { return false; } 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); @@ -885,7 +898,8 @@ public: QUICK_SELECT_DESC(QUICK_RANGE_SELECT *q, uint used_key_parts, bool *create_err); int get_next(); - bool reverse_sorted() { return 1; } + bool reverse_sorted() const { return true; } + bool reverse_sort_possible() const { return true; } int get_type() { return QS_TYPE_RANGE_DESC; } QUICK_SELECT_I *make_reverse(uint used_key_parts_arg) { @@ -921,7 +935,8 @@ class SQL_SELECT :public Sql_alloc { bool check_quick(THD *thd, bool force_quick_range, ha_rows limit) { key_map tmp(key_map::ALL_BITS); - return test_quick_select(thd, tmp, 0, limit, force_quick_range, FALSE) < 0; + return test_quick_select(thd, tmp, 0, limit, force_quick_range, + ORDER::ORDER_NOT_RELEVANT) < 0; } inline bool skip_record(THD *thd, bool *skip_record) { @@ -929,8 +944,8 @@ class SQL_SELECT :public Sql_alloc { return thd->is_error(); } int test_quick_select(THD *thd, key_map keys, table_map prev_tables, - ha_rows limit, bool force_quick_range, - bool ordered_output); + ha_rows limit, bool force_quick_range, + const ORDER::enum_order interesting_order); }; === modified file 'sql/sql_lex.cc' --- a/sql/sql_lex.cc 2011-03-11 09:35:38 +0000 +++ b/sql/sql_lex.cc 2011-04-01 14:04:52 +0000 @@ -2196,7 +2196,7 @@ void st_select_lex::print_order(String * } else (*order->item)->print(str, query_type); - if (!order->asc) + if (order->direction == ORDER::ORDER_DESC) str->append(STRING_WITH_LEN(" desc")); if (order->next) str->append(','); === modified file 'sql/sql_parse.cc' --- a/sql/sql_parse.cc 2011-03-17 17:39:31 +0000 +++ b/sql/sql_parse.cc 2011-04-01 14:04:52 +0000 @@ -5796,7 +5796,7 @@ bool add_to_list(THD *thd, SQL_I_Listitem_ptr= item; order->item= &order->item_ptr; - order->asc = asc; + order->direction= (asc ? ORDER::ORDER_ASC : ORDER::ORDER_DESC); order->free_me=0; order->used=0; order->counter_used= 0; === modified file 'sql/sql_select.cc' --- a/sql/sql_select.cc 2011-03-29 07:20:17 +0000 +++ b/sql/sql_select.cc 2011-04-01 14:04:52 +0000 @@ -4516,7 +4516,6 @@ static ha_rows get_quick_record_count(TH TABLE *table, const key_map *keys,ha_rows limit) { - int error; DBUG_ENTER("get_quick_record_count"); uchar buff[STACK_BUFF_ALLOC]; if (check_stack_overrun(thd, STACK_MIN_SIZE, buff)) @@ -4524,8 +4523,13 @@ static ha_rows get_quick_record_count(TH if (select) { select->head=table; - if ((error= select->test_quick_select(thd, *(key_map *)keys,(table_map) 0, - limit, 0, FALSE)) == 1) + int error= select->test_quick_select(thd, + *keys, + 0, //empty table_map + limit, + false, //don't force quick range + ORDER::ORDER_NOT_RELEVANT); + if (error == 1) DBUG_RETURN(select->quick->records); if (error == -1) { @@ -9895,13 +9899,14 @@ static bool make_join_select(JOIN *join, if (sel->cond && !sel->cond->fixed) sel->cond->quick_fix_field(); - if (sel->test_quick_select(thd, tab->keys, - used_tables & ~ current_map, - (join->select_options & - OPTION_FOUND_ROWS ? - HA_POS_ERROR : - join->unit->select_limit_cnt), 0, - FALSE) < 0) + if (sel->test_quick_select(thd, tab->keys, + used_tables & ~ current_map, + (join->select_options & + OPTION_FOUND_ROWS ? + HA_POS_ERROR : + join->unit->select_limit_cnt), + false, // don't force quick range + ORDER::ORDER_NOT_RELEVANT) < 0) { /* Before reporting "Impossible WHERE" for the whole query @@ -9914,8 +9919,9 @@ static bool make_join_select(JOIN *join, (join->select_options & OPTION_FOUND_ROWS ? HA_POS_ERROR : - join->unit->select_limit_cnt),0, - FALSE) < 0) + join->unit->select_limit_cnt), + false, //don't force quick range + ORDER::ORDER_NOT_RELEVANT) < 0) DBUG_RETURN(1); // Impossible WHERE } else @@ -18424,9 +18430,12 @@ test_if_quick_select(JOIN_TAB *tab) { delete tab->select->quick; tab->select->quick=0; - return tab->select->test_quick_select(tab->join->thd, tab->keys, - (table_map) 0, HA_POS_ERROR, 0, - FALSE); + return tab->select->test_quick_select(tab->join->thd, + tab->keys, + 0, // empty table map + HA_POS_ERROR, + false, // don't force quick range + ORDER::ORDER_NOT_RELEVANT); } @@ -19651,9 +19660,11 @@ static int test_if_order_by_key(ORDER *o if (key_part->field != field || !field->part_of_sortkey.is_set(idx)) DBUG_RETURN(0); + const ORDER::enum_order keypart_order= + (key_part->key_part_flag & HA_REVERSE_SORT) ? + ORDER::ORDER_DESC : ORDER::ORDER_ASC; /* set flag to 1 if we can use read-next on key, else to -1 */ - flag= ((order->asc == !(key_part->key_part_flag & HA_REVERSE_SORT)) ? - 1 : -1); + flag= (order->direction == keypart_order) ? 1 : -1; if (reverse && flag != reverse) DBUG_RETURN(0); reverse=flag; // Remember if reverse @@ -20110,13 +20121,15 @@ test_if_skip_sort_order(JOIN_TAB *tab,OR new_ref_key_map.set_bit(new_ref_key); // only for new_ref_key. select->quick= 0; - if (select->test_quick_select(tab->join->thd, new_ref_key_map, 0, + if (select->test_quick_select(tab->join->thd, + new_ref_key_map, + 0, // empty table_map (tab->join->select_options & OPTION_FOUND_ROWS) ? HA_POS_ERROR : - tab->join->unit->select_limit_cnt,0, - TRUE) <= - 0) + tab->join->unit->select_limit_cnt, + false, // don't force quick range + order->direction) <= 0) goto use_filesort; } ref_key= new_ref_key; @@ -20161,11 +20174,14 @@ test_if_skip_sort_order(JOIN_TAB *tab,OR key_map map; // Force the creation of quick select map.set_bit(best_key); // only best_key. select->quick= 0; - select->test_quick_select(join->thd, map, 0, + select->test_quick_select(join->thd, + map, + 0, // empty table_map join->select_options & OPTION_FOUND_ROWS ? HA_POS_ERROR : join->unit->select_limit_cnt, - TRUE, FALSE); + true, // force quick range + order->direction); } order_direction= best_key_direction; /* @@ -20195,18 +20211,13 @@ check_reverse_order: */ if (select->quick->reverse_sorted()) goto skipped_filesort; - else - { - int quick_type= select->quick->get_type(); - if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE || - quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT || - quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION || - quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX) - { - tab->limit= 0; - goto use_filesort; // Use filesort - } - } + + /* + test_quick_select() should not create a quick that cannot do + reverse ordering + */ + DBUG_ASSERT((select->quick == save_quick) || + select->quick->reverse_sort_possible()); } } @@ -20316,7 +20327,7 @@ check_reverse_order: /* Cleanup: We may have both a 'select->quick' and 'save_quick' (original) - at this point. Delete the one that we wan't use. + at this point. Delete the one that we won't use. */ skipped_filesort: @@ -20823,7 +20834,7 @@ SORT_FIELD *make_unireg_sortorder(ORDER } else pos->item= *order->item; - pos->reverse=! order->asc; + pos->reverse= (order->direction == ORDER::ORDER_DESC); DBUG_ASSERT(pos->field != NULL || pos->item != NULL); } *length=count; @@ -21336,7 +21347,7 @@ create_distinct_group(THD *thd, Item **r */ ord->item= ref_pointer_array; } - ord->asc=1; + ord->direction= ORDER::ORDER_ASC; *prev=ord; prev= &ord->next; } @@ -21413,7 +21424,7 @@ test_if_subpart(ORDER *a,ORDER *b) for (; a && b; a=a->next,b=b->next) { if ((*a->item)->eq(*b->item,1)) - a->asc=b->asc; + a->direction= b->direction; else return 0; } === modified file 'sql/sql_update.cc' --- a/sql/sql_update.cc 2011-02-25 16:41:57 +0000 +++ b/sql/sql_update.cc 2011-04-01 14:04:52 +0000 @@ -1732,7 +1732,7 @@ loop_end: /* Make an unique key over the first field to avoid duplicated updates */ bzero((char*) &group, sizeof(group)); - group.asc= 1; + group.direction= ORDER::ORDER_ASC; group.item= (Item**) temp_fields.head_ref(); tmp_param->quick_group=1; === modified file 'sql/table.h' --- a/sql/table.h 2011-03-30 11:43:32 +0000 +++ b/sql/table.h 2011-04-01 14:04:52 +0000 @@ -190,16 +190,22 @@ private: typedef struct st_order { struct st_order *next; - Item **item; /* Point at item in select fields */ - Item *item_ptr; /* Storage for initial item */ + Item **item; /* Point at item in select fields */ + Item *item_ptr; /* Storage for initial item */ int counter; /* position in SELECT list, correct - only if counter_used is true*/ - bool asc; /* true if ascending */ - bool free_me; /* true if item isn't shared */ - bool in_field_list; /* true if in select field list */ + only if counter_used is true */ + enum enum_order { + ORDER_NOT_RELEVANT, + ORDER_ASC, + ORDER_DESC + }; + + enum_order direction; /* Requested direction of ordering */ + bool free_me; /* true if item isn't shared */ + bool in_field_list; /* true if in select field list */ bool counter_used; /* parameter was counter of columns */ - Field *field; /* If tmp-table group */ - char *buff; /* If tmp-table group */ + Field *field; /* If tmp-table group */ + char *buff; /* If tmp-table group */ table_map used, depend_map; } ORDER; --===============9053889988086994912== MIME-Version: 1.0 Content-Type: text/bzr-bundle; charset="us-ascii"; name="bzr/jorgen.loland@stripped" Content-Transfer-Encoding: 7bit Content-Disposition: inline # Bazaar merge directive format 2 (Bazaar 0.90) # revision_id: jorgen.loland@stripped\ # dc3lfm7m6ii3auna # target_branch: file:///export/home/jl208045/mysql/mysql-trunk-\ # 11882131/ # testament_sha1: 47b3f4ef3190a60bd6ddb60f971df4ddbed96a83 # timestamp: 2011-04-01 16:04:56 +0200 # base_revision_id: mattias.jonsson@stripped\ # q8lg4fcceeof0yrl # # Begin bundle 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 Gtu5DUzNTneN2I/+hX/4u5IpwoSADwT5gA== --===============9053889988086994912==--