Loading mysql-test/r/update.result +59 −0 Original line number Diff line number Diff line Loading @@ -263,3 +263,62 @@ test delete from t1 where count(*)=1; ERROR HY000: Invalid use of group function drop table t1; create table t1 ( a int, index (a) ); insert into t1 values (0),(0),(0),(0),(0),(0),(0),(0); flush status; select a from t1 order by a limit 1; a 0 show status like 'handler_read%'; Variable_name Value Handler_read_first 1 Handler_read_key 0 Handler_read_next 0 Handler_read_prev 0 Handler_read_rnd 0 Handler_read_rnd_next 0 flush status; update t1 set a=unix_timestamp() order by a limit 1; show status like 'handler_read%'; Variable_name Value Handler_read_first 1 Handler_read_key 0 Handler_read_next 0 Handler_read_prev 0 Handler_read_rnd 1 Handler_read_rnd_next 0 flush status; delete from t1 order by a limit 1; show status like 'handler_read%'; Variable_name Value Handler_read_first 1 Handler_read_key 0 Handler_read_next 0 Handler_read_prev 0 Handler_read_rnd 0 Handler_read_rnd_next 0 flush status; delete from t1 order by a desc limit 1; show status like 'handler_read%'; Variable_name Value Handler_read_first 0 Handler_read_key 0 Handler_read_next 0 Handler_read_prev 0 Handler_read_rnd 1 Handler_read_rnd_next 9 alter table t1 disable keys; flush status; delete from t1 order by a limit 1; show status like 'handler_read%'; Variable_name Value Handler_read_first 0 Handler_read_key 0 Handler_read_next 0 Handler_read_prev 0 Handler_read_rnd 1 Handler_read_rnd_next 9 select count(*) from t1; count(*) 5 drop table t1; mysql-test/t/update.test +29 −0 Original line number Diff line number Diff line Loading @@ -227,4 +227,33 @@ select DATABASE(); delete from t1 where count(*)=1; drop table t1; # BUG#12915: Optimize "DELETE|UPDATE ... ORDER BY ... LIMIT n" to use an index create table t1 ( a int, index (a) ); insert into t1 values (0),(0),(0),(0),(0),(0),(0),(0); flush status; select a from t1 order by a limit 1; show status like 'handler_read%'; flush status; update t1 set a=unix_timestamp() order by a limit 1; show status like 'handler_read%'; flush status; delete from t1 order by a limit 1; show status like 'handler_read%'; flush status; delete from t1 order by a desc limit 1; show status like 'handler_read%'; alter table t1 disable keys; flush status; delete from t1 order by a limit 1; show status like 'handler_read%'; select count(*) from t1; drop table t1; # End of 4.1 tests sql/mysql_priv.h +2 −0 Original line number Diff line number Diff line Loading @@ -1105,6 +1105,8 @@ void change_byte(byte *,uint,char,char); void init_read_record(READ_RECORD *info, THD *thd, TABLE *reg_form, SQL_SELECT *select, int use_record_cache, bool print_errors); void init_read_record_idx(READ_RECORD *info, THD *thd, TABLE *table, bool print_error, uint idx); void end_read_record(READ_RECORD *info); ha_rows filesort(THD *thd, TABLE *form,struct st_sort_field *sortorder, uint s_length, SQL_SELECT *select, Loading sql/opt_range.cc +90 −0 Original line number Diff line number Diff line Loading @@ -582,6 +582,96 @@ SEL_ARG *SEL_ARG::clone_tree() return root; } /* Find the best index to retrieve first N records in given order SYNOPSIS get_index_for_order() table Table to be accessed order Required ordering limit Number of records that will be retrieved DESCRIPTION Find the best index that allows to retrieve first #limit records in the given order cheaper then one would retrieve them using full table scan. IMPLEMENTATION Run through all table indexes and find the shortest index that allows records to be retrieved in given order. We look for the shortest index as we will have fewer index pages to read with it. This function is used only by UPDATE/DELETE, so we take into account how the UPDATE/DELETE code will work: * index can only be scanned in forward direction * HA_EXTRA_KEYREAD will not be used Perhaps these assumptions could be relaxed RETURN index number MAX_KEY if no such index was found. */ uint get_index_for_order(TABLE *table, ORDER *order, ha_rows limit) { uint idx; uint match_key= MAX_KEY, match_key_len= MAX_KEY_LENGTH + 1; ORDER *ord; for (ord= order; ord; ord= ord->next) if (!ord->asc) return MAX_KEY; for (idx= 0; idx < table->keys; idx++) { if (!(table->keys_in_use_for_query.is_set(idx))) continue; KEY_PART_INFO *keyinfo= table->key_info[idx].key_part; uint partno= 0; /* The below check is sufficient considering we now have either BTREE indexes (records are returned in order for any index prefix) or HASH indexes (records are not returned in order for any index prefix). */ if (!(table->file->index_flags(idx, 0, 1) & HA_READ_ORDER)) continue; for (ord= order; ord; ord= ord->next, partno++) { Item *item= order->item[0]; if (!(item->type() == Item::FIELD_ITEM && ((Item_field*)item)->field->eq(keyinfo[partno].field))) break; } if (!ord && table->key_info[idx].key_length < match_key_len) { /* Ok, the ordering is compatible and this key is shorter then previous match (we want shorter keys as we'll have to read fewer index pages for the same number of records) */ match_key= idx; match_key_len= table->key_info[idx].key_length; } } if (match_key != MAX_KEY) { /* Found an index that allows records to be retrieved in the requested order. Now we'll check if using the index is cheaper then doing a table scan. */ double full_scan_time= table->file->scan_time(); double index_scan_time= table->file->read_time(match_key, 1, limit); if (index_scan_time > full_scan_time) match_key= MAX_KEY; } return match_key; } /* Test if a key can be used in different ranges Loading sql/opt_range.h +2 −0 Original line number Diff line number Diff line Loading @@ -167,4 +167,6 @@ class FT_SELECT: public QUICK_SELECT { QUICK_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, struct st_table_ref *ref); uint get_index_for_order(TABLE *table, ORDER *order, ha_rows limit); #endif Loading
mysql-test/r/update.result +59 −0 Original line number Diff line number Diff line Loading @@ -263,3 +263,62 @@ test delete from t1 where count(*)=1; ERROR HY000: Invalid use of group function drop table t1; create table t1 ( a int, index (a) ); insert into t1 values (0),(0),(0),(0),(0),(0),(0),(0); flush status; select a from t1 order by a limit 1; a 0 show status like 'handler_read%'; Variable_name Value Handler_read_first 1 Handler_read_key 0 Handler_read_next 0 Handler_read_prev 0 Handler_read_rnd 0 Handler_read_rnd_next 0 flush status; update t1 set a=unix_timestamp() order by a limit 1; show status like 'handler_read%'; Variable_name Value Handler_read_first 1 Handler_read_key 0 Handler_read_next 0 Handler_read_prev 0 Handler_read_rnd 1 Handler_read_rnd_next 0 flush status; delete from t1 order by a limit 1; show status like 'handler_read%'; Variable_name Value Handler_read_first 1 Handler_read_key 0 Handler_read_next 0 Handler_read_prev 0 Handler_read_rnd 0 Handler_read_rnd_next 0 flush status; delete from t1 order by a desc limit 1; show status like 'handler_read%'; Variable_name Value Handler_read_first 0 Handler_read_key 0 Handler_read_next 0 Handler_read_prev 0 Handler_read_rnd 1 Handler_read_rnd_next 9 alter table t1 disable keys; flush status; delete from t1 order by a limit 1; show status like 'handler_read%'; Variable_name Value Handler_read_first 0 Handler_read_key 0 Handler_read_next 0 Handler_read_prev 0 Handler_read_rnd 1 Handler_read_rnd_next 9 select count(*) from t1; count(*) 5 drop table t1;
mysql-test/t/update.test +29 −0 Original line number Diff line number Diff line Loading @@ -227,4 +227,33 @@ select DATABASE(); delete from t1 where count(*)=1; drop table t1; # BUG#12915: Optimize "DELETE|UPDATE ... ORDER BY ... LIMIT n" to use an index create table t1 ( a int, index (a) ); insert into t1 values (0),(0),(0),(0),(0),(0),(0),(0); flush status; select a from t1 order by a limit 1; show status like 'handler_read%'; flush status; update t1 set a=unix_timestamp() order by a limit 1; show status like 'handler_read%'; flush status; delete from t1 order by a limit 1; show status like 'handler_read%'; flush status; delete from t1 order by a desc limit 1; show status like 'handler_read%'; alter table t1 disable keys; flush status; delete from t1 order by a limit 1; show status like 'handler_read%'; select count(*) from t1; drop table t1; # End of 4.1 tests
sql/mysql_priv.h +2 −0 Original line number Diff line number Diff line Loading @@ -1105,6 +1105,8 @@ void change_byte(byte *,uint,char,char); void init_read_record(READ_RECORD *info, THD *thd, TABLE *reg_form, SQL_SELECT *select, int use_record_cache, bool print_errors); void init_read_record_idx(READ_RECORD *info, THD *thd, TABLE *table, bool print_error, uint idx); void end_read_record(READ_RECORD *info); ha_rows filesort(THD *thd, TABLE *form,struct st_sort_field *sortorder, uint s_length, SQL_SELECT *select, Loading
sql/opt_range.cc +90 −0 Original line number Diff line number Diff line Loading @@ -582,6 +582,96 @@ SEL_ARG *SEL_ARG::clone_tree() return root; } /* Find the best index to retrieve first N records in given order SYNOPSIS get_index_for_order() table Table to be accessed order Required ordering limit Number of records that will be retrieved DESCRIPTION Find the best index that allows to retrieve first #limit records in the given order cheaper then one would retrieve them using full table scan. IMPLEMENTATION Run through all table indexes and find the shortest index that allows records to be retrieved in given order. We look for the shortest index as we will have fewer index pages to read with it. This function is used only by UPDATE/DELETE, so we take into account how the UPDATE/DELETE code will work: * index can only be scanned in forward direction * HA_EXTRA_KEYREAD will not be used Perhaps these assumptions could be relaxed RETURN index number MAX_KEY if no such index was found. */ uint get_index_for_order(TABLE *table, ORDER *order, ha_rows limit) { uint idx; uint match_key= MAX_KEY, match_key_len= MAX_KEY_LENGTH + 1; ORDER *ord; for (ord= order; ord; ord= ord->next) if (!ord->asc) return MAX_KEY; for (idx= 0; idx < table->keys; idx++) { if (!(table->keys_in_use_for_query.is_set(idx))) continue; KEY_PART_INFO *keyinfo= table->key_info[idx].key_part; uint partno= 0; /* The below check is sufficient considering we now have either BTREE indexes (records are returned in order for any index prefix) or HASH indexes (records are not returned in order for any index prefix). */ if (!(table->file->index_flags(idx, 0, 1) & HA_READ_ORDER)) continue; for (ord= order; ord; ord= ord->next, partno++) { Item *item= order->item[0]; if (!(item->type() == Item::FIELD_ITEM && ((Item_field*)item)->field->eq(keyinfo[partno].field))) break; } if (!ord && table->key_info[idx].key_length < match_key_len) { /* Ok, the ordering is compatible and this key is shorter then previous match (we want shorter keys as we'll have to read fewer index pages for the same number of records) */ match_key= idx; match_key_len= table->key_info[idx].key_length; } } if (match_key != MAX_KEY) { /* Found an index that allows records to be retrieved in the requested order. Now we'll check if using the index is cheaper then doing a table scan. */ double full_scan_time= table->file->scan_time(); double index_scan_time= table->file->read_time(match_key, 1, limit); if (index_scan_time > full_scan_time) match_key= MAX_KEY; } return match_key; } /* Test if a key can be used in different ranges Loading
sql/opt_range.h +2 −0 Original line number Diff line number Diff line Loading @@ -167,4 +167,6 @@ class FT_SELECT: public QUICK_SELECT { QUICK_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, struct st_table_ref *ref); uint get_index_for_order(TABLE *table, ORDER *order, ha_rows limit); #endif