Commit 1fe423ae authored by unknown's avatar unknown
Browse files

BUG#12915: Added single-table UPDATE/DELTE ... ORDER BY ... LIMIT

optimization: now can use index to find records to update/delete
when there is no WHERE clause.


mysql-test/r/update.result:
  Testcase for BUG#12915
mysql-test/t/update.test:
  Testcase for BUG#12915
sql/mysql_priv.h:
  BUG#12915: Added init_read_record_idx function.
sql/opt_range.cc:
  BUG#12915: Added get_index_for_order() - find an index that can be
  used to get first N table records in given ordering cheaper then 
  one would with full table scan.
sql/opt_range.h:
  BUG#12915: Added get_index_for_order() function
sql/records.cc:
  BUG#12915: Added init_read_record_idx(), rr_index() that allow to scan
  index using init_read_record()/read_record.read_record()
sql/sql_delete.cc:
  BUG#12915: Added single-table DELETE ... ORDER BY ... LIMIT optimization: 
  now can use index to find records to delete when there is no WHERE clause.
sql/sql_update.cc:
  BUG#12915: Added single-table UPDATE ... ORDER BY ... LIMIT optimization: 
  now can use index to find records to update when there is no WHERE clause.
sql/structs.h:
  BUG#12915: Added init_read_record_idx(), rr_index() that allow to scan
  index using init_read_record()/READ_RECORD::read_record()
parent 7b8a6e45
Loading
Loading
Loading
Loading
+59 −0
Original line number Diff line number Diff line
@@ -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;
+29 −0
Original line number Diff line number Diff line
@@ -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
+2 −0
Original line number Diff line number Diff line
@@ -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,
+88 −0
Original line number Diff line number Diff line
@@ -582,6 +582,94 @@ SEL_ARG *SEL_ARG::clone_tree()
  return root;
}


/*
  Find an index that allows to retrieve first #limit records in the given
  order cheaper then one would retrieve them using full table scan.

  SYNOPSIS
    get_index_for_order()
      table  Table to be accessed
      order  Required ordering
      limit  Number of records that will be retrieved

  DESCRIPTION
    Run through all table indexes and find the shortest index that allows
    records to be retrieved in given order. If there is such index and
    reading first #limit records from it is cheaper then scanning the entire
    table, return 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

+2 −0
Original line number Diff line number Diff line
@@ -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