From: Jorgen Loland Date: March 28 2011 8:45am Subject: bzr commit into mysql-5.5 branch (jorgen.loland:3404) Bug#11882131 List-Archive: http://lists.mysql.com/commits/133983 X-Bug: 11882131 Message-Id: <20110328084509.414618C4@atum21.norway.sun.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="===============2544392593109652164==" --===============2544392593109652164== 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-5.5-11882131/ based on revid:alfranio.correia@stripped 3404 Jorgen Loland 2011-03-28 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 is ensure 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.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.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.result' --- a/mysql-test/r/order_by.result 2011-02-07 09:40:42 +0000 +++ b/mysql-test/r/order_by.result 2011-03-28 08:45:06 +0000 @@ -1651,7 +1651,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-16 14:11:20 +0000 +++ b/sql/item_sum.cc 2011-03-28 08:45:06 +0000 @@ -2894,7 +2894,7 @@ int group_concat_key_cmp_with_order(void 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)->order == ORDER::ORDER_ASC) ? res : -res; } } /* @@ -3435,7 +3435,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]->order == 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 2010-12-29 00:26:31 +0000 +++ b/sql/opt_range.cc 2011-03-28 08:45:06 +0000 @@ -695,6 +695,8 @@ public: /* Number of ranges in the last checked tree->key */ uint n_ranges; uint8 first_null_comp; /* first null component if any, 0 - otherwise */ + + ORDER::enum_order order; }; class TABLE_READ_PLAN; @@ -2147,8 +2149,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) + table_map prev_tables, + ha_rows limit, bool force_quick_range, + const ORDER::enum_order &interesting_order) { uint idx; double scan_time; @@ -2204,6 +2207,7 @@ 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.order= interesting_order; thd->no_errors=1; // Don't warn about NULL init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0); @@ -2328,10 +2332,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) && - optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE)) + optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE) && + interesting_order != ORDER::ORDER_DESC) { /* Get best non-covering ROR-intersection plan and prepare data for @@ -2355,7 +2361,9 @@ int SQL_SELECT::test_quick_select(THD *t } else { - if (optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE)) + // Cannot return rows in descending order. + if (optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE) && + interesting_order != ORDER::ORDER_DESC) { /* Try creating index_merge/ROR-union scan. */ SEL_IMERGE *imerge; @@ -4605,6 +4613,9 @@ TRP_ROR_INTERSECT *get_best_ror_intersec !optimizer_flag(param->thd, OPTIMIZER_SWITCH_INDEX_MERGE_INTERSECT)) DBUG_RETURN(NULL); + if (param->order == 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. @@ -9434,6 +9445,8 @@ 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); + if (param->order == ORDER::ORDER_DESC) /* Cannot do reverse ordering */ + 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 2010-12-17 12:52:39 +0000 +++ b/sql/opt_range.h 2011-03-28 08:45:06 +0000 @@ -278,6 +278,7 @@ public: virtual void range_end() {} virtual bool reverse_sorted() = 0; + virtual bool reverse_sort_possible() = 0; virtual bool unique_key_range() { return false; } virtual bool clustered_pk_range() { return false; } @@ -439,7 +440,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() { return true; } bool unique_key_range(); int init_ror_merged_scan(bool reuse_handler); void save_last_pos() @@ -537,6 +539,7 @@ public: int reset(void); int get_next(); bool reverse_sorted() { return false; } + bool reverse_sort_possible() { 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); @@ -614,6 +617,7 @@ public: int reset(void); int get_next(); bool reverse_sorted() { return false; } + bool reverse_sort_possible() { 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); @@ -684,6 +688,7 @@ public: int reset(void); int get_next(); bool reverse_sorted() { return false; } + bool reverse_sort_possible() { 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); @@ -825,6 +830,7 @@ public: int reset(); int get_next(); bool reverse_sorted() { return false; } + bool reverse_sort_possible() { 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); @@ -845,7 +851,8 @@ class QUICK_SELECT_DESC: public QUICK_RA public: QUICK_SELECT_DESC(QUICK_RANGE_SELECT *q, uint used_key_parts); int get_next(); - bool reverse_sorted() { return 1; } + bool reverse_sorted() { return true; } + bool reverse_sort_possible() { return true; } int get_type() { return QS_TYPE_RANGE_DESC; } QUICK_SELECT_I *make_reverse(uint used_key_parts_arg) { @@ -881,7 +888,8 @@ class SQL_SELECT :public Sql_alloc { { key_map tmp; tmp.set_all(); - return test_quick_select(thd, tmp, 0, limit, force_quick_range) < 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) { @@ -889,7 +897,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); + 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-08 17:39:25 +0000 +++ b/sql/sql_lex.cc 2011-03-28 08:45:06 +0000 @@ -2178,7 +2178,7 @@ void st_select_lex::print_order(String * } else (*order->item)->print(str, query_type); - if (!order->asc) + if (order->order == 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-08 17:39:25 +0000 +++ b/sql/sql_parse.cc 2011-03-28 08:45:06 +0000 @@ -5688,7 +5688,7 @@ bool add_to_list(THD *thd, SQL_I_Listitem_ptr= item; order->item= &order->item_ptr; - order->asc = asc; + order->order = 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-16 14:11:20 +0000 +++ b/sql/sql_select.cc 2011-03-28 08:45:06 +0000 @@ -2607,8 +2607,9 @@ 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)) == 1) + error= select->test_quick_select(thd, *(key_map *)keys,(table_map) 0, + limit, 0, ORDER::ORDER_NOT_RELEVANT); + if (error == 1) DBUG_RETURN(select->quick->records); if (error == -1) { @@ -6580,12 +6581,13 @@ make_join_select(JOIN *join,SQL_SELECT * 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) < 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), + 0, ORDER::ORDER_NOT_RELEVANT) < 0) { /* Before reporting "Impossible WHERE" for the whole query @@ -6598,7 +6600,9 @@ make_join_select(JOIN *join,SQL_SELECT * (join->select_options & OPTION_FOUND_ROWS ? HA_POS_ERROR : - join->unit->select_limit_cnt),0) < 0) + join->unit->select_limit_cnt), + 0, ORDER::ORDER_NOT_RELEVANT) + < 0) DBUG_RETURN(1); // Impossible WHERE } else @@ -12436,7 +12440,8 @@ 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); + (table_map) 0, HA_POS_ERROR, 0, + ORDER::ORDER_NOT_RELEVANT); } @@ -13298,7 +13303,8 @@ static int test_if_order_by_key(ORDER *o DBUG_RETURN(0); /* 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)) ? + bool asc_order= (order->order == ORDER::ORDER_ASC); + flag= ((asc_order == !(key_part->key_part_flag & HA_REVERSE_SORT)) ? 1 : -1); if (reverse && flag != reverse) DBUG_RETURN(0); @@ -13739,8 +13745,8 @@ test_if_skip_sort_order(JOIN_TAB *tab,OR (tab->join->select_options & OPTION_FOUND_ROWS) ? HA_POS_ERROR : - tab->join->unit->select_limit_cnt,0) <= - 0) + tab->join->unit->select_limit_cnt, 0, + order->order) <= 0) goto use_filesort; } ref_key= new_ref_key; @@ -13789,7 +13795,7 @@ test_if_skip_sort_order(JOIN_TAB *tab,OR join->select_options & OPTION_FOUND_ROWS ? HA_POS_ERROR : join->unit->select_limit_cnt, - 0); + 0, order->order); } order_direction= best_key_direction; /* @@ -13818,18 +13824,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 - } - } + + bool quick_created= (select->quick != save_quick); + /* + test_quick_select() should not create a quick that cannot do + reverse ordering + */ + DBUG_ASSERT(!quick_created || select->quick->reverse_sort_possible()); } } @@ -13929,7 +13930,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: @@ -14425,7 +14426,7 @@ SORT_FIELD *make_unireg_sortorder(ORDER } else pos->item= *order->item; - pos->reverse=! order->asc; + pos->reverse= (order->order == ORDER::ORDER_DESC); } *length=count; DBUG_RETURN(sort); @@ -15162,7 +15163,7 @@ create_distinct_group(THD *thd, Item **r */ ord->item= ref_pointer_array; } - ord->asc=1; + ord->order= ORDER::ORDER_ASC; *prev=ord; prev= &ord->next; } @@ -15239,7 +15240,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->order= b->order; else return 0; } === modified file 'sql/sql_update.cc' --- a/sql/sql_update.cc 2011-02-21 15:49:03 +0000 +++ b/sql/sql_update.cc 2011-03-28 08:45:06 +0000 @@ -1731,7 +1731,7 @@ loop_end: /* Make an unique key over the first field to avoid duplicated updates */ bzero((char*) &group, sizeof(group)); - group.asc= 1; + group.order= 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-25 09:06:07 +0000 +++ b/sql/table.h 2011-03-28 08:45:06 +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 order; /* 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 */ --===============2544392593109652164== 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\ # 64iqa40zsmr5ea7m # target_branch: file:///export/home/jl208045/mysql/mysql-5.5-\ # 11882131/ # testament_sha1: e9aa751e0a51e8f71cc7d7e6e99210b313236382 # timestamp: 2011-03-28 10:45:09 +0200 # base_revision_id: alfranio.correia@stripped\ # s1jvs2bl5nxe65r5 # # Begin bundle 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 oSGPmVgC --===============2544392593109652164==--