Commit 867e790d authored by sergefp@mysql.com's avatar sergefp@mysql.com

Merge spetrunia@bk-internal.mysql.com:/home/bk/mysql-5.0

into mysql.com:/home/psergey/mysql-5.0-bug12915-merge
parents b310e430 841a2abd
...@@ -263,3 +263,62 @@ test ...@@ -263,3 +263,62 @@ test
delete from t1 where count(*)=1; delete from t1 where count(*)=1;
ERROR HY000: Invalid use of group function ERROR HY000: Invalid use of group function
drop table t1; 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;
...@@ -227,4 +227,33 @@ select DATABASE(); ...@@ -227,4 +227,33 @@ select DATABASE();
delete from t1 where count(*)=1; delete from t1 where count(*)=1;
drop table t1; 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 # End of 4.1 tests
...@@ -1334,6 +1334,8 @@ void change_byte(byte *,uint,char,char); ...@@ -1334,6 +1334,8 @@ void change_byte(byte *,uint,char,char);
void init_read_record(READ_RECORD *info, THD *thd, TABLE *reg_form, void init_read_record(READ_RECORD *info, THD *thd, TABLE *reg_form,
SQL_SELECT *select, SQL_SELECT *select,
int use_record_cache, bool print_errors); 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); void end_read_record(READ_RECORD *info);
ha_rows filesort(THD *thd, TABLE *form,struct st_sort_field *sortorder, ha_rows filesort(THD *thd, TABLE *form,struct st_sort_field *sortorder,
uint s_length, SQL_SELECT *select, uint s_length, SQL_SELECT *select,
......
...@@ -3674,7 +3674,6 @@ static void bootstrap(FILE *file) ...@@ -3674,7 +3674,6 @@ static void bootstrap(FILE *file)
THD *thd= new THD; THD *thd= new THD;
thd->bootstrap=1; thd->bootstrap=1;
thd->client_capabilities=0;
my_net_init(&thd->net,(st_vio*) 0); my_net_init(&thd->net,(st_vio*) 0);
thd->max_client_packet_length= thd->net.max_packet; thd->max_client_packet_length= thd->net.max_packet;
thd->security_ctx->master_access= ~(ulong)0; thd->security_ctx->master_access= ~(ulong)0;
......
...@@ -1362,6 +1362,95 @@ SEL_ARG *SEL_ARG::clone_tree() ...@@ -1362,6 +1362,95 @@ SEL_ARG *SEL_ARG::clone_tree()
} }
/*
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->s->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;
}
/* /*
Table rows retrieval plan. Range optimizer creates QUICK_SELECT_I-derived Table rows retrieval plan. Range optimizer creates QUICK_SELECT_I-derived
objects from table read plans. objects from table read plans.
......
...@@ -716,4 +716,6 @@ public: ...@@ -716,4 +716,6 @@ public:
QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table,
struct st_table_ref *ref, struct st_table_ref *ref,
ha_rows records); ha_rows records);
uint get_index_for_order(TABLE *table, ORDER *order, ha_rows limit);
#endif #endif
...@@ -29,7 +29,59 @@ static int rr_from_cache(READ_RECORD *info); ...@@ -29,7 +29,59 @@ static int rr_from_cache(READ_RECORD *info);
static int init_rr_cache(THD *thd, READ_RECORD *info); static int init_rr_cache(THD *thd, READ_RECORD *info);
static int rr_cmp(uchar *a,uchar *b); static int rr_cmp(uchar *a,uchar *b);
/* init struct for read with info->read_record */ static int rr_index(READ_RECORD *info);
/*
Initialize READ_RECORD structure to perform full index scan
SYNOPSIS
init_read_record_idx()
info READ_RECORD structure to initialize.
thd Thread handle
table Table to be accessed
print_error If true, call table->file->print_error() if an error
occurs (except for end-of-records error)
idx index to scan
DESCRIPTION
Initialize READ_RECORD structure to perform full index scan (in forward
direction) using read_record.read_record() interface.
This function has been added at late stage and is used only by
UPDATE/DELETE. Other statements perform index scans using
join_read_first/next functions.
*/
void init_read_record_idx(READ_RECORD *info, THD *thd, TABLE *table,
bool print_error, uint idx)
{
bzero((char*) info,sizeof(*info));
info->thd=thd;
info->table=table;
info->file= table->file;
info->forms= &info->table; /* Only one table */
info->record= table->record[0];
info->ref_length= table->file->ref_length;
info->select=NULL;
info->print_error=print_error;
info->ignore_not_found_rows= 0;
table->status=0; /* And it's always found */
if (!table->file->inited)
{
table->file->ha_index_init(idx);
table->file->extra(HA_EXTRA_RETRIEVE_PRIMARY_KEY);
}
info->read_record= rr_index;
info->first= TRUE;
}
/* init struct for read with info->read_record */
void init_read_record(READ_RECORD *info,THD *thd, TABLE *table, void init_read_record(READ_RECORD *info,THD *thd, TABLE *table,
SQL_SELECT *select, SQL_SELECT *select,
...@@ -187,6 +239,60 @@ static int rr_quick(READ_RECORD *info) ...@@ -187,6 +239,60 @@ static int rr_quick(READ_RECORD *info)
} }
/*
A READ_RECORD::read_record implementation that reads index sequentially
SYNOPSIS
rr_index()
info Scan info
DESCRIPTION
Read the next index record (in forward direction) and translate return
value.
RETURN
0 Ok
-1 End of records
1 Error
*/
static int rr_index(READ_RECORD *info)
{
int tmp;
while (1)
{
if (info->first)
{
info->first= FALSE;
tmp= info->file->index_first(info->record);
}
else
tmp= info->file->index_next(info->record);
if (!tmp)
break;
if (info->thd->killed)
{
my_error(ER_SERVER_SHUTDOWN,MYF(0));
return 1;
}
if (tmp != HA_ERR_RECORD_DELETED)
{
if (tmp == HA_ERR_END_OF_FILE)
tmp= -1;
else
{
if (info->print_error)
info->table->file->print_error(tmp,MYF(0));
if (tmp < 0) // Fix negative BDB errno
tmp=1;
}
break;
}
}
return tmp;
}
static int rr_sequential(READ_RECORD *info) static int rr_sequential(READ_RECORD *info)
{ {
int tmp; int tmp;
......
...@@ -67,7 +67,6 @@ static int init_failsafe_rpl_thread(THD* thd) ...@@ -67,7 +67,6 @@ static int init_failsafe_rpl_thread(THD* thd)
*/ */
thd->system_thread = thd->bootstrap = 1; thd->system_thread = thd->bootstrap = 1;
thd->security_ctx->skip_grants(); thd->security_ctx->skip_grants();
thd->client_capabilities = 0;
my_net_init(&thd->net, 0); my_net_init(&thd->net, 0);
thd->net.read_timeout = slave_net_timeout; thd->net.read_timeout = slave_net_timeout;
thd->max_client_packet_length=thd->net.max_packet; thd->max_client_packet_length=thd->net.max_packet;
......
...@@ -2869,7 +2869,6 @@ static int init_slave_thread(THD* thd, SLAVE_THD_TYPE thd_type) ...@@ -2869,7 +2869,6 @@ static int init_slave_thread(THD* thd, SLAVE_THD_TYPE thd_type)
thd->system_thread = (thd_type == SLAVE_THD_SQL) ? thd->system_thread = (thd_type == SLAVE_THD_SQL) ?
SYSTEM_THREAD_SLAVE_SQL : SYSTEM_THREAD_SLAVE_IO; SYSTEM_THREAD_SLAVE_SQL : SYSTEM_THREAD_SLAVE_IO;
thd->security_ctx->skip_grants(); thd->security_ctx->skip_grants();
thd->client_capabilities = 0;
my_net_init(&thd->net, 0); my_net_init(&thd->net, 0);
thd->net.read_timeout = slave_net_timeout; thd->net.read_timeout = slave_net_timeout;
thd->slave_thread = 1; thd->slave_thread = 1;
......
...@@ -218,6 +218,7 @@ THD::THD() ...@@ -218,6 +218,7 @@ THD::THD()
#ifndef EMBEDDED_LIBRARY #ifndef EMBEDDED_LIBRARY
net.vio=0; net.vio=0;
#endif #endif
client_capabilities= 0; // minimalistic client
net.last_error[0]=0; // If error on boot net.last_error[0]=0; // If error on boot
net.query_cache_query=0; // If error on boot net.query_cache_query=0; // If error on boot
ull=0; ull=0;
......
...@@ -37,6 +37,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, ...@@ -37,6 +37,7 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
bool using_limit=limit != HA_POS_ERROR; bool using_limit=limit != HA_POS_ERROR;
bool transactional_table, safe_update, const_cond; bool transactional_table, safe_update, const_cond;
ha_rows deleted; ha_rows deleted;
uint usable_index= MAX_KEY;
SELECT_LEX *select_lex= &thd->lex->select_lex; SELECT_LEX *select_lex= &thd->lex->select_lex;
DBUG_ENTER("mysql_delete"); DBUG_ENTER("mysql_delete");
...@@ -140,27 +141,40 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, ...@@ -140,27 +141,40 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
tables.table = table; tables.table = table;
tables.alias = table_list->alias; tables.alias = table_list->alias;
table->sort.io_cache = (IO_CACHE *) my_malloc(sizeof(IO_CACHE),
MYF(MY_FAE | MY_ZEROFILL));
if (select_lex->setup_ref_array(thd, order->elements) || if (select_lex->setup_ref_array(thd, order->elements) ||
setup_order(thd, select_lex->ref_pointer_array, &tables, setup_order(thd, select_lex->ref_pointer_array, &tables,
fields, all_fields, (ORDER*) order->first) || fields, all_fields, (ORDER*) order->first))
!(sortorder=make_unireg_sortorder((ORDER*) order->first, &length)) || {
delete select;
free_underlaid_joins(thd, &thd->lex->select_lex);
DBUG_RETURN(TRUE);
}
if (!select && limit != HA_POS_ERROR)
usable_index= get_index_for_order(table, (ORDER*)(order->first), limit);
if (usable_index == MAX_KEY)
{
table->sort.io_cache= (IO_CACHE *) my_malloc(sizeof(IO_CACHE),
MYF(MY_FAE | MY_ZEROFILL));
if ( !(sortorder=make_unireg_sortorder((ORDER*) order->first, &length)) ||
(table->sort.found_records = filesort(thd, table, sortorder, length, (table->sort.found_records = filesort(thd, table, sortorder, length,
select, HA_POS_ERROR, select, HA_POS_ERROR,
&examined_rows)) &examined_rows))
== HA_POS_ERROR) == HA_POS_ERROR)
{ {
delete select;
free_underlaid_joins(thd, &thd->lex->select_lex);
}
/*
Filesort has already found and selected the rows we want to delete,
so we don't need the where clause
*/
delete select; delete select;
free_underlaid_joins(thd, select_lex); free_underlaid_joins(thd, select_lex);
DBUG_RETURN(TRUE); select= 0;
} }
/*
Filesort has already found and selected the rows we want to delete,
so we don't need the where clause
*/
delete select;
select= 0;
} }
/* If quick select is used, initialize it before retrieving rows. */ /* If quick select is used, initialize it before retrieving rows. */
...@@ -170,7 +184,11 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds, ...@@ -170,7 +184,11 @@ bool mysql_delete(THD *thd, TABLE_LIST *table_list, COND *conds,
free_underlaid_joins(thd, select_lex); free_underlaid_joins(thd, select_lex);
DBUG_RETURN(TRUE); DBUG_RETURN(TRUE);
} }
init_read_record(&info,thd,table,select,1,1); if (usable_index==MAX_KEY)
init_read_record(&info,thd,table,select,1,1);
else
init_read_record_idx(&info, thd, table, 1, usable_index);
deleted=0L; deleted=0L;
init_ftfuncs(thd, select_lex, 1); init_ftfuncs(thd, select_lex, 1);
thd->proc_info="updating"; thd->proc_info="updating";
......
...@@ -122,7 +122,8 @@ int mysql_update(THD *thd, ...@@ -122,7 +122,8 @@ int mysql_update(THD *thd,
bool used_key_is_modified, transactional_table; bool used_key_is_modified, transactional_table;
int res; int res;
int error=0; int error=0;
uint used_index; uint used_index= MAX_KEY;
bool need_sort= TRUE;
#ifndef NO_EMBEDDED_ACCESS_CHECKS #ifndef NO_EMBEDDED_ACCESS_CHECKS
uint want_privilege; uint want_privilege;
#endif #endif
...@@ -241,6 +242,11 @@ int mysql_update(THD *thd, ...@@ -241,6 +242,11 @@ int mysql_update(THD *thd,
send_ok(thd); // No matching records send_ok(thd); // No matching records
DBUG_RETURN(0); DBUG_RETURN(0);
} }
if (!select && limit != HA_POS_ERROR)
{
if (MAX_KEY != (used_index= get_index_for_order(table, order, limit)))
need_sort= FALSE;
}
/* If running in safe sql mode, don't allow updates without keys */ /* If running in safe sql mode, don't allow updates without keys */
if (table->quick_keys.is_clear_all()) if (table->quick_keys.is_clear_all())
{ {
...@@ -261,6 +267,10 @@ int mysql_update(THD *thd, ...@@ -261,6 +267,10 @@ int mysql_update(THD *thd,
used_key_is_modified= (!select->quick->unique_key_range() && used_key_is_modified= (!select->quick->unique_key_range() &&
select->quick->check_if_keys_used(&fields)); select->quick->check_if_keys_used(&fields));
} }
else if (used_index != MAX_KEY)
{
used_key_is_modified= check_if_key_used(table, used_index, fields);
}
else if ((used_index=table->file->key_used_on_scan) < MAX_KEY) else if ((used_index=table->file->key_used_on_scan) < MAX_KEY)
used_key_is_modified=check_if_key_used(table, used_index, fields); used_key_is_modified=check_if_key_used(table, used_index, fields);
else else
...@@ -276,10 +286,11 @@ int mysql_update(THD *thd, ...@@ -276,10 +286,11 @@ int mysql_update(THD *thd,
if (used_index < MAX_KEY && old_used_keys.is_set(used_index)) if (used_index < MAX_KEY && old_used_keys.is_set(used_index))
{ {
table->key_read=1; table->key_read=1;
table->file->extra(HA_EXTRA_KEYREAD); table->file->extra(HA_EXTRA_KEYREAD); //todo: psergey: check
} }
if (order) /* note: can actually avoid sorting below.. */
if (order && need_sort)
{ {
/* /*
Doing an ORDER BY; Let filesort find and sort the rows we are going Doing an ORDER BY; Let filesort find and sort the rows we are going
...@@ -323,7 +334,10 @@ int mysql_update(THD *thd, ...@@ -323,7 +334,10 @@ int mysql_update(THD *thd,
/* If quick select is used, initialize it before retrieving rows. */ /* If quick select is used, initialize it before retrieving rows. */
if (select && select->quick && select->quick->reset()) if (select && select->quick && select->quick->reset())
goto err; goto err;
init_read_record(&info,thd,table,select,0,1); if (used_index == MAX_KEY)
init_read_record(&info,thd,table,select,0,1);
else
init_read_record_idx(&info, thd, table, 1, used_index);
thd->proc_info="Searching rows for update"; thd->proc_info="Searching rows for update";
uint tmp_limit= limit; uint tmp_limit= limit;
...@@ -352,6 +366,10 @@ int mysql_update(THD *thd, ...@@ -352,6 +366,10 @@ int mysql_update(THD *thd,
error= 1; // Aborted error= 1; // Aborted
limit= tmp_limit; limit= tmp_limit;
end_read_record(&info); end_read_record(&info);
/* if we got here we must not use index in the main update loop below */
used_index= MAX_KEY;
/* Change select to use tempfile */ /* Change select to use tempfile */
if (select) if (select)
{ {
......
...@@ -135,6 +135,7 @@ typedef struct st_read_record { /* Parameter to read_record */ ...@@ -135,6 +135,7 @@ typedef struct st_read_record { /* Parameter to read_record */
byte *cache,*cache_pos,*cache_end,*read_positions; byte *cache,*cache_pos,*cache_end,*read_positions;
IO_CACHE *io_cache; IO_CACHE *io_cache;
bool print_error, ignore_not_found_rows; bool print_error, ignore_not_found_rows;
bool first; /* used only with rr_index_read */
} READ_RECORD; } READ_RECORD;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment