Commit 144f82a9 authored by Igor Babaev's avatar Igor Babaev

Addressed the feedback from the review of Monty on the cumulative patch for

mwl#21.
parent ac984f41
......@@ -30,6 +30,15 @@ extern "C" {
#define tree_set_pointer(element,ptr) *((uchar **) (element+1))=((uchar*) (ptr))
/*
A tree with its flag set to TREE_ONLY_DUPS behaves differently on inserting
an element that is not in the tree:
the element is not added at all, but instead tree_insert() returns a special
address TREE_ELEMENT_UNIQUE as an indication that the function has not failed
due to lack of memory.
*/
#define TREE_ELEMENT_UNIQUE ((TREE_ELEMENT *) 1)
#define TREE_NO_DUPS 1
#define TREE_ONLY_DUPS 2
......
......@@ -141,9 +141,6 @@ ha_rows filesort(THD *thd, TABLE *table, SORT_FIELD *sortorder, uint s_length,
/* filesort cannot handle zero-length records. */
DBUG_ASSERT(param.sort_length);
param.ref_length= table->file->ref_length;
param.min_dupl_count= 0;
param.addon_field= 0;
param.addon_length= 0;
if (!(table->file->ha_table_flags() & HA_FAST_KEY_READ) &&
!table->fulltext_searched && !sort_positions)
{
......@@ -1197,8 +1194,11 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
QUEUE queue;
qsort2_cmp cmp;
void *first_cmp_arg;
volatile THD::killed_state *killed= &current_thd->killed;
element_count dupl_count;
uchar *src;
THD::killed_state not_killable;
uchar *unique_buff= param->unique_buff;
volatile THD::killed_state *killed= &current_thd->killed;
DBUG_ENTER("merge_buffers");
status_var_increment(current_thd->status_var.filesort_merge_passes);
......@@ -1213,13 +1213,13 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
rec_length= param->rec_length;
res_length= param->res_length;
sort_length= param->sort_length;
element_count dupl_count;
uchar *src;
uint dupl_count_ofs= rec_length-sizeof(element_count);
uint min_dupl_count= param->min_dupl_count;
offset= rec_length-
(flag && min_dupl_count ? sizeof(dupl_count) : 0)-res_length;
bool check_dupl_count= flag && min_dupl_count;
offset= (rec_length-
(flag && min_dupl_count ? sizeof(dupl_count) : 0)-res_length);
uint wr_len= flag ? res_length : rec_length;
uint wr_offset= flag ? offset : 0;
maxcount= (ulong) (param->keys/((uint) (Tb-Fb) +1));
to_start_filepos= my_b_tell(to_file);
strpos= sort_buffer;
......@@ -1228,7 +1228,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
/* The following will fire if there is not enough space in sort_buffer */
DBUG_ASSERT(maxcount!=0);
if (param->unique_buff)
if (unique_buff)
{
cmp= param->compare;
first_cmp_arg= (void *) &param->cmp_context;
......@@ -1253,25 +1253,22 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
queue_insert(&queue, (uchar*) buffpek);
}
if (param->unique_buff)
if (unique_buff)
{
/*
Called by Unique::get()
Copy the first argument to param->unique_buff for unique removal.
Copy the first argument to unique_buff for unique removal.
Store it also in 'to_file'.
This is safe as we know that there is always more than one element
in each block to merge (This is guaranteed by the Unique:: algorithm
*/
buffpek= (BUFFPEK*) queue_top(&queue);
memcpy(param->unique_buff, buffpek->key, rec_length);
memcpy(unique_buff, buffpek->key, rec_length);
if (min_dupl_count)
memcpy(&dupl_count, param->unique_buff+dupl_count_ofs,
memcpy(&dupl_count, unique_buff+dupl_count_ofs,
sizeof(dupl_count));
buffpek->key+= rec_length;
if (! --buffpek->mem_count)
{
if (!(error= (int) read_to_buffer(from_file,buffpek,
if (!(error= (int) read_to_buffer(from_file, buffpek,
rec_length)))
{
VOID(queue_remove(&queue,0));
......@@ -1297,7 +1294,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
src= buffpek->key;
if (cmp) // Remove duplicates
{
if (!(*cmp)(first_cmp_arg, &(param->unique_buff),
if (!(*cmp)(first_cmp_arg, &unique_buff,
(uchar**) &buffpek->key))
{
if (min_dupl_count)
......@@ -1310,24 +1307,32 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
}
if (min_dupl_count)
{
memcpy(param->unique_buff+dupl_count_ofs, &dupl_count,
memcpy(unique_buff+dupl_count_ofs, &dupl_count,
sizeof(dupl_count));
}
src= param->unique_buff;
src= unique_buff;
}
if (!flag || !min_dupl_count || dupl_count >= min_dupl_count)
/*
Do not write into the output file if this is the final merge called
for a Unique object used for intersection and dupl_count is less
than min_dupl_count.
If the Unique object is used to intersect N sets of unique elements
then for any element:
dupl_count >= N <=> the element is occurred in each of these N sets.
*/
if (!check_dupl_count || dupl_count >= min_dupl_count)
{
if (my_b_write(to_file, src+(flag ? offset : 0), wr_len))
if (my_b_write(to_file, src+wr_offset, wr_len))
{
error=1; goto err; /* purecov: inspected */
}
}
if (cmp)
{
memcpy(param->unique_buff, (uchar*) buffpek->key, rec_length);
memcpy(unique_buff, (uchar*) buffpek->key, rec_length);
if (min_dupl_count)
memcpy(&dupl_count, param->unique_buff+dupl_count_ofs,
memcpy(&dupl_count, unique_buff+dupl_count_ofs,
sizeof(dupl_count));
}
if (!--max_rows)
......@@ -1340,7 +1345,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
buffpek->key+= rec_length;
if (! --buffpek->mem_count)
{
if (!(error= (int) read_to_buffer(from_file,buffpek,
if (!(error= (int) read_to_buffer(from_file, buffpek,
rec_length)))
{
VOID(queue_remove(&queue,0));
......@@ -1363,7 +1368,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
*/
if (cmp)
{
if (!(*cmp)(first_cmp_arg, &(param->unique_buff), (uchar**) &buffpek->key))
if (!(*cmp)(first_cmp_arg, &unique_buff, (uchar**) &buffpek->key))
{
if (min_dupl_count)
{
......@@ -1376,13 +1381,13 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
}
if (min_dupl_count)
memcpy(param->unique_buff+dupl_count_ofs, &dupl_count,
memcpy(unique_buff+dupl_count_ofs, &dupl_count,
sizeof(dupl_count));
if (!flag || !min_dupl_count || dupl_count >= min_dupl_count)
if (!check_dupl_count || dupl_count >= min_dupl_count)
{
src= param->unique_buff;
if (my_b_write(to_file, src+(flag ? offset : 0), wr_len))
src= unique_buff;
if (my_b_write(to_file, src+wr_offset, wr_len))
{
error=1; goto err; /* purecov: inspected */
}
......@@ -1404,7 +1409,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
max_rows-= buffpek->mem_count;
if (flag == 0)
{
if (my_b_write(to_file,(uchar*) buffpek->key,
if (my_b_write(to_file, (uchar*) buffpek->key,
(rec_length*buffpek->mem_count)))
{
error= 1; goto err; /* purecov: inspected */
......@@ -1418,11 +1423,12 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
src != end ;
src+= rec_length)
{
if (flag && min_dupl_count &&
memcmp(&min_dupl_count, src+dupl_count_ofs,
sizeof(dupl_count_ofs))<0)
continue;
if (check_dupl_count)
{
memcpy((uchar *) &dupl_count, src+dupl_count_ofs, sizeof(dupl_count));
if (dupl_count < min_dupl_count)
continue;
}
if (my_b_write(to_file, src, wr_len))
{
error=1; goto err;
......@@ -1430,7 +1436,7 @@ int merge_buffers(SORTPARAM *param, IO_CACHE *from_file,
}
}
}
while ((error=(int) read_to_buffer(from_file,buffpek, rec_length))
while ((error=(int) read_to_buffer(from_file, buffpek, rec_length))
!= -1 && error != 0);
end:
......
......@@ -340,6 +340,9 @@ class Default_object_creation_ctx : public Object_creation_ctx
*/
#define TIME_FOR_COMPARE_ROWID (TIME_FOR_COMPARE*100)
/* cost1 is better that cost2 only if cost1 + COST_EPS < cost2 */
#define COST_EPS 0.001
/*
For sequential disk seeks the cost formula is:
DISK_SEEK_BASE_COST + DISK_SEEK_PROP_COST * #blocks_to_skip
......
......@@ -1735,11 +1735,11 @@ QUICK_RANGE_SELECT::~QUICK_RANGE_SELECT()
}
QUICK_INDEX_MERGE_SELECT::QUICK_INDEX_MERGE_SELECT(THD *thd_param,
TABLE *table)
QUICK_INDEX_SORT_SELECT::QUICK_INDEX_SORT_SELECT(THD *thd_param,
TABLE *table)
:unique(NULL), pk_quick_select(NULL), thd(thd_param)
{
DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::QUICK_INDEX_MERGE_SELECT");
DBUG_ENTER("QUICK_INDEX_SORT_SELECT::QUICK_INDEX_SORT_SELECT");
index= MAX_KEY;
head= table;
bzero(&read_record, sizeof(read_record));
......@@ -1747,95 +1747,37 @@ QUICK_INDEX_MERGE_SELECT::QUICK_INDEX_MERGE_SELECT(THD *thd_param,
DBUG_VOID_RETURN;
}
int QUICK_INDEX_MERGE_SELECT::init()
int QUICK_INDEX_SORT_SELECT::init()
{
DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::init");
DBUG_ENTER("QUICK_INDEX_SORT_SELECT::init");
DBUG_RETURN(0);
}
int QUICK_INDEX_MERGE_SELECT::reset()
int QUICK_INDEX_SORT_SELECT::reset()
{
DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::reset");
DBUG_RETURN (read_keys_and_merge());
DBUG_ENTER("QUICK_INDEX_SORT_SELECT::reset");
DBUG_RETURN(read_keys_and_merge());
}
bool
QUICK_INDEX_MERGE_SELECT::push_quick_back(QUICK_RANGE_SELECT *quick_sel_range)
QUICK_INDEX_SORT_SELECT::push_quick_back(QUICK_RANGE_SELECT *quick_sel_range)
{
/*
Save quick_select that does scan on clustered primary key as it will be
processed separately.
*/
if (head->file->primary_key_is_clustered() &&
quick_sel_range->index == head->s->primary_key)
pk_quick_select= quick_sel_range;
else
return quick_selects.push_back(quick_sel_range);
return 0;
}
QUICK_INDEX_MERGE_SELECT::~QUICK_INDEX_MERGE_SELECT()
{
List_iterator_fast<QUICK_RANGE_SELECT> quick_it(quick_selects);
QUICK_RANGE_SELECT* quick;
DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::~QUICK_INDEX_MERGE_SELECT");
delete unique;
quick_it.rewind();
while ((quick= quick_it++))
quick->file= NULL;
quick_selects.delete_elements();
delete pk_quick_select;
/* It's ok to call the next two even if they are already deinitialized */
end_read_record(&read_record);
free_io_cache(head);
free_root(&alloc,MYF(0));
DBUG_VOID_RETURN;
}
QUICK_INDEX_INTERSECT_SELECT::QUICK_INDEX_INTERSECT_SELECT(THD *thd_param,
TABLE *table)
:unique(NULL), pk_quick_select(NULL), thd(thd_param)
{
DBUG_ENTER("QUICK_INDEX_INTERSECT_SELECT::QUICK_INDEX_INTERSECT_SELECT");
index= MAX_KEY;
head= table;
bzero(&read_record, sizeof(read_record));
init_sql_alloc(&alloc, thd->variables.range_alloc_block_size, 0);
DBUG_VOID_RETURN;
}
int QUICK_INDEX_INTERSECT_SELECT::init()
{
DBUG_ENTER("QUICK_INDEX_INTERSECT_SELECT::init");
DBUG_RETURN(0);
}
int QUICK_INDEX_INTERSECT_SELECT::reset()
{
DBUG_ENTER("QUICK_INDEX_INTERSECT_SELECT::reset");
DBUG_RETURN (read_keys_and_merge());
}
bool
QUICK_INDEX_INTERSECT_SELECT::push_quick_back(QUICK_RANGE_SELECT *quick_sel_range)
{
/*
Save quick_select that does scan on clustered primary key as it will be
processed separately.
*/
DBUG_ENTER("QUICK_INDEX_SORT_SELECT::push_quick_back");
if (head->file->primary_key_is_clustered() &&
quick_sel_range->index == head->s->primary_key)
{
/* A quick_select over a clustered primary key is handled specifically */
pk_quick_select= quick_sel_range;
else
return quick_selects.push_back(quick_sel_range);
return 0;
DBUG_RETURN(0);
}
DBUG_RETURN(quick_selects.push_back(quick_sel_range));
}
QUICK_INDEX_INTERSECT_SELECT::~QUICK_INDEX_INTERSECT_SELECT()
QUICK_INDEX_SORT_SELECT::~QUICK_INDEX_SORT_SELECT()
{
List_iterator_fast<QUICK_RANGE_SELECT> quick_it(quick_selects);
QUICK_RANGE_SELECT* quick;
DBUG_ENTER("QUICK_INDEX_INTERSECT_SELECT::~QUICK_INDEX_INTERSECT_SELECT");
DBUG_ENTER("QUICK_INDEX_SORT_SELECT::~QUICK_INDEX_SORT_SELECT");
delete unique;
quick_it.rewind();
while ((quick= quick_it++))
......@@ -1849,8 +1791,6 @@ QUICK_INDEX_INTERSECT_SELECT::~QUICK_INDEX_INTERSECT_SELECT()
DBUG_VOID_RETURN;
}
QUICK_ROR_INTERSECT_SELECT::QUICK_ROR_INTERSECT_SELECT(THD *thd_param,
TABLE *table,
bool retrieve_full_rows,
......@@ -2655,6 +2595,8 @@ class TRP_INDEX_INTERSECT : public TABLE_READ_PLAN
MEM_ROOT *parent_alloc);
TRP_RANGE **range_scans; /* array of ptrs to plans of intersected scans */
TRP_RANGE **range_scans_end; /* end of the array */
/* keys whose scans are to be filtered by cpk conditions */
key_map filtered_scans;
};
......@@ -2738,6 +2680,9 @@ typedef struct st_index_scan_info
KEY *key_info;
uint used_key_parts;
/* Estimate of # records filtered out by intersection with cpk */
ha_rows filtered_out;
/* Bitmap of fields used in index intersection */
MY_BITMAP used_fields;
/* Fields used in the query and covered by ROR scan. */
......@@ -3071,15 +3016,20 @@ int SQL_SELECT::test_quick_select(THD *thd, key_map keys_to_use,
best_trp= rori_trp;
}
}
if (optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE) &&
/*
Do not look for an index intersection plan if there is a covering
index. The scan by this covering index will be always cheaper than
any index intersection.
*/
if (param.table->covering_keys.is_clear_all() &&
optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE) &&
optimizer_flag(thd, OPTIMIZER_SWITCH_INDEX_MERGE_SORT_INTERSECT))
{
if ((intersect_trp= get_best_index_intersect(&param, tree,
best_read_time)))
{
best_trp= intersect_trp;
best_read_time= best_trp->read_cost;
best_read_time= best_trp->read_cost;
}
}
......@@ -4756,7 +4706,7 @@ TABLE_READ_PLAN *merge_same_index_scans(PARAM *param, SEL_IMERGE *imerge,
intersection plan.
*/
typedef struct st_common_index_intersection_info
typedef struct st_common_index_intersect_info
{
PARAM *param; /* context info for range optimizations */
uint key_size; /* size of a ROWID element stored in Unique object */
......@@ -4767,10 +4717,6 @@ typedef struct st_common_index_intersection_info
INDEX_SCAN_INFO *cpk_scan; /* clustered primary key used in intersection */
bool in_memory; /* unique object for intersection is completely in memory */
/* estimate of the number of records filtered out from the first scan by
ranges for the clustered primary key scan (cpk_scan) */
ha_rows filtered_out_records;
double filter_cost; /* cost of checking the the cpk_scan rabhe conditions */
INDEX_SCAN_INFO **search_scans; /* scans possibly included in intersect */
uint n_search_scans; /* number of elements in search_scans */
......@@ -4781,10 +4727,12 @@ typedef struct st_common_index_intersection_info
ha_rows best_records;
uint best_length; /* number of indexes in the current best intersection */
INDEX_SCAN_INFO **best_intersect; /* the current best index intersection */
/* scans from the best intersect to be filtrered by cpk conditions */
key_map filtered_scans;
uint *buff_elems; /* buffer to calculate cost of index intersection */
} COMMON_INDEX_INTERSECTION_INFO;
} COMMON_INDEX_INTERSECT_INFO;
/*
......@@ -4793,25 +4741,28 @@ typedef struct st_common_index_intersection_info
check_index_intersect_extension.
*/
typedef struct st_partial_index_intersection_info
typedef struct st_partial_index_intersect_info
{
COMMON_INDEX_INTERSECTION_INFO *common_info; /* shared by index intersects */
COMMON_INDEX_INTERSECT_INFO *common_info; /* shared by index intersects */
uint length; /* number of index scans in the partial intersection */
ha_rows records; /* estimate of the number of records in intersection */
double cost; /* cost of the partial index intersection */
/* estimate of total number of records in all index scans of intersection */
ha_rows records_in_scans;
/* estimate of total number of records of all scans of the partial index
intersect sent to the Unique object used for the intersection */
ha_rows records_sent_to_unique;
/* total cost of the scans of indexes from the partial index intersection */
double index_read_cost;
bool with_cpk_filter; /* cpk filter is to be used for the first scan */
bool use_cpk_filter; /* cpk filter is to be used for this scan */
bool in_memory; /* uses unique object in memory */
double in_memory_cost; /* cost of using unique object in memory */
key_map filtered_scans; /* scans to be filtered by cpk conditions */
MY_BITMAP *intersect_fields; /* bitmap of fields used in intersection */
} PARTIAL_INDEX_INTERSECTION_INFO;
} PARTIAL_INDEX_INTERSECT_INFO;
/* Check whether two indexes have the same first n components */
......@@ -4856,6 +4807,20 @@ int cmp_intersect_index_scan(INDEX_SCAN_INFO **a, INDEX_SCAN_INFO **b)
}
static inline
void set_field_bitmap_for_index_prefix(MY_BITMAP *field_bitmap,
KEY_PART_INFO *key_part,
uint used_key_parts)
{
bitmap_clear_all(field_bitmap);
for (KEY_PART_INFO *key_part_end= key_part+used_key_parts;
key_part < key_part_end; key_part++)
{
bitmap_set_bit(field_bitmap, key_part->fieldnr-1);
}
}
/*
Round up table cardinality read from statistics provided by engine.
This function should go away when mysql test will allow to handle
......@@ -4878,6 +4843,10 @@ ha_rows get_table_cardinality_for_index_intersect(TABLE *table)
}
static
ha_rows records_in_index_intersect_extension(PARTIAL_INDEX_INTERSECT_INFO *curr,
INDEX_SCAN_INFO *ext_index_scan);
/*
Prepare to search for the best index intersection
......@@ -4889,7 +4858,7 @@ ha_rows get_table_cardinality_for_index_intersect(TABLE *table)
init OUT info for an initial pseudo step of the intersection plans
cutoff_cost cut off cost of the interesting index intersection
NOTES
DESCRIPTION
The function initializes all fields of the structure 'common' to be used
when searching for the best intersection plan. It also allocates
memory to store the most cheap index intersection.
......@@ -4902,11 +4871,12 @@ ha_rows get_table_cardinality_for_index_intersect(TABLE *table)
static
bool prepare_search_best_index_intersect(PARAM *param,
SEL_TREE *tree,
COMMON_INDEX_INTERSECTION_INFO *common,
PARTIAL_INDEX_INTERSECTION_INFO *init,
COMMON_INDEX_INTERSECT_INFO *common,
PARTIAL_INDEX_INTERSECT_INFO *init,
double cutoff_cost)
{
uint i;
uint n_search_scans;
double cost;
INDEX_SCAN_INFO **index_scan;
INDEX_SCAN_INFO **scan_ptr;
......@@ -4917,9 +4887,9 @@ bool prepare_search_best_index_intersect(PARAM *param,
if (!n_index_scans)
return 1;
bzero(init, sizeof(PARTIAL_INDEX_INTERSECTION_INFO));
bzero(init, sizeof(*init));
init->common_info= common;
init->cost= cutoff_cost+2*0.01;
init->cost= cutoff_cost;
common->param= param;
common->key_size= table->file->ref_length;
......@@ -4927,20 +4897,24 @@ bool prepare_search_best_index_intersect(PARAM *param,
common->max_memory_size= param->thd->variables.sortbuff_size;
common->cutoff_cost= cutoff_cost;
common->cpk_scan= NULL;
common->filtered_out_records= 0;
common->table_cardinality=
get_table_cardinality_for_index_intersect(table);
if (n_index_scans <= 1)
return TRUE;
for (i=0, index_scan= tree->index_scans; i < n_index_scans; i++, index_scan++)
{
if ((*index_scan)->keynr == table->s->primary_key &&
table->file->primary_key_is_clustered())
{
common->cpk_scan= cpk_scan= *index_scan;
break;
if (table->file->primary_key_is_clustered())
{
INDEX_SCAN_INFO **index_scan_end;
index_scan= tree->index_scans;
index_scan_end= index_scan+n_index_scans;
for ( ; index_scan < index_scan_end; index_scan++)
{
if ((*index_scan)->keynr == table->s->primary_key)
{
common->cpk_scan= cpk_scan= *index_scan;
break;
}
}
}
......@@ -4973,14 +4947,21 @@ bool prepare_search_best_index_intersect(PARAM *param,
for (scan_ptr= selected_index_scans; *scan_ptr ; scan_ptr++)
{
/*
When we have range conditions for two different indexes with the same
beginning it does not make sense to consider both of them for index
intersection if the range conditions are covered by common initial
components of the indexes. Actually in this case the indexes are
guaranteed to have the same range conditions.
*/
if ((*scan_ptr)->used_key_parts == used_key_parts &&
same_index_prefix((*scan_ptr)->key_info, key_info, used_key_parts))
break;
}
if (!*scan_ptr || cost < (*scan_ptr)->index_read_cost)
{
(*index_scan)->index_read_cost= cost;
*scan_ptr= *index_scan;
(*scan_ptr)->index_read_cost= cost;
}
}
......@@ -4992,15 +4973,16 @@ bool prepare_search_best_index_intersect(PARAM *param,
return TRUE;
records_in_scans+= (*scan_ptr)->records;
}
n_search_scans= i;
if (cpk_scan && create_fields_bitmap(param, &cpk_scan->used_fields))
return TRUE;
if (!(common->n_search_scans= i))
if (!(common->n_search_scans= n_search_scans))
return TRUE;
common->best_uses_cpk= FALSE;
common->best_cost= cutoff_cost + 0.01;
common->best_cost= cutoff_cost + COST_EPS;
common->best_length= 0;
if (!(common->best_intersect=
......@@ -5009,7 +4991,7 @@ bool prepare_search_best_index_intersect(PARAM *param,
(i + test(cpk_scan != NULL)))))
return TRUE;
uint calc_cost_buff_size=
size_t calc_cost_buff_size=
Unique::get_cost_calc_buff_size(records_in_scans,
common->key_size,
common->max_memory_size);
......@@ -5017,25 +4999,167 @@ bool prepare_search_best_index_intersect(PARAM *param,
calc_cost_buff_size)))
return TRUE;
my_qsort(selected_index_scans, i, sizeof(INDEX_SCAN_INFO *),
my_qsort(selected_index_scans, n_search_scans, sizeof(INDEX_SCAN_INFO *),
(qsort_cmp) cmp_intersect_index_scan);
if (cpk_scan)
{
PARTIAL_INDEX_INTERSECT_INFO curr;
set_field_bitmap_for_index_prefix(&cpk_scan->used_fields,
cpk_scan->key_info->key_part,
cpk_scan->used_key_parts);
curr.common_info= common;
curr.intersect_fields= &cpk_scan->used_fields;
curr.records= cpk_scan->records;
curr.length= 1;
for (scan_ptr=selected_index_scans, i= 0; *scan_ptr; scan_ptr++, i++)
{
ha_rows scan_records= (*scan_ptr)->records;
ha_rows records= records_in_index_intersect_extension(&curr, *scan_ptr);
(*scan_ptr)->filtered_out= records >= scan_records ?
0 : scan_records-records;
}
}
else
{
for (scan_ptr=selected_index_scans, i= 0; *scan_ptr; scan_ptr++, i++)
(*scan_ptr)->filtered_out= 0;
}
return FALSE;
}
static inline
void set_field_bitmap_for_index_prefix(MY_BITMAP *field_bitmap,
KEY_PART_INFO *key_part,
uint used_key_parts)
{
bitmap_clear_all(field_bitmap);
for (KEY_PART_INFO *key_part_end= key_part+used_key_parts;
key_part < key_part_end; key_part++)
{
bitmap_set_bit(field_bitmap, key_part->fieldnr-1);
}
}
/*
On Estimation of the Number of Records in an Index Intersection
===============================================================
Consider query Q over table t. Let C be the WHERE condition of this query,
and, idx1(a1_1,...,a1_k1) and idx2(a2_1,...,a2_k2) be some indexes defined
on table t.
Let rt1 and rt2 be the range trees extracted by the range optimizer from C
for idx1 and idx2 respectively.
Let #t be the estimate of the number of records in table t provided for the
optimizer.
Let #r1 and #r2 be the estimates of the number of records in the range trees
rt1 and rt2, respectively, obtained by the range optimizer.
We need to get an estimate for the number of records in the index
intersection of rt1 and rt2. In other words, we need to estimate the
cardinality of the set of records that are in both trees. Let's designate
this number by #r.
If we do not make any assumptions then we can only state that
#r<=min(#r1,#r2).
With this estimate we can't say that the index intersection scan will be
cheaper than the cheapest index scan.
Let Rt1 and Rt2 be AND/OR conditions representing rt and rt2 respectively.
The probability that a record belongs to rt1 is sel(Rt1)=#r1/#t.
The probability that a record belongs to rt2 is sel(Rt2)=#r2/#t.
If we assume that the values in columns of idx1 and idx2 are independent
then #r/#t=sel(Rt1&Rt2)=sel(Rt1)*sel(Rt2)=(#r1/#t)*(#r2/#t).
So in this case we have: #r=#r1*#r2/#t.
The above assumption of independence of the columns in idx1 and idx2 means
that:
- all columns are different
- values from one column do not correlate with values from any other column.
We can't help with the case when column correlate with each other.
Yet, if they are assumed to be uncorrelated the value of #r theoretically can
be evaluated . Unfortunately this evaluation, in general, is rather complex.
Let's consider two indexes idx1:(dept, manager), idx2:(dept, building)
over table 'employee' and two range conditions over these indexes:
Rt1: dept=10 AND manager LIKE 'S%'
Rt2: dept=10 AND building LIKE 'L%'.
We can state that:
sel(Rt1&Rt2)=sel(dept=10)*sel(manager LIKE 'S%')*sel(building LIKE 'L%')
=sel(Rt1)*sel(Rt2)/sel(dept=10).
sel(Rt1/2_0:dept=10) can be estimated if we know the cardinality #r1_0 of
the range for sub-index idx1_0 (dept) of the index idx1 or the cardinality
#rt2_0 of the same range for sub-index idx2_0(dept) of the index idx2.
The current code does not make an estimate either for #rt1_0, or for #rt2_0,
but it can be adjusted to provide those numbers.
Alternatively, min(rec_per_key) for (dept) could be used to get an upper
bound for the value of sel(Rt1&Rt2). Yet this statistics is not provided
now.
Let's consider two other indexes idx1:(dept, last_name),
idx2:(first_name, last_name) and two range conditions over these indexes:
Rt1: dept=5 AND last_name='Sm%'
Rt2: first_name='Robert' AND last_name='Sm%'.
sel(Rt1&Rt2)=sel(dept=5)*sel(last_name='Sm5')*sel(first_name='Robert')
=sel(Rt2)*sel(dept=5)
Here max(rec_per_key) for (dept) could be used to get an upper bound for
the value of sel(Rt1&Rt2).
When the intersected indexes have different major columns, but some
minor column are common the picture may be more complicated.
Let's consider the following range conditions for the same indexes as in
the previous example:
Rt1: (Rt11: dept=5 AND last_name='So%')
OR
(Rt12: dept=7 AND last_name='Saw%')
Rt2: (Rt21: first_name='Robert' AND last_name='Saw%')
OR
(Rt22: first_name='Bob' AND last_name='So%')
Here we have:
sel(Rt1&Rt2)= sel(Rt11)*sel(Rt21)+sel(Rt22)*sel(dept=5) +
sel(Rt21)*sel(dept=7)+sel(Rt12)*sel(Rt22)
Now consider the range condition:
Rt1_0: (dept=5 OR dept=7)
For this condition we can state that:
sel(Rt1_0&Rt2)=(sel(dept=5)+sel(dept=7))*(sel(Rt21)+sel(Rt22))=
sel(dept=5)*sel(Rt21)+sel(dept=7)*sel(Rt21)+
sel(dept=5)*sel(Rt22)+sel(dept=7)*sel(Rt22)=
sel(dept=5)*sel(Rt21)+sel(Rt21)*sel(dept=7)+
sel(Rt22)*sel(dept=5)+sel(dept=7)*sel(Rt22) >
sel(Rt11)*sel(Rt21)+sel(Rt22)*sel(dept=5)+
sel(Rt21)*sel(dept=7)+sel(Rt12)*sel(Rt22) >
sel(Rt1 & Rt2)
We've just demonstrated for an example what is intuitively almost obvious
in general. We can remove the ending parts fromrange trees getting less
selective range conditions for sub-indexes.
So if not a most major component with the number k of an index idx is
encountered in the index with which we intersect we can use the sub-index
idx_k-1 that includes the components of idx up to the i-th component and
the range tree for idx_k-1 to make an upper bound estimate for the number
of records in the index intersection.
The range tree for idx_k-1 we use here is the subtree of the original range
tree for idx that contains only parts from the first k-1 components.
As it was mentioned above the range optimizer currently does not provide
an estimate for the number of records in the ranges for sub-indexes.
However, some reasonable upper bound estimate can be obtained.
Let's consider the following range tree:
Rt: (first_name='Robert' AND last_name='Saw%')
OR
(first_name='Bob' AND last_name='So%')
Let #r be the number of records in Rt. Let f_1 be the fan-out of column
last_name:
f_1 = rec_per_key[first_name]/rec_per_key[last_name].
The the number of records in the range tree:
Rt_0: (first_name='Robert' OR first_name='Bob')
for the sub-index (first_name) is not greater than max(#r*f_1, #t).
Strictly speaking, we can state only that it's not greater than
max(#r*max_f_1, #t), where
max_f_1= max_rec_per_key[first_name]/min_rec_per_key[last_name].
Yet, if #r/#t is big enough (and this is the case of an index intersection,
because using this index range with a single index scan is cheaper than
the cost of the intersection when #r/#t is small) then almost safely we
can use here f_1 instead of max_f_1.
The above considerations can be used in future development. Now, they are
used partly in the function that provides a rough upper bound estimate for
the number of records in an index intersection that follow below.
*/
/*
Estimate the number of records selected by an extension a partial intersection
......@@ -5045,101 +5169,94 @@ void set_field_bitmap_for_index_prefix(MY_BITMAP *field_bitmap,
curr partial intersection plan to be extended
ext_index_scan the evaluated extension of this partial plan
NOTES
The function figures out how many records can be expected if the
index scans from curr is intersected with ext_index_scan
DESCRIPTION
The function provides an estimate for the number of records in the
intersection of the partial index intersection curr with the index
ext_index_scan. If all intersected indexes does not have common columns
then the function returns an exact estimate (assuming there are no
correlations between values in the columns). If the intersected indexes
have common columns the function returns an upper bound for the number
of records in the intersection provided that the intersection of curr
with ext_index_scan can is expected to have less records than the expected
number of records in the partial intersection curr. In this case the
function also assigns the bitmap of the columns in the extended
intersection to ext_index_scan->used_fields.
If the function cannot expect that the number of records in the extended
intersection is less that the expected number of records #r in curr then
the function returns a number bigger than #r.
NOTES
See the comment before the desription of the function that explains the
reasoning used by this function.
RETURN
The expected number of rows in the extended index intersection
*/
static
ha_rows records_in_index_intersect_extension(PARTIAL_INDEX_INTERSECTION_INFO *curr,
ha_rows records_in_index_intersect_extension(PARTIAL_INDEX_INTERSECT_INFO *curr,
INDEX_SCAN_INFO *ext_index_scan)
{
KEY *key_info= ext_index_scan->key_info;
KEY_PART_INFO* key_part= key_info->key_part;
KEY_PART_INFO* key_part_end= key_part+ext_index_scan->used_key_parts;
uint key_parts= key_info->key_parts;
uint used_key_parts= ext_index_scan->used_key_parts;
MY_BITMAP *used_fields= &ext_index_scan->used_fields;
if (!curr->length)
{
set_field_bitmap_for_index_prefix(used_fields, key_part,
ext_index_scan->used_key_parts);
/*
If this the first index in the intersection just mark the
fields in the used_fields bitmap and return the expected
number of records in the range scan for the index provided
by the range optimizer.
*/
set_field_bitmap_for_index_prefix(used_fields, key_part, used_key_parts);
return ext_index_scan->records;
}
else
uint i;
bool better_selectivity= FALSE;
ha_rows records= curr->records;
MY_BITMAP *curr_intersect_fields= curr->intersect_fields;
for (i= 0; i < used_key_parts; i++, key_part++)
{
if (bitmap_is_set(curr_intersect_fields, key_part->fieldnr-1))
break;
}
if (i)
{
bool better_selectivity= FALSE;
ha_rows table_cardinality= curr->common_info->table_cardinality;
ha_rows records= curr->records;
bitmap_copy(used_fields, curr->intersect_fields);
records= (ha_rows) ((double) records / table_cardinality *
ext_index_scan->records);
set_if_bigger(records, 1);
for (uint i= 0 ; key_part < key_part_end; i++, key_part++)
{
if (bitmap_is_set(used_fields, key_part->fieldnr-1))
{
ulong *rec_per_key= key_info->rec_per_key+i;
ulong f1= rec_per_key[0] ? rec_per_key[0] : 1;
ulong f2= i+1 < key_parts && rec_per_key[1] ? rec_per_key[1] : 1;
records= (ha_rows) ((double) records / f2 * f1);
}
else
{
better_selectivity= TRUE;
ha_rows ext_records= ext_index_scan->records;
if (i < used_key_parts)
{
ulong *rec_per_key= key_info->rec_per_key+i-1;
ulong f1= rec_per_key[0] ? rec_per_key[0] : 1;
ulong f2= rec_per_key[1] ? rec_per_key[1] : 1;
ext_records= (ha_rows) ((double) ext_records / f2 * f1);
}
if (ext_records < table_cardinality)
{
better_selectivity= TRUE;
records= (ha_rows) ((double) records / table_cardinality *
ext_records);
bitmap_copy(used_fields, curr_intersect_fields);
key_part= key_info->key_part;
for (uint j= 0; j < used_key_parts; j++, key_part++)
bitmap_set_bit(used_fields, key_part->fieldnr-1);
}
}
return !better_selectivity ? curr->records+1 :
!records ? 1 : records;
}
}
static inline
double get_unique_intersect_cost(COMMON_INDEX_INTERSECTION_INFO *common,
uint length, ha_rows records_in_scans,
ha_rows filtered_out_records,
ha_rows last_index_records,
bool *in_memory,
double *in_memory_cost)
{
double cost;
if (length > 1 && *in_memory)
{
ha_rows records_in_first_scan= common->search_scans[0]->records;
ha_rows elems_in_tree= records_in_first_scan-filtered_out_records;
*in_memory_cost+= Unique::get_search_cost(elems_in_tree,
common->compare_factor) *
last_index_records;
cost= *in_memory_cost;
}
else
{
ha_rows records_to_intersect= records_in_scans-filtered_out_records;
cost= Unique::get_use_cost(common->buff_elems,
records_to_intersect,
common->key_size,
common->max_memory_size,
common->compare_factor,
TRUE, in_memory);
if (*in_memory)
*in_memory_cost= cost;
}
return cost;
return !better_selectivity ? records+1 :
!records ? 1 : records;
}
static inline
double get_cpk_filter_cost(INDEX_SCAN_INFO *index_scan,
double get_cpk_filter_cost(ha_rows filtered_records,
INDEX_SCAN_INFO *cpk_scan,
double compare_factor)
{
return log((double) (cpk_scan->range_count+1)) / (compare_factor * M_LN2) *
index_scan->records;
filtered_records;
}
......@@ -5152,104 +5269,112 @@ double get_cpk_filter_cost(INDEX_SCAN_INFO *index_scan,
ext_index_scan a possible extension of this plan to be checked
next OUT the structure to be filled for the extended plan
NOTES
DESCRIPTION
The function checks whether it makes sense to extend the index
intersection plan adding the index ext_index_scan, and, if this
the case, the function fills in the structure fir the extended plan.
the case, the function fills in the structure for the extended plan.
RETURN
TRUE if the given plan makes to extend
TRUE if it makes sense to extend the given plan
FALSE otherwise
*/
static
bool check_index_intersect_extension(PARTIAL_INDEX_INTERSECTION_INFO *curr,
bool check_index_intersect_extension(PARTIAL_INDEX_INTERSECT_INFO *curr,
INDEX_SCAN_INFO *ext_index_scan,
PARTIAL_INDEX_INTERSECTION_INFO *next)
PARTIAL_INDEX_INTERSECT_INFO *next)
{
ha_rows records;
ha_rows records_in_scans;
ha_rows records_sent_to_unique;
double cost;
ha_rows records2= 0;
ha_rows filtered_out_records= 0;
ha_rows index_scan_records= ext_index_scan->records;
COMMON_INDEX_INTERSECTION_INFO *common_info= curr->common_info;
INDEX_SCAN_INFO *cpk_scan= common_info->cpk_scan;
ha_rows ext_index_scan_records= ext_index_scan->records;
ha_rows records_filtered_out_by_cpk= ext_index_scan->filtered_out;
COMMON_INDEX_INTERSECT_INFO *common_info= curr->common_info;
double cutoff_cost= common_info->cutoff_cost;
uint compare_factor= common_info->compare_factor;
uint idx= curr->length;
bool with_cpk_filter= curr->with_cpk_filter;
if (with_cpk_filter)
filtered_out_records= common_info->filtered_out_records;
next->index_read_cost= curr->index_read_cost+ext_index_scan->index_read_cost;
if (next->index_read_cost > cutoff_cost)
return FALSE;
records_in_scans= curr->records_in_scans + index_scan_records;
if ((next->in_memory= curr->in_memory))
next->in_memory_cost= curr->in_memory_cost;
records= records_in_index_intersect_extension(curr, ext_index_scan);
if (idx && records > curr->records)
return FALSE;
next->records= records;
next->intersect_fields= &ext_index_scan->used_fields;
next->filtered_scans= curr->filtered_scans;
cost= get_unique_intersect_cost(common_info, idx+1, records_in_scans,
filtered_out_records, index_scan_records,
&next->in_memory, &next->in_memory_cost);
if (idx == 0 && cpk_scan)
{
next->length= 1;
records2= records_in_index_intersect_extension(next, cpk_scan);
next->length= 0;
if (records2 < records)
filtered_out_records= records-records2;
common_info->filter_cost= get_cpk_filter_cost(ext_index_scan, cpk_scan,
compare_factor);
common_info->filtered_out_records= filtered_out_records;
records_sent_to_unique= curr->records_sent_to_unique;
next->use_cpk_filter= FALSE;
/* Calculate the cost of using a Unique object for index intersection */
if (idx && next->in_memory)
{
/*
All rowids received from the first scan are expected in one unique tree
*/
ha_rows elems_in_tree= common_info->search_scans[0]->records-
common_info->search_scans[0]->filtered_out ;
next->in_memory_cost+= Unique::get_search_cost(elems_in_tree,
common_info->compare_factor)*
ext_index_scan_records;
cost= next->in_memory_cost;
}
next->records_in_scans= records_in_scans;
next->with_cpk_filter= with_cpk_filter;
if (!with_cpk_filter && common_info->filtered_out_records)
else
{
double cost2;
bool in_memory_save= next->in_memory;
if (idx)
{
next->length= curr->length+1;
records2= records_in_index_intersect_extension(next, cpk_scan);
next->length= curr->length;
}
cost2= get_unique_intersect_cost(common_info, idx+1, records_in_scans,
filtered_out_records, index_scan_records,
&next->in_memory, &next->in_memory_cost);
cost2+= common_info->filter_cost;
if (cost2 < cost)
{
cost= cost2;
records= records2;
next->with_cpk_filter= TRUE;
*next->intersect_fields= cpk_scan->used_fields;
}
else
next->in_memory= in_memory_save;
uint *buff_elems= common_info->buff_elems;
uint key_size= common_info->key_size;
uint compare_factor= common_info->compare_factor;
ulonglong max_memory_size= common_info->max_memory_size;
records_sent_to_unique+= ext_index_scan_records;
cost= Unique::get_use_cost(buff_elems, records_sent_to_unique, key_size,
max_memory_size, compare_factor, TRUE,
&next->in_memory);
if (records_filtered_out_by_cpk)
{
/* Check whether using cpk filter for this scan is beneficial */
double cost2;
bool in_memory2;
ha_rows records2= records_sent_to_unique-records_filtered_out_by_cpk;
cost2= Unique::get_use_cost(buff_elems, records2, key_size,
max_memory_size, compare_factor, TRUE,
&in_memory2);
cost2+= get_cpk_filter_cost(ext_index_scan_records, common_info->cpk_scan,
compare_factor);
if (cost > cost2 + COST_EPS)
{
cost= cost2;
next->in_memory= in_memory2;
next->use_cpk_filter= TRUE;
records_sent_to_unique= records2;
}
}
if (next->in_memory)
next->in_memory_cost= cost;
}
if (next->use_cpk_filter)
{
next->filtered_scans.set_bit(ext_index_scan->keynr);
bitmap_union(&ext_index_scan->used_fields,
&common_info->cpk_scan->used_fields);
}
next->records_sent_to_unique= records_sent_to_unique;
records= records_in_index_intersect_extension(curr, ext_index_scan);
if (idx && records > curr->records)
return FALSE;
if (next->use_cpk_filter && curr->filtered_scans.is_clear_all())
records-= records_filtered_out_by_cpk;
next->records= records;
cost+= next->index_read_cost;
if (cost >= cutoff_cost)
return FALSE;
next->records= records;
cost+= get_sweep_read_cost(common_info->param, records);
cost+= get_sweep_read_cost(common_info->param, records);
next->cost= cost;
next->length= curr->length+1;
......@@ -5265,28 +5390,29 @@ bool check_index_intersect_extension(PARTIAL_INDEX_INTERSECTION_INFO *curr,
find_index_intersect_best_extension()
curr partial intersection to evaluate all possible extension for
NOTES
DESCRIPTION
The function tries to extend the partial plan curr in all possible ways
to look for a cheapest index intersection whose cost less than the
cut off value set in curr->common_info.cutoff_cost.
*/
static
void find_index_intersect_best_extension(PARTIAL_INDEX_INTERSECTION_INFO *curr)
void find_index_intersect_best_extension(PARTIAL_INDEX_INTERSECT_INFO *curr)
{
PARTIAL_INDEX_INTERSECTION_INFO next;
COMMON_INDEX_INTERSECTION_INFO *common_info= curr->common_info;
PARTIAL_INDEX_INTERSECT_INFO next;
COMMON_INDEX_INTERSECT_INFO *common_info= curr->common_info;
INDEX_SCAN_INFO **index_scans= common_info->search_scans;
uint idx= curr->length;
INDEX_SCAN_INFO **rem_first_index_scan_ptr= &index_scans[idx];
double cost= curr->cost;
if (cost < common_info->best_cost)
if (cost + COST_EPS < common_info->best_cost)
{
common_info->best_cost= cost;
common_info->best_length= curr->length;
common_info->best_records= curr->records;
common_info->best_uses_cpk= curr->with_cpk_filter;
common_info->filtered_scans= curr->filtered_scans;
common_info->best_uses_cpk= !curr->filtered_scans.is_clear_all();
uint sz= sizeof(INDEX_SCAN_INFO *) * curr->length;
memcpy(common_info->best_intersect, common_info->search_scans, sz);
common_info->cutoff_cost= cost;
......@@ -5320,7 +5446,7 @@ void find_index_intersect_best_extension(PARTIAL_INDEX_INTERSECTION_INFO *curr)
tree tree of ranges for indexes than can be intersected
read_time cut off value for the evaluated plans
NOTES
DESCRIPTION
The function looks for the cheapest index intersection of the range
scans to access a table. The info about the ranges for all indexes
is provided by the range optimizer and is passed through the
......@@ -5343,8 +5469,8 @@ TRP_INDEX_INTERSECT *get_best_index_intersect(PARAM *param, SEL_TREE *tree,
TRP_RANGE **cur_range;
TRP_RANGE **range_scans;
INDEX_SCAN_INFO *index_scan;
COMMON_INDEX_INTERSECTION_INFO common;
PARTIAL_INDEX_INTERSECTION_INFO init;
COMMON_INDEX_INTERSECT_INFO common;
PARTIAL_INDEX_INTERSECT_INFO init;
TRP_INDEX_INTERSECT *intersect_trp= NULL;
TABLE *table= param->table;
......@@ -5383,7 +5509,7 @@ TRP_INDEX_INTERSECT *get_best_index_intersect(PARAM *param, SEL_TREE *tree,
{
TRP_RANGE *trp= *cur_range;
trp->read_cost= index_scan->index_read_cost;
trp->records= index_scan->records;
trp->records= index_scan->records;
trp->is_ror= FALSE;
table->intersect_keys.set_bit(index_scan->keynr);
cur_range++;
......@@ -5415,6 +5541,7 @@ TRP_INDEX_INTERSECT *get_best_index_intersect(PARAM *param, SEL_TREE *tree,
intersect_trp->records= common.best_records;
intersect_trp->range_scans= range_scans;
intersect_trp->range_scans_end= cur_range;
intersect_trp->filtered_scans= common.filtered_scans;
}
DBUG_RETURN(intersect_trp);
}
......@@ -6426,6 +6553,7 @@ QUICK_SELECT_I *TRP_INDEX_INTERSECT::make_quick(PARAM *param,
quick_intersect->records= records;
quick_intersect->read_time= read_cost;
quick_intersect->filtered_scans= filtered_scans;
for (TRP_RANGE **range_scan= range_scans; range_scan != range_scans_end;
range_scan++)
{
......@@ -9827,19 +9955,7 @@ bool QUICK_SELECT_I::is_keys_used(const MY_BITMAP *fields)
return is_key_used(head, index, fields);
}
bool QUICK_INDEX_MERGE_SELECT::is_keys_used(const MY_BITMAP *fields)
{
QUICK_RANGE_SELECT *quick;
List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
while ((quick= it++))
{
if (is_key_used(head, quick->index, fields))
return 1;
}
return 0;
}
bool QUICK_INDEX_INTERSECT_SELECT::is_keys_used(const MY_BITMAP *fields)
bool QUICK_INDEX_SORT_SELECT::is_keys_used(const MY_BITMAP *fields)
{
QUICK_RANGE_SELECT *quick;
List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
......@@ -10002,6 +10118,7 @@ int read_keys_and_merge_scans(THD *thd,
QUICK_RANGE_SELECT *pk_quick_select,
READ_RECORD *read_record,
bool intersection,
key_map *filtered_scans,
Unique **unique_ptr)
{
List_iterator_fast<QUICK_RANGE_SELECT> cur_quick_it(quick_selects);
......@@ -10009,6 +10126,8 @@ int read_keys_and_merge_scans(THD *thd,
int result;
Unique *unique= *unique_ptr;
handler *file= head->file;
bool with_cpk_filter= pk_quick_select != NULL;
DBUG_ENTER("read_keys_and_merge");
/* We're going to just read rowids. */
......@@ -10054,6 +10173,8 @@ int read_keys_and_merge_scans(THD *thd,
{
while ((result= cur_quick->get_next()) == HA_ERR_END_OF_FILE)
{
if (intersection)
with_cpk_filter= filtered_scans->is_set(cur_quick->index);
if (first_quick)
{
first_quick= FALSE;
......@@ -10084,18 +10205,10 @@ int read_keys_and_merge_scans(THD *thd,
if (thd->killed)
goto err;
if (intersection)
{
if (first_quick &&
pk_quick_select && !pk_quick_select->row_in_ranges())
continue;
}
else
{
/* skip row if it will be retrieved by clustered PK scan */
if (pk_quick_select && pk_quick_select->row_in_ranges())
continue;
}
if (with_cpk_filter &&
pk_quick_select->row_in_ranges() != intersection )
continue;
cur_quick->file->position(cur_quick->record);
if (unique->unique_add((char*)cur_quick->file->ref))
goto err;
......@@ -10108,7 +10221,7 @@ int read_keys_and_merge_scans(THD *thd,
*/
result= unique->get(head);
/*
index_merge currently doesn't support "using index" at all
index merge currently doesn't support "using index" at all
*/
head->disable_keyread();
init_read_record(read_record, thd, head, (SQL_SELECT*) 0, 1 , 1, TRUE);
......@@ -10126,7 +10239,7 @@ int QUICK_INDEX_MERGE_SELECT::read_keys_and_merge()
int result;
DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::read_keys_and_merge");
result= read_keys_and_merge_scans(thd, head, quick_selects, pk_quick_select,
&read_record, FALSE, &unique);
&read_record, FALSE, NULL, &unique);
doing_pk_scan= FALSE;
DBUG_RETURN(result);
}
......@@ -10173,8 +10286,8 @@ int QUICK_INDEX_INTERSECT_SELECT::read_keys_and_merge()
int result;
DBUG_ENTER("QUICK_INDEX_INTERSECT_SELECT::read_keys_and_merge");
result= read_keys_and_merge_scans(thd, head, quick_selects, pk_quick_select,
&read_record, TRUE, &unique);
doing_pk_scan= FALSE;
&read_record, TRUE, &filtered_scans,
&unique);
DBUG_RETURN(result);
}
......@@ -10188,7 +10301,6 @@ int QUICK_INDEX_INTERSECT_SELECT::get_next()
result= HA_ERR_END_OF_FILE;
end_read_record(&read_record);
free_io_cache(head);
/* All rows from Unique have been retrieved, do a clustered PK scan */
}
DBUG_RETURN(result);
......@@ -13391,7 +13503,7 @@ void QUICK_RANGE_SELECT::dbug_dump(int indent, bool verbose)
/* purecov: end */
}
void QUICK_INDEX_MERGE_SELECT::dbug_dump(int indent, bool verbose)
void QUICK_INDEX_SORT_SELECT::dbug_dump(int indent, bool verbose)
{
List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
QUICK_RANGE_SELECT *quick;
......@@ -13407,22 +13519,6 @@ void QUICK_INDEX_MERGE_SELECT::dbug_dump(int indent, bool verbose)
fprintf(DBUG_FILE, "%*s}\n", indent, "");
}
void QUICK_INDEX_INTERSECT_SELECT::dbug_dump(int indent, bool verbose)
{
List_iterator_fast<QUICK_RANGE_SELECT> it(quick_selects);
QUICK_RANGE_SELECT *quick;
fprintf(DBUG_FILE, "%*squick index_intersect select\n", indent, "");
fprintf(DBUG_FILE, "%*smerged scans {\n", indent, "");
while ((quick= it++))
quick->dbug_dump(indent+2, verbose);
if (pk_quick_select)
{
fprintf(DBUG_FILE, "%*sclustered PK quick:\n", indent, "");
pk_quick_select->dbug_dump(indent+2, verbose);
}
fprintf(DBUG_FILE, "%*s}\n", indent, "");
}
void QUICK_ROR_INTERSECT_SELECT::dbug_dump(int indent, bool verbose)
{
List_iterator_fast<QUICK_SELECT_WITH_RECORD> it(quick_selects);
......
......@@ -328,6 +328,7 @@ class QUICK_SELECT_I
selects output and/or can produce output suitable for merging.
*/
virtual void add_info_string(String *str) {}
/*
Return 1 if any index used by this quick select
uses field which is marked in passed bitmap.
......@@ -406,9 +407,11 @@ class QUICK_RANGE_SELECT : public QUICK_SELECT_I
QUICK_RANGE_SELECT *pk_quick_select,
READ_RECORD *read_record,
bool intersection,
key_map *filtered_scans,
Unique **unique_ptr);
friend class QUICK_SELECT_DESC;
friend class QUICK_INDEX_SORT_SELECT;
friend class QUICK_INDEX_MERGE_SELECT;
friend class QUICK_INDEX_INTERSECT_SELECT;
friend class QUICK_ROR_INTERSECT_SELECT;
......@@ -464,40 +467,44 @@ class QUICK_RANGE_SELECT_GEOM: public QUICK_RANGE_SELECT
/*
QUICK_INDEX_MERGE_SELECT - index_merge access method quick select.
QUICK_INDEX_SORT_SELECT is the base class for the common functionality of:
- QUICK_INDEX_MERGE_SELECT, access based on multi-index merge/union
- QUICK_INDEX_INTERSECT_SELECT, access based on multi-index intersection
QUICK_INDEX_MERGE_SELECT uses
QUICK_INDEX_SORT_SELECT uses
* QUICK_RANGE_SELECTs to get rows
* Unique class to remove duplicate rows
* Unique class
- to remove duplicate rows for QUICK_INDEX_MERGE_SELECT
- to intersect rows for QUICK_INDEX_INTERSECT_SELECT
INDEX MERGE OPTIMIZER
Current implementation doesn't detect all cases where index_merge could
Current implementation doesn't detect all cases where index merge could
be used, in particular:
* index_merge will never be used if range scan is possible (even if
range scan is more expensive)
* index_merge+'using index' is not supported (this the consequence of
* index merge+'using index' is not supported (this the consequence of
the above restriction)
* If WHERE part contains complex nested AND and OR conditions, some ways
to retrieve rows using index_merge will not be considered. The choice
to retrieve rows using index merge will not be considered. The choice
of read plan may depend on the order of conjuncts/disjuncts in WHERE
part of the query, see comments near imerge_list_or_list and
SEL_IMERGE::or_sel_tree_with_checks functions for details.
* There is no "index_merge_ref" method (but index_merge on non-first
* There is no "index_merge_ref" method (but index merge on non-first
table in join is possible with 'range checked for each record').
See comments around SEL_IMERGE class and test_quick_select for more
details.
ROW RETRIEVAL ALGORITHM
index_merge uses Unique class for duplicates removal. index_merge takes
advantage of Clustered Primary Key (CPK) if the table has one.
The index_merge algorithm consists of two phases:
index merge/intersection uses Unique class for duplicates removal.
index merge/intersection takes advantage of Clustered Primary Key (CPK)
if the table has one.
The index merge/intersection algorithm consists of two phases:
Phase 1
(implemented by a QUICK_INDEX_MERGE_SELECT::read_keys_and_merge call):
Phase 1 (implemented in QUICK_INDEX_MERGE_SELECT::prepare_unique):
prepare()
{
activate 'index only';
......@@ -511,32 +518,31 @@ class QUICK_RANGE_SELECT_GEOM: public QUICK_RANGE_SELECT
deactivate 'index only';
}
Phase 2 (implemented as sequence of QUICK_INDEX_MERGE_SELECT::get_next
calls):
Phase 2
(implemented as sequence of QUICK_INDEX_MERGE_SELECT::get_next calls):
fetch()
{
retrieve all rows from row pointers stored in Unique;
retrieve all rows from row pointers stored in Unique
(merging/intersecting them);
free Unique;
retrieve all rows for CPK scan;
if (! intersection)
retrieve all rows for CPK scan;
}
*/
class QUICK_INDEX_MERGE_SELECT : public QUICK_SELECT_I
class QUICK_INDEX_SORT_SELECT : public QUICK_SELECT_I
{
protected:
Unique *unique;
public:
QUICK_INDEX_MERGE_SELECT(THD *thd, TABLE *table);
~QUICK_INDEX_MERGE_SELECT();
QUICK_INDEX_SORT_SELECT(THD *thd, TABLE *table);
~QUICK_INDEX_SORT_SELECT();
int init();
int reset(void);
int get_next();
bool reverse_sorted() { 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);
void add_info_string(String *str);
bool is_keys_used(const MY_BITMAP *fields);
#ifndef DBUG_OFF
void dbug_dump(int indent, bool verbose);
......@@ -544,60 +550,54 @@ class QUICK_INDEX_MERGE_SELECT : public QUICK_SELECT_I
bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range);
/* range quick selects this index_merge read consists of */
/* range quick selects this index merge/intersect consists of */
List<QUICK_RANGE_SELECT> quick_selects;
/* quick select that uses clustered primary key (NULL if none) */
QUICK_RANGE_SELECT* pk_quick_select;
/* true if this select is currently doing a clustered PK scan */
bool doing_pk_scan;
MEM_ROOT alloc;
THD *thd;
int read_keys_and_merge();
virtual int read_keys_and_merge()= 0;
/* used to get rows collected in Unique */
READ_RECORD read_record;
};
class QUICK_INDEX_INTERSECT_SELECT : public QUICK_SELECT_I
class QUICK_INDEX_MERGE_SELECT : public QUICK_INDEX_SORT_SELECT
{
Unique *unique;
private:
/* true if this select is currently doing a clustered PK scan */
bool doing_pk_scan;
protected:
int read_keys_and_merge();
public:
QUICK_INDEX_INTERSECT_SELECT(THD *thd, TABLE *table);
~QUICK_INDEX_INTERSECT_SELECT();
QUICK_INDEX_MERGE_SELECT(THD *thd, TABLE *table)
:QUICK_INDEX_SORT_SELECT(thd, table) {}
int init();
int reset(void);
int get_next();
bool reverse_sorted() { return false; }
bool unique_key_range() { return false; }
int get_type() { return QS_TYPE_INDEX_INTERSECT; }
int get_next();
int get_type() { return QS_TYPE_INDEX_MERGE; }
void add_keys_and_lengths(String *key_names, String *used_lengths);
void add_info_string(String *str);
bool is_keys_used(const MY_BITMAP *fields);
#ifndef DBUG_OFF
void dbug_dump(int indent, bool verbose);
#endif
bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range);
/* range quick selects this index_merge read consists of */
List<QUICK_RANGE_SELECT> quick_selects;
/* quick select that uses clustered primary key (NULL if none) */
QUICK_RANGE_SELECT* pk_quick_select;
/* true if this select is currently doing a clustered PK scan */
bool doing_pk_scan;
};
MEM_ROOT alloc;
THD *thd;
class QUICK_INDEX_INTERSECT_SELECT : public QUICK_INDEX_SORT_SELECT
{
protected:
int read_keys_and_merge();
/* used to get rows collected in Unique */
READ_RECORD read_record;
public:
QUICK_INDEX_INTERSECT_SELECT(THD *thd, TABLE *table)
:QUICK_INDEX_SORT_SELECT(thd, table) {}
key_map filtered_scans;
int get_next();
int get_type() { return QS_TYPE_INDEX_INTERSECT; }
void add_keys_and_lengths(String *key_names, String *used_lengths);
void add_info_string(String *str);
};
......
......@@ -2998,7 +2998,7 @@ class Unique :public Sql_alloc
bool flush();
uint size;
uint full_size;
uint min_dupl_count;
uint min_dupl_count; /* always 0 for unions, > 0 for intersections */
public:
ulong elements;
......@@ -3022,6 +3022,7 @@ class Unique :public Sql_alloc
bool get(TABLE *table);
/* Cost of searching for an element in the tree */
inline static double get_search_cost(uint tree_elems, uint compare_factor)
{
return log((double) tree_elems) / (compare_factor * M_LN2);
......
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