Commit e2cd3dd1 authored by timour@mysql.com's avatar timour@mysql.com

WL#1724 "Min/Max Optimization for Queries with Group By Clause"

- after-review changes
- merged with the source tree from 204-08-27
parent a5f063e0
......@@ -200,19 +200,19 @@ select distinct 1 from t1,t3 where t1.a=t3.a;
1
explain SELECT distinct t1.a from t1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL PRIMARY 4 NULL 4 Using index
1 SIMPLE t1 range NULL PRIMARY 4 NULL 5 Using index for group-by
explain SELECT distinct t1.a from t1 order by a desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL PRIMARY 4 NULL 4 Using index
1 SIMPLE t1 range NULL PRIMARY 4 NULL 5 Using index for group-by; Using temporary; Using filesort
explain SELECT t1.a from t1 group by a order by a desc;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL PRIMARY 4 NULL 4 Using index
1 SIMPLE t1 range NULL PRIMARY 4 NULL 5 Using index for group-by; Using temporary; Using filesort
explain SELECT distinct t1.a from t1 order by a desc limit 1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index NULL PRIMARY 4 NULL 4 Using index
1 SIMPLE t1 index NULL PRIMARY 4 NULL 5 Using index
explain SELECT distinct a from t3 order by a desc limit 2;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t3 index NULL a 5 NULL 204 Using index
1 SIMPLE t3 index NULL a 5 NULL 10 Using index
explain SELECT distinct a,b from t3 order by a+1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t3 ALL NULL NULL NULL NULL 204 Using temporary; Using filesort
......
This diff is collapsed.
This diff is collapsed.
......@@ -1515,7 +1515,8 @@ longlong ha_myisam::get_auto_increment()
int error;
byte key[MI_MAX_KEY_LENGTH];
(void) extra(HA_EXTRA_KEYREAD);
key_copy(key,table,table->next_number_index,
key_copy(key, table->record[0],
table->key_info + table->next_number_index,
table->next_number_key_offset);
error=mi_rkey(file,table->record[1],(int) table->next_number_index,
key,table->next_number_key_offset,HA_READ_PREFIX_LAST);
......
......@@ -984,7 +984,8 @@ longlong handler::get_auto_increment()
else
{
byte key[MAX_KEY_LENGTH];
key_copy(key,table,table->next_number_index,
key_copy(key, table->record[0],
table->key_info + table->next_number_index,
table->next_number_key_offset);
error=index_read(table->record[1], key, table->next_number_key_offset,
HA_READ_PREFIX_LAST);
......
......@@ -159,6 +159,46 @@ bool Item_ident::remove_dependence_processor(byte * arg)
}
/*
Store the pointer to this item field into a list if not already there.
SYNOPSIS
Item_field::collect_item_field_processor()
arg pointer to a List<Item_field>
DESCRIPTION
The method is used by Item::walk to collect all unique Item_field objects
from a tree of Items into a set of items represented as a list.
IMPLEMENTATION
Item_cond::walk() and Item_func::walk() stop the evaluation of the
processor function for its arguments once the processor returns
true.Therefore in order to force this method being called for all item
arguments in a condition the method must return false.
RETURN
false on success (force the evaluation of collect_item_field_processor
for the subsequent items.)
true o/w (stop evaluation of subsequent items.)
*/
bool Item_field::collect_item_field_processor(byte *arg)
{
DBUG_ENTER("Item_field::collect_item_field_processor");
DBUG_PRINT("info", ("%s", field->field_name ? field->field_name : "noname"));
List<Item_field> *item_list= (List<Item_field>*) arg;
List_iterator<Item_field> item_list_it(*item_list);
Item_field *curr_item;
while ((curr_item= item_list_it++))
{
if (curr_item->eq(this, 1))
DBUG_RETURN(false); /* Already in the set. */
}
item_list->push_back(this);
DBUG_RETURN(false);
}
bool Item::check_cols(uint c)
{
if (c != 1)
......
......@@ -260,6 +260,22 @@ public:
virtual bool remove_dependence_processor(byte * arg) { return 0; }
virtual bool remove_fixed(byte * arg) { fixed= 0; return 0; }
/*
All collect_* methods are used as arguments to walk() to collect
specific types items.
TODO:
A more generic implementation would add a special class
Collect_processor_param that can store arbitrary sets of item kinds
(currently specified as enums), along with a list to store items of the
specified kinds. This would allow to collect combinations of items of
arbitrary kinds without having to add a new collect method each time.
There can be one generic collect_processor method that checks the item type
and compares it with the item types in Collect_processor_param.
*/
virtual bool collect_item_field_processor(byte * arg) { return 0; }
virtual bool collect_item_sum_min_processor(byte * arg) { return 0; }
virtual bool collect_item_sum_max_processor(byte * arg) { return 0; }
virtual bool has_non_min_max_sum_processor(byte * arg) { return 0; }
virtual Item *this_item() { return this; } /* For SPs mostly. */
virtual Item *this_const_item() const { return const_cast<Item*>(this); } /* For SPs mostly. */
......@@ -493,6 +509,7 @@ public:
bool get_time(TIME *ltime);
bool is_null() { return field->is_null(); }
Item *get_tmp_table_item(THD *thd);
bool collect_item_field_processor(byte * arg);
void cleanup();
inline uint32 max_disp_length() { return field->max_length(); }
friend class Item_default_value;
......@@ -984,6 +1001,8 @@ public:
(*ref)->save_in_field(result_field, no_conversions);
}
Item *real_item() { return *ref; }
bool walk(Item_processor processor, byte *arg)
{ return (*ref)->walk(processor, arg); }
void print(String *str);
void cleanup();
};
......
......@@ -182,6 +182,45 @@ bool Item_sum::walk (Item_processor processor, byte *argument)
return (this->*processor)(argument);
}
/*
Store the pointer to this item into a list if not already there.
SYNOPSIS
Item_sum::collect()
item_list pointer to a List<Item_sum> where item_sum objects are collected
DESCRIPTION
The method is used by collect_item_sum_*_processor, called by
Item_sum::walk, to collect all unique Item_sum_min and Item_sum_max objects
from a tree of Items into a set of items represented as a list.
IMPLEMENTATION
Item_cond::walk() and Item_func::walk() stop the evaluation of the
processor function for its arguments once the processor returns
true.Therefore in order to force this method being called for all item
arguments in a condition the method must return false.
RETURN
FALSE on success (force the evaluation of collect_item_sum_*_processor
for the subsequent items.)
TRUE o/w (stop evaluation of subsequent items.)
*/
bool Item_sum::collect(List<Item_sum> *item_list)
{
List_iterator<Item_sum> item_list_it(*item_list);
Item_sum *curr_item;
while ((curr_item= item_list_it++))
{
if (curr_item == this)
return FALSE; /* Already in the set. */
}
item_list->push_back(this);
return FALSE;
}
String *
Item_sum_num::val_str(String *str)
{
......
......@@ -27,6 +27,8 @@ class Item_arena;
class Item_sum :public Item_result_field
{
private:
bool collect(List<Item_sum> *item_list);
public:
enum Sumfunctype
{ COUNT_FUNC, COUNT_DISTINCT_FUNC, SUM_FUNC, SUM_DISTINCT_FUNC, AVG_FUNC,
......@@ -98,6 +100,33 @@ public:
bool save_args(Item_arena* stmt);
bool walk (Item_processor processor, byte *argument);
/* Collect Item_sum_min objects into a list supplied by the caller. */
bool collect_item_sum_min_processor(byte *arg)
{
if (Item_sum::MIN_FUNC == this->sum_func())
return collect((List<Item_sum>*) arg);
else
return FALSE;
}
/* Collect Item_sum_max objects into a list supplied by the caller. */
bool collect_item_sum_max_processor(byte *arg)
{
if (Item_sum::MAX_FUNC == this->sum_func())
return collect((List<Item_sum>*) arg);
else
return FALSE;
}
/* Check if there are any aggregate functions other than MIN and MAX. */
bool has_non_min_max_sum_processor(byte * arg)
{
Sumfunctype sum_type= this->sum_func();
if ((sum_type != Item_sum::MIN_FUNC) && (sum_type != Item_sum::MAX_FUNC))
return TRUE;
return FALSE;
}
};
......
......@@ -66,94 +66,121 @@ int find_ref_key(TABLE *table,Field *field, uint *key_length)
}
/* Copy a key from record to some buffer */
/* if length == 0 then copy whole key */
/*
Copy part of a record that forms a key or key prefix to a buffer.
SYNOPSIS
key_copy()
to_key buffer that will be used as a key
from_record full record to be copied from
key_info descriptor of the index
key_length specifies length of all keyparts that will be copied
DESCRIPTION
The function takes a complete table record (as e.g. retrieved by
handler::index_read()), and a description of an index on the same table,
and extracts the first key_length bytes of the record which are part of a
key into to_key. If length == 0 then copy all bytes from the record that
form a key.
RETURN
None
*/
void key_copy(byte *key,TABLE *table,uint idx,uint key_length)
void key_copy(byte *to_key, byte *from_record, KEY *key_info, uint key_length)
{
uint length;
KEY *key_info=table->key_info+idx;
KEY_PART_INFO *key_part;
if (key_length == 0)
key_length=key_info->key_length;
for (key_part=key_info->key_part;
(int) key_length > 0 ;
key_part++)
key_length= key_info->key_length;
for (key_part= key_info->key_part; (int) key_length > 0; key_part++)
{
if (key_part->null_bit)
{
*key++= test(table->record[0][key_part->null_offset] &
*to_key++= test(from_record[key_part->null_offset] &
key_part->null_bit);
key_length--;
}
if (key_part->key_part_flag & HA_BLOB_PART)
{
char *pos;
ulong blob_length=((Field_blob*) key_part->field)->get_length();
key_length-=2;
ulong blob_length= ((Field_blob*) key_part->field)->get_length();
key_length-= 2;
((Field_blob*) key_part->field)->get_ptr(&pos);
length=min(key_length,key_part->length);
set_if_smaller(blob_length,length);
int2store(key,(uint) blob_length);
key+=2; // Skip length info
memcpy(key,pos,blob_length);
length=min(key_length, key_part->length);
set_if_smaller(blob_length, length);
int2store(to_key, (uint) blob_length);
to_key+= 2; // Skip length info
memcpy(to_key, pos, blob_length);
}
else
{
length=min(key_length,key_part->length);
memcpy(key,table->record[0]+key_part->offset,(size_t) length);
length= min(key_length, key_part->length);
memcpy(to_key, from_record + key_part->offset, (size_t) length);
}
key+=length;
key_length-=length;
to_key+= length;
key_length-= length;
}
} /* key_copy */
}
/* restore a key from some buffer to record */
/*
Restore a key from some buffer to record.
SYNOPSIS
key_restore()
to_record record buffer where the key will be restored to
from_key buffer that contains a key
key_info descriptor of the index
key_length specifies length of all keyparts that will be restored
DESCRIPTION
This function converts a key into record format. It can be used in cases
when we want to return a key as a result row.
RETURN
None
*/
void key_restore(TABLE *table,byte *key,uint idx,uint key_length)
void key_restore(byte *to_record, byte *from_key, KEY *key_info,
uint key_length)
{
uint length;
KEY *key_info=table->key_info+idx;
KEY_PART_INFO *key_part;
if (key_length == 0)
{
if (idx == (uint) -1)
return;
key_length=key_info->key_length;
key_length= key_info->key_length;
}
for (key_part=key_info->key_part;
(int) key_length > 0 ;
key_part++)
for (key_part= key_info->key_part ; (int) key_length > 0 ; key_part++)
{
if (key_part->null_bit)
{
if (*key++)
table->record[0][key_part->null_offset]|= key_part->null_bit;
if (*from_key++)
to_record[key_part->null_offset]|= key_part->null_bit;
else
table->record[0][key_part->null_offset]&= ~key_part->null_bit;
to_record[key_part->null_offset]&= ~key_part->null_bit;
key_length--;
}
if (key_part->key_part_flag & HA_BLOB_PART)
{
uint blob_length=uint2korr(key);
key+=2;
key_length-=2;
uint blob_length= uint2korr(from_key);
from_key+= 2;
key_length-= 2;
((Field_blob*) key_part->field)->set_ptr((ulong) blob_length,
(char*) key);
length=key_part->length;
(char*) from_key);
length= key_part->length;
}
else
{
length=min(key_length,key_part->length);
memcpy(table->record[0]+key_part->offset,key,(size_t) length);
length= min(key_length, key_part->length);
memcpy(to_record + key_part->offset, from_key, (size_t) length);
}
key+=length;
key_length-=length;
from_key+= length;
key_length-= length;
}
} /* key_restore */
}
/*
......
......@@ -733,7 +733,8 @@ bool add_proc_to_list(THD *thd, Item *item);
TABLE *unlink_open_table(THD *thd,TABLE *list,TABLE *find);
SQL_SELECT *make_select(TABLE *head, table_map const_tables,
table_map read_tables, COND *conds, int *error);
table_map read_tables, COND *conds, int *error,
bool allow_null_cond= false);
enum find_item_error_report_type {REPORT_ALL_ERRORS, REPORT_EXCEPT_NOT_FOUND,
IGNORE_ERRORS};
extern const Item **not_found_item;
......@@ -814,8 +815,9 @@ void print_plan(JOIN* join, double read_time, double record_count,
void mysql_print_status(THD *thd);
/* key.cc */
int find_ref_key(TABLE *form,Field *field, uint *offset);
void key_copy(byte *key,TABLE *form,uint index,uint key_length);
void key_restore(TABLE *form,byte *key,uint index,uint key_length);
void key_copy(byte *to_key, byte *from_record, KEY *key_info, uint key_length);
void key_restore(byte *to_record, byte *from_key, KEY *key_info,
uint key_length);
bool key_cmp_if_same(TABLE *form,const byte *key,uint index,uint key_length);
void key_unpack(String *to,TABLE *form,uint index);
bool check_if_key_used(TABLE *table, uint idx, List<Item> &fields);
......
This diff is collapsed.
......@@ -146,7 +146,8 @@ public:
QS_TYPE_RANGE_DESC = 2,
QS_TYPE_FULLTEXT = 3,
QS_TYPE_ROR_INTERSECT = 4,
QS_TYPE_ROR_UNION = 5
QS_TYPE_ROR_UNION = 5,
QS_TYPE_GROUP_MIN_MAX = 6
};
/* Get type of this quick select - one of the QS_TYPE_* values */
......@@ -278,14 +279,12 @@ public:
int init();
int get_next();
void range_end();
int get_next_prefix(uint prefix_length, byte *cur_prefix);
bool reverse_sorted() { return 0; }
bool unique_key_range();
int init_ror_merged_scan(bool reuse_handler);
void save_last_pos()
{
file->position(record);
};
{ file->position(record); }
int get_type() { return QS_TYPE_RANGE; }
void add_keys_and_lengths(String *key_names, String *used_lengths);
void add_info_string(String *str);
......@@ -518,6 +517,103 @@ private:
};
/*
Index scan for GROUP-BY queries with MIN/MAX aggregate functions.
This class provides a specialized index access method for GROUP-BY queries
of the forms:
SELECT A_1,...,A_k, [B_1,...,B_m], [MIN(C)], [MAX(C)]
FROM T
WHERE [RNG(A_1,...,A_p ; where p <= k)]
[AND EQ(B_1,...,B_m)]
[AND PC(C)]
[AND PA(A_i1,...,A_iq)]
GROUP BY A_1,...,A_k;
or
SELECT DISTINCT A_i1,...,A_ik
FROM T
WHERE [RNG(A_1,...,A_p ; where p <= k)]
[AND PA(A_i1,...,A_iq)];
where all selected fields are parts of the same index.
The class of queries that can be processed by this quick select is fully
specified in the description of get_best_trp_group_min_max() in opt_range.cc.
The get_next() method directly produces result tuples, thus obviating the
need to call end_send_group() because all grouping is already done inside
get_next().
Since one of the requirements is that all select fields are part of the same
index, this class produces only index keys, and not complete records.
*/
class QUICK_GROUP_MIN_MAX_SELECT : public QUICK_SELECT_I
{
private:
handler *file; /* The handler used to get data. */
JOIN *join; /* Descriptor of the current query */
KEY *index_info; /* The index chosen for data access */
byte *record; /* Buffer where the next record is returned. */
byte *tmp_record; /* Temporary storage for next_min(), next_max(). */
byte *group_prefix; /* Key prefix consisting of the GROUP fields. */
uint group_prefix_len; /* Length of the group prefix. */
byte *last_prefix; /* Prefix of the last group for detecting EOF. */
bool have_min; /* Specify whether we are computing */
bool have_max; /* a MIN, a MAX, or both. */
bool seen_first_key; /* Denotes whether the first key was retrieved.*/
KEY_PART_INFO *min_max_arg_part; /* The keypart of the only argument field */
/* of all MIN/MAX functions. */
uint min_max_arg_len; /* The length of the MIN/MAX argument field */
byte *key_infix; /* Infix of constants from equality predicates. */
uint key_infix_len;
DYNAMIC_ARRAY min_max_ranges; /* Array of range ptrs for the MIN/MAX field. */
uint real_prefix_len; /* Length of key prefix extended with key_infix. */
List<Item_sum> *min_functions;
List<Item_sum> *max_functions;
List_iterator<Item_sum> *min_functions_it;
List_iterator<Item_sum> *max_functions_it;
public:
/*
The following two members are public to allow easy access from
TRP_GROUP_MIN_MAX::make_quick()
*/
MEM_ROOT alloc; /* Memory pool for this and quick_prefix_select data. */
QUICK_RANGE_SELECT *quick_prefix_select;/* For retrieval of group prefixes. */
private:
int next_prefix();
int next_min_in_range();
int next_max_in_range();
int next_min();
int next_max();
void update_min_result();
void update_max_result();
public:
QUICK_GROUP_MIN_MAX_SELECT(TABLE *table, JOIN *join, bool have_min,
bool have_max, KEY_PART_INFO *min_max_arg_part,
uint group_prefix_len, uint used_key_parts,
KEY *index_info, uint use_index, double read_cost,
ha_rows records, uint key_infix_len,
byte *key_infix, MEM_ROOT *parent_alloc);
~QUICK_GROUP_MIN_MAX_SELECT();
bool add_range(SEL_ARG *sel_range);
void update_key_stat();
bool alloc_buffers();
int init();
int reset();
int get_next();
bool reverse_sorted() { 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);
#ifndef DBUG_OFF
void dbug_dump(int indent, bool verbose);
#endif
};
class QUICK_SELECT_DESC: public QUICK_RANGE_SELECT
{
public:
......
......@@ -336,7 +336,7 @@ int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds)
1 Otherwise
*/
static bool simple_pred(Item_func *func_item, Item **args, bool *inv_order)
bool simple_pred(Item_func *func_item, Item **args, bool *inv_order)
{
Item *item;
*inv_order= 0;
......
......@@ -1884,7 +1884,7 @@ GRANT_TABLE::GRANT_TABLE(TABLE *form, TABLE *col_privs)
col_privs->field[1]->pack_length()+
col_privs->field[2]->pack_length()+
col_privs->field[3]->pack_length());
key_copy(key,col_privs,0,key_len);
key_copy(key,col_privs->record[0],col_privs->key_info,key_len);
col_privs->field[4]->store("",0, &my_charset_latin1);
col_privs->file->ha_index_init(0);
if (col_privs->file->index_read(col_privs->record[0],
......@@ -1996,7 +1996,7 @@ static int replace_column_table(GRANT_TABLE *g_t,
table->field[3]->store(table_name,(uint) strlen(table_name), &my_charset_latin1);
key_length=(table->field[0]->pack_length()+ table->field[1]->pack_length()+
table->field[2]->pack_length()+ table->field[3]->pack_length());
key_copy(key,table,0,key_length);
key_copy(key,table->record[0],table->key_info,key_length);
rights &= COL_ACLS; // Only ACL for columns
......@@ -2009,7 +2009,7 @@ static int replace_column_table(GRANT_TABLE *g_t,
{
ulong privileges = xx->rights;
bool old_row_exists=0;
key_restore(table,key,0,key_length);
key_restore(table->record[0],key,table->key_info,key_length);
table->field[4]->store(xx->column.ptr(),xx->column.length(),
&my_charset_latin1);
......@@ -2025,7 +2025,7 @@ static int replace_column_table(GRANT_TABLE *g_t,
}
old_row_exists = 0;
restore_record(table,default_values); // Get empty record
key_restore(table,key,0,key_length);
key_restore(table->record[0],key,table->key_info,key_length);
table->field[4]->store(xx->column.ptr(),xx->column.length(),
&my_charset_latin1);
}
......
......@@ -333,7 +333,7 @@ int mysql_ha_read(THD *thd, TABLE_LIST *tables,
send_error(thd,ER_OUTOFMEMORY);
goto err;
}
key_copy(key, table, keyno, key_len);
key_copy(key, table->record[0], table->key_info + keyno, key_len);
err=table->file->index_read(table->record[0],
key,key_len,ha_rkey_mode);
mode=rkey_to_rnext[(int)ha_rkey_mode];
......
......@@ -635,7 +635,7 @@ int write_record(TABLE *table,COPY_INFO *info)
goto err;
}
}
key_copy((byte*) key,table,key_nr,0);
key_copy((byte*) key,table->record[0],table->key_info+key_nr,0);
if ((error=(table->file->index_read_idx(table->record[1],key_nr,
(byte*) key,
table->key_info[key_nr].
......
......@@ -188,6 +188,7 @@ static bool update_sum_func(Item_sum **func);
static void select_describe(JOIN *join, bool need_tmp_table,bool need_order,
bool distinct, const char *message=NullS);
static Item *remove_additional_cond(Item* conds);
static void add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab);
/*
......@@ -635,7 +636,7 @@ JOIN::optimize()
conds=new Item_int((longlong) 1,1); // Always true
}
select=make_select(*table, const_table_map,
const_table_map, conds, &error);
const_table_map, conds, &error, true);
if (error)
{ /* purecov: inspected */
error= -1; /* purecov: inspected */
......@@ -2304,6 +2305,12 @@ make_join_statistics(JOIN *join,TABLE_LIST *tables,COND *conds,
if (s->worst_seeks < 2.0) // Fix for small tables
s->worst_seeks=2.0;
/*
Add to stat->const_keys those indexes for which all group fields or
all select distinct fields participate in one index.
*/
add_group_and_distinct_keys(join, s);
if (!s->const_keys.is_clear_all() &&
!s->table->pos_in_table_list->embedding)
{
......@@ -2312,7 +2319,9 @@ make_join_statistics(JOIN *join,TABLE_LIST *tables,COND *conds,
select= make_select(s->table, found_const_table_map,
found_const_table_map,
s->on_expr ? s->on_expr : conds,
&error);
&error, true);
if (!select)
DBUG_RETURN(1);
records= get_quick_record_count(join->thd, select, s->table,
&s->const_keys, join->row_limit);
s->quick=select->quick;
......@@ -2347,13 +2356,13 @@ make_join_statistics(JOIN *join,TABLE_LIST *tables,COND *conds,
}
}
/* Find best combination and return it */
join->join_tab=stat;
join->map2table=stat_ref;
join->table= join->all_tables=table_vector;
join->const_tables=const_count;
join->found_const_table_map=found_const_table_map;
/* Find an optimal join order of the non-constant tables. */
if (join->const_tables != join->tables)
{
optimize_keyuse(join, keyuse_array);
......@@ -2365,6 +2374,7 @@ make_join_statistics(JOIN *join,TABLE_LIST *tables,COND *conds,
sizeof(POSITION)*join->const_tables);
join->best_read=1.0;
}
/* Generate an execution plan from the found optimal join order. */
DBUG_RETURN(join->thd->killed || get_best_combination(join));
}
......@@ -2561,6 +2571,10 @@ add_key_field(KEY_FIELD **key_fields,uint and_level, COND *cond,
bool is_const=1;
for (uint i=0; i<num_values; i++)
/*
TODO: this looks like a bug, should be
is_const&= (value[i])->const_item();
*/
is_const&= (*value)->const_item();
if (is_const)
stat[0].const_keys.merge(possible_keys);
......@@ -2997,6 +3011,68 @@ static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array)
}
/*
Discover the indexes that can be used for GROUP BY or DISTINCT queries.
SYNOPSIS
add_group_and_distinct_keys()
join
join_tab
DESCRIPTION
If the query has a GROUP BY clause, find all indexes that contain all
GROUP BY fields, and add those indexes to join->const_keys.
If the query has a DISTINCT clause, find all indexes that contain all
SELECT fields, and add those indexes to join->const_keys.
This allows later on such queries to be processed by a
QUICK_GROUP_MIN_MAX_SELECT.
RETURN
None
*/
static void
add_group_and_distinct_keys(JOIN *join, JOIN_TAB *join_tab)
{
List<Item_field> indexed_fields;
List_iterator<Item_field> indexed_fields_it(indexed_fields);
ORDER *cur_group;
Item_field *cur_item;
key_map possible_keys(0);
if (join->group_list)
{ /* Collect all query fields referenced in the GROUP clause. */
for (cur_group= join->group_list; cur_group; cur_group= cur_group->next)
(*cur_group->item)->walk(&Item::collect_item_field_processor,
(byte*) &indexed_fields);
}
else if (join->select_distinct)
{ /* Collect all query fields referenced in the SELECT clause. */
List<Item> &select_items= join->fields_list;
List_iterator<Item> select_items_it(select_items);
Item *item;
while ((item= select_items_it++))
item->walk(&Item::collect_item_field_processor, (byte*) &indexed_fields);
}
else
return;
if (indexed_fields.elements == 0)
return;
/* Intersect the keys of all group fields. */
cur_item= indexed_fields_it++;
possible_keys.merge(cur_item->field->part_of_key);
while ((cur_item= indexed_fields_it++))
{
possible_keys.intersect(cur_item->field->part_of_key);
}
if (!possible_keys.is_clear_all())
join_tab->const_keys.merge(possible_keys);
}
/*****************************************************************************
Go through all combinations of not marked tables and find the one
which uses least records
......@@ -4883,6 +4959,8 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
if (select)
{
table_map used_tables;
if (cond) /* Because of QUICK_GROUP_MIN_MAX_SELECT */
{ /* there may be a select without a cond. */
if (join->tables > 1)
cond->update_used_tables(); // Tablenr may have changed
if (join->const_tables == join->tables &&
......@@ -4899,6 +4977,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
DBUG_RETURN(1); // Impossible const condition
}
}
}
used_tables=((select->const_tables=join->const_table_map) |
OUTER_REF_TABLE_BIT | RAND_TABLE_BIT);
for (uint i=join->const_tables ; i < join->tables ; i++)
......@@ -4928,8 +5007,10 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
join->best_positions[i].records_read= rows2double(tab->quick->records);
}
COND *tmp=make_cond_for_table(cond,used_tables,current_map);
if (!tmp && tab->quick)
COND *tmp= NULL;
if (cond)
tmp= make_cond_for_table(cond,used_tables,current_map);
if (cond && !tmp && tab->quick)
{ // Outer join
/*
Hack to handle the case where we only refer to a table
......@@ -4937,8 +5018,9 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
*/
tmp=new Item_int((longlong) 1,1); // Always true
}
if (tmp)
if (tmp || !cond)
{
if (tmp)
DBUG_EXECUTE("where",print_where(tmp,tab->table->table_name););
SQL_SELECT *sel=tab->select=(SQL_SELECT*)
join->thd->memdup((gptr) select, sizeof(SQL_SELECT));
......@@ -4950,9 +5032,17 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
The guard will turn the predicate on only after
the first match for outer tables is encountered.
*/
if (cond)
{/*
Because of QUICK_GROUP_MIN_MAX_SELECT there may be a select without
a cond, so neutralize the hack above.
*/
if (!(tmp= add_found_match_trig_cond(first_inner_tab, tmp, 0)))
DBUG_RETURN(1);
tab->select_cond=sel->cond=tmp;
}
else
tab->select_cond= sel->cond= NULL;
sel->head=tab->table;
if (tab->quick)
......@@ -4992,7 +5082,8 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
the index if we are using limit and this is the first table
*/
if ((!tab->keys.is_subset(tab->const_keys) && i > 0) ||
if (cond &&
(!tab->keys.is_subset(tab->const_keys) && i > 0) ||
(!tab->const_keys.is_clear_all() && i == join->const_tables &&
join->unit->select_limit_cnt <
join->best_positions[i].records_read &&
......@@ -5050,7 +5141,8 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
}
if (i != join->const_tables && tab->use_quick != 2)
{ /* Read with cache */
if ((tmp=make_cond_for_table(cond,
if (cond &&
(tmp=make_cond_for_table(cond,
join->const_table_map |
current_map,
current_map)))
......@@ -7444,11 +7536,18 @@ do_select(JOIN *join,List<Item> *fields,TABLE *table,Procedure *procedure)
}
else
{
if (join->sort_and_group || (join->procedure &&
join->procedure->flags & PROC_GROUP))
end_select=end_send_group;
/* Test if data is accessed via QUICK_GROUP_MIN_MAX_SELECT. */
bool is_using_quick_group_min_max_select=
(join->join_tab->select && join->join_tab->select->quick &&
(join->join_tab->select->quick->get_type() ==
QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX));
if ((join->sort_and_group ||
(join->procedure && join->procedure->flags & PROC_GROUP)) &&
!is_using_quick_group_min_max_select)
end_select= end_send_group;
else
end_select=end_send;
end_select= end_send;
}
join->join_tab[join->tables-1].next_select=end_select;
......@@ -9225,7 +9324,8 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit,
& HA_READ_PREV) ||
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_ROR_UNION ||
quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
DBUG_RETURN(0); // Use filesort
/* ORDER BY range_key DESC */
......@@ -11455,11 +11555,16 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
extra.append(tab->keys.print(buf));
extra.append(')');
}
else
else if (tab->select->cond)
extra.append("; Using where");
}
if (key_read)
{
if (quick_type == QUICK_SELECT_I::QS_TYPE_GROUP_MIN_MAX)
extra.append("; Using index for group-by");
else
extra.append("; Using index");
}
if (table->reginfo.not_exists_optimize)
extra.append("; Not exists");
if (need_tmp_table)
......
......@@ -399,6 +399,7 @@ bool create_myisam_from_heap(THD *thd, TABLE *table, TMP_TABLE_PARAM *param,
uint find_shortest_key(TABLE *table, const key_map *usable_keys);
/* functions from opt_sum.cc */
bool simple_pred(Item_func *func_item, Item **args, bool *inv_order);
int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds);
/* from sql_delete.cc, used by opt_range.cc */
......
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