From: Jorgen Loland Date: April 1 2011 12:43pm Subject: bzr commit into mysql-trunk branch (jorgen.loland:3351) Bug#11882131 List-Archive: http://lists.mysql.com/commits/134461 X-Bug: 11882131 Message-Id: <20110401124313.46F19974@atum21.norway.sun.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============0365877432192241410==" --===============0365877432192241410== 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 12:43:09 +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 12:43:09 +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 12:43:09 +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 12:43:09 +0000 @@ -742,6 +742,10 @@ public: bool is_ror_scan; /* Number of ranges in the last checked tree->key */ uint n_ranges; + + /* Order direction +*/ + ORDER::enum_order order_direction; }; class TABLE_READ_PLAN; @@ -2142,6 +2146,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 +2203,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 +2266,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 +2393,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 +2422,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 +4587,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 +9667,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 12:43:09 +0000 @@ -276,7 +276,15 @@ public: /* Range end should be called when we have looped over the whole index */ virtual void range_end() {} + /** + Whether the range access method returns records in reverse order. + */ virtual bool reverse_sorted() = 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() { 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() @@ -573,6 +582,7 @@ public: int reset(void); int get_next(); bool reverse_sorted() { 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); @@ -651,6 +661,7 @@ public: int reset(void); int get_next(); bool reverse_sorted() { 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); @@ -722,6 +733,7 @@ public: int reset(void); int get_next(); bool reverse_sorted() { 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); @@ -864,6 +876,7 @@ public: int reset(); int get_next(); bool reverse_sorted() { 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() { 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 12:43:09 +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 12:43:09 +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 12:43:09 +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 12:43:09 +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 12:43:09 +0000 @@ -194,7 +194,13 @@ typedef struct st_order { 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 */ + 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 */ --===============0365877432192241410== 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\ # 6vi7e4095p14x495 # target_branch: file:///export/home/jl208045/mysql/mysql-trunk-\ # 11882131/ # testament_sha1: 28a29f421ba3148caf723e5034c02531767f9de0 # timestamp: 2011-04-01 14:43:13 +0200 # base_revision_id: mattias.jonsson@stripped\ # q8lg4fcceeof0yrl # # Begin bundle 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 --===============0365877432192241410==--