Added index_merge access method

parent 0466c250
...@@ -95,6 +95,7 @@ peter@linux.local ...@@ -95,6 +95,7 @@ peter@linux.local
peter@mysql.com peter@mysql.com
peterg@mysql.com peterg@mysql.com
pgulutzan@linux.local pgulutzan@linux.local
psergey@psergey.(none)
ram@gw.udmsearch.izhnet.ru ram@gw.udmsearch.izhnet.ru
ram@mysql.r18.ru ram@mysql.r18.ru
ram@ram.(none) ram@ram.(none)
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
** Create a FT or QUICK RANGE based on a key ** Create a FT or QUICK RANGE based on a key
****************************************************************************/ ****************************************************************************/
QUICK_SELECT *get_ft_or_quick_select_for_ref(TABLE *table, JOIN_TAB *tab) QUICK_RANGE_SELECT *get_ft_or_quick_select_for_ref(TABLE *table, JOIN_TAB *tab)
{ {
if (tab->type == JT_FT) if (tab->type == JT_FT)
return new FT_SELECT(table, &tab->ref); return new FT_SELECT(table, &tab->ref);
......
...@@ -24,17 +24,18 @@ ...@@ -24,17 +24,18 @@
#pragma interface /* gcc class implementation */ #pragma interface /* gcc class implementation */
#endif #endif
class FT_SELECT: public QUICK_SELECT { class FT_SELECT: public QUICK_RANGE_SELECT {
public: public:
TABLE_REF *ref; TABLE_REF *ref;
FT_SELECT(TABLE *table, TABLE_REF *tref) : FT_SELECT(TABLE *table, TABLE_REF *tref) :
QUICK_SELECT (table,tref->key,1), ref(tref) { init(); } QUICK_RANGE_SELECT (table,tref->key,1), ref(tref) { init(); }
int init() { return error=file->ft_init(); } int init() { QUICK_RANGE_SELECT::init(); return (error=file->ft_init()); }
int get_next() { return error=file->ft_read(record); } int get_next() { return error=file->ft_read(record); }
int get_type() { return QS_TYPE_FULLTEXT; }
}; };
QUICK_SELECT *get_ft_or_quick_select_for_ref(TABLE *table, JOIN_TAB *tab); QUICK_RANGE_SELECT *get_ft_or_quick_select_for_ref(TABLE *table, JOIN_TAB *tab);
#endif #endif
...@@ -267,14 +267,17 @@ public: ...@@ -267,14 +267,17 @@ public:
SEL_ARG *clone_tree(); SEL_ARG *clone_tree();
}; };
class SEL_IMERGE;
class SEL_TREE :public Sql_alloc class SEL_TREE :public Sql_alloc
{ {
public: public:
enum Type { IMPOSSIBLE, ALWAYS, MAYBE, KEY, KEY_SMALLER } type; enum Type { IMPOSSIBLE, ALWAYS, MAYBE, KEY, KEY_SMALLER } type;
SEL_TREE(enum Type type_arg) :type(type_arg) {} SEL_TREE(enum Type type_arg) :type(type_arg) {}
SEL_TREE() :type(KEY) { bzero((char*) keys,sizeof(keys));} SEL_TREE() :type(KEY), keys_map(0) { bzero((char*) keys,sizeof(keys));}
SEL_ARG *keys[MAX_KEY]; SEL_ARG *keys[MAX_KEY];
key_map keys_map; /* bitmask of non-NULL elements in keys */
List<SEL_IMERGE> merges; /* possible ways to read rows using index_merge */
}; };
...@@ -301,10 +304,19 @@ static ha_rows check_quick_keys(PARAM *param,uint index,SEL_ARG *key_tree, ...@@ -301,10 +304,19 @@ static ha_rows check_quick_keys(PARAM *param,uint index,SEL_ARG *key_tree,
char *min_key,uint min_key_flag, char *min_key,uint min_key_flag,
char *max_key, uint max_key_flag); char *max_key, uint max_key_flag);
static QUICK_SELECT *get_quick_select(PARAM *param,uint index, QUICK_RANGE_SELECT *get_quick_select(PARAM *param,uint index,
SEL_ARG *key_tree); SEL_ARG *key_tree, MEM_ROOT *alloc = NULL);
static int get_quick_select_params(SEL_TREE *tree, PARAM& param,
key_map& needed_reg, TABLE *head,
bool index_read_can_be_used,
double* read_time,
ha_rows* records,
SEL_ARG*** key_to_read);
#ifndef DBUG_OFF #ifndef DBUG_OFF
static void print_quick(QUICK_SELECT *quick,key_map needed_reg); void print_quick_sel_imerge(QUICK_INDEX_MERGE_SELECT *quick,
key_map needed_reg);
void print_quick_sel_range(QUICK_RANGE_SELECT *quick,key_map needed_reg);
#endif #endif
static SEL_TREE *tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2); static SEL_TREE *tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2);
static SEL_TREE *tree_or(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2); static SEL_TREE *tree_or(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2);
...@@ -312,16 +324,234 @@ static SEL_ARG *sel_add(SEL_ARG *key1,SEL_ARG *key2); ...@@ -312,16 +324,234 @@ static SEL_ARG *sel_add(SEL_ARG *key1,SEL_ARG *key2);
static SEL_ARG *key_or(SEL_ARG *key1,SEL_ARG *key2); static SEL_ARG *key_or(SEL_ARG *key1,SEL_ARG *key2);
static SEL_ARG *key_and(SEL_ARG *key1,SEL_ARG *key2,uint clone_flag); static SEL_ARG *key_and(SEL_ARG *key1,SEL_ARG *key2,uint clone_flag);
static bool get_range(SEL_ARG **e1,SEL_ARG **e2,SEL_ARG *root1); static bool get_range(SEL_ARG **e1,SEL_ARG **e2,SEL_ARG *root1);
static bool get_quick_keys(PARAM *param,QUICK_SELECT *quick,KEY_PART *key, bool get_quick_keys(PARAM *param,QUICK_RANGE_SELECT *quick,KEY_PART *key,
SEL_ARG *key_tree,char *min_key,uint min_key_flag, SEL_ARG *key_tree,char *min_key,uint min_key_flag,
char *max_key,uint max_key_flag); char *max_key,uint max_key_flag);
static bool eq_tree(SEL_ARG* a,SEL_ARG *b); static bool eq_tree(SEL_ARG* a,SEL_ARG *b);
static SEL_ARG null_element(SEL_ARG::IMPOSSIBLE); static SEL_ARG null_element(SEL_ARG::IMPOSSIBLE);
static bool null_part_in_key(KEY_PART *key_part, const char *key, uint length); static bool null_part_in_key(KEY_PART *key_part, const char *key, uint length);
bool sel_trees_can_be_ored(SEL_TREE *tree1, SEL_TREE *tree2, PARAM* param);
/*
SEL_IMERGE is a list of possible ways to do index merge, i.e. it is
a condition in the following form:
(t_1||t_2||...||t_N) && (next)
where all t_i are SEL_TREEs, next is another SEL_IMERGE and no pair
(t_i,t_j) contains SEL_ARGS for the same index.
SEL_TREE contained in SEL_IMERGE always has merges=NULL.
This class relies on memory manager to do the cleanup.
*/
class SEL_IMERGE : public Sql_alloc
{
enum { PREALLOCED_TREES= 10};
public:
SEL_TREE *trees_prealloced[PREALLOCED_TREES];
SEL_TREE **trees; /* trees used to do index_merge */
SEL_TREE **trees_next; /* last of these trees */
SEL_TREE **trees_end; /* end of allocated space */
SEL_ARG ***best_keys; /* best keys to read in SEL_TREEs */
SEL_IMERGE() :
trees(&trees_prealloced[0]),
trees_next(trees),
trees_end(trees + PREALLOCED_TREES)
{}
int or_sel_tree(PARAM *param, SEL_TREE *tree);
int or_sel_tree_with_checks(PARAM *param, SEL_TREE *new_tree);
int or_sel_imerge_with_checks(PARAM *param, SEL_IMERGE* imerge);
};
/*
Add SEL_TREE to this index_merge without any checks,
NOTES
This function implements the following:
(x_1||...||x_N) || t = (x_1||...||x_N||t), where x_i, t are SEL_TREEs
RETURN
0 - OK
-1 - Out of memory.
*/
int SEL_IMERGE::or_sel_tree(PARAM *param, SEL_TREE *tree)
{
if (trees_next == trees_end)
{
const int realloc_ratio= 2; /* Double size for next round */
uint old_elements= (trees_end - trees);
uint old_size= sizeof(SEL_TREE**) * old_elements;
uint new_size= old_size * realloc_ratio;
SEL_TREE **new_trees;
if (!(new_trees= (SEL_TREE**)alloc_root(param->mem_root, new_size)))
return -1;
memcpy(new_trees, trees, old_size);
trees= new_trees;
trees_next= trees + old_elements;
trees_end= trees + old_elements * realloc_ratio;
}
*(trees_next++)= tree;
return 0;
}
/*
Perform OR operation on this SEL_IMERGE and supplied SEL_TREE new_tree,
combining new_tree with one of the trees in this SEL_IMERGE if they both
have SEL_ARGs for the same key.
SYNOPSIS
or_sel_tree_with_checks()
param PARAM from SQL_SELECT::test_quick_select
new_tree SEL_TREE with type KEY or KEY_SMALLER.
NOTES
This does the following:
(t_1||...||t_k)||new_tree =
either
= (t_1||...||t_k||new_tree)
or
= (t_1||....||(t_j|| new_tree)||...||t_k),
where t_i, y are SEL_TREEs.
new_tree is combined with the first t_j it has a SEL_ARG on common
key with. As a consequence of this, choice of keys to do index_merge
read may depend on the order of conditions in WHERE part of the query.
RETURN
0 OK
1 One of the trees was combined with new_tree to SEL_TREE::ALWAYS,
and (*this) should be discarded.
-1 An error occurred.
*/
int SEL_IMERGE::or_sel_tree_with_checks(PARAM *param, SEL_TREE *new_tree)
{
for (SEL_TREE** tree = trees;
tree != trees_next;
tree++)
{
if (sel_trees_can_be_ored(*tree, new_tree, param))
{
*tree = tree_or(param, *tree, new_tree);
if (!*tree)
return 1;
if (((*tree)->type == SEL_TREE::MAYBE) ||
((*tree)->type == SEL_TREE::ALWAYS))
return 1;
/* SEL_TREE::IMPOSSIBLE is impossible here */
return 0;
}
}
/* new tree cannot be combined with any of existing trees */
return or_sel_tree(param, new_tree);
}
/*
Perform OR operation on this index_merge and supplied index_merge list.
RETURN
0 - OK
1 - One of conditions in result is always TRUE and this SEL_IMERGE
should be discarded.
-1 - An error occurred
*/
int SEL_IMERGE::or_sel_imerge_with_checks(PARAM *param, SEL_IMERGE* imerge)
{
for (SEL_TREE** tree= imerge->trees;
tree != imerge->trees_next;
tree++)
{
if (or_sel_tree_with_checks(param, *tree))
return 1;
}
return 0;
}
/*
Perform AND operation on two index_merge lists, storing result in *im1.
*/
inline void imerge_list_and_list(List<SEL_IMERGE> *im1, List<SEL_IMERGE> *im2)
{
im1->concat(im2);
}
/*
Perform OR operation on 2 index_merge lists, storing result in first list.
NOTES
The following conversion is implemented:
(a_1 &&...&& a_N)||(b_1 &&...&& b_K) = AND_i,j(a_i || b_j) =>
=> (a_1||b_1).
i.e. all conjuncts except the first one are currently dropped.
This is done to avoid producing N*K ways to do index_merge.
If (a_1||b_1) produce a condition that is always true, NULL is
returned and index_merge is discarded. (while it is actually
possible to try harder).
As a consequence of this, choice of keys to do index_merge
read may depend on the order of conditions in WHERE part of
the query.
RETURN
0 OK, result is stored in *im1
other Error, both passed lists are unusable
*/
int imerge_list_or_list(PARAM *param,
List<SEL_IMERGE> *im1,
List<SEL_IMERGE> *im2)
{
SEL_IMERGE *imerge= im1->head();
im1->empty();
im1->push_back(imerge);
return imerge->or_sel_imerge_with_checks(param, im2->head());
}
/*
Perform OR operation on index_merge list and key tree.
RETURN
0 OK, result is stored in *im1
other Error
*/
int imerge_list_or_tree(PARAM *param,
List<SEL_IMERGE> *im1,
SEL_TREE *tree)
{
SEL_IMERGE *imerge;
List_iterator<SEL_IMERGE> it(*im1);
while((imerge= it++))
{
if (imerge->or_sel_tree_with_checks(param, tree))
it.remove();
}
return im1->is_empty();
}
/*************************************************************************** /***************************************************************************
** Basic functions for SQL_SELECT and QUICK_SELECT ** Basic functions for SQL_SELECT and QUICK_RANGE_SELECT
***************************************************************************/ ***************************************************************************/
/* make a select from mysql info /* make a select from mysql info
...@@ -378,23 +608,34 @@ SQL_SELECT::~SQL_SELECT() ...@@ -378,23 +608,34 @@ SQL_SELECT::~SQL_SELECT()
#undef index // Fix for Unixware 7 #undef index // Fix for Unixware 7
QUICK_SELECT::QUICK_SELECT(TABLE *table,uint key_nr,bool no_alloc) QUICK_SELECT_I::QUICK_SELECT_I()
:dont_free(0),error(0),index(key_nr),max_used_key_length(0), :max_used_key_length(0),
used_key_parts(0), head(table), it(ranges),range(0) used_key_parts(0)
{}
QUICK_RANGE_SELECT::QUICK_RANGE_SELECT(TABLE *table,uint key_nr,bool no_alloc,
MEM_ROOT *parent_alloc)
:dont_free(0),error(0),it(ranges),range(0)
{ {
if (!no_alloc) index= key_nr;
head= table;
if (!no_alloc && !parent_alloc)
{ {
init_sql_alloc(&alloc,1024,0); // Allocates everything here init_sql_alloc(&alloc,1024,0); // Allocates everything here
my_pthread_setspecific_ptr(THR_MALLOC,&alloc); my_pthread_setspecific_ptr(THR_MALLOC,&alloc);
} }
else else
bzero((char*) &alloc,sizeof(alloc)); bzero((char*) &alloc,sizeof(alloc));
file=head->file; file= head->file;
record=head->record[0]; record= head->record[0];
init();
} }
QUICK_SELECT::~QUICK_SELECT() int QUICK_RANGE_SELECT::init()
{
return (error= file->index_init(index));
}
QUICK_RANGE_SELECT::~QUICK_RANGE_SELECT()
{ {
if (!dont_free) if (!dont_free)
{ {
...@@ -403,6 +644,42 @@ QUICK_SELECT::~QUICK_SELECT() ...@@ -403,6 +644,42 @@ QUICK_SELECT::~QUICK_SELECT()
} }
} }
QUICK_INDEX_MERGE_SELECT::QUICK_INDEX_MERGE_SELECT(THD *thd, TABLE *table)
:cur_quick_it(quick_selects), index_merge(thd)
{
index= MAX_KEY;
head= table;
init_sql_alloc(&alloc,1024,0);
}
int QUICK_INDEX_MERGE_SELECT::init()
{
int error;
cur_quick_it.rewind();
cur_quick_select= cur_quick_it++;
if (error= index_merge.init(head))
return error;
return cur_quick_select->init();
}
void QUICK_INDEX_MERGE_SELECT::reset()
{
cur_quick_select->reset();
}
bool
QUICK_INDEX_MERGE_SELECT::push_quick_back(QUICK_RANGE_SELECT *quick_sel_range)
{
return quick_selects.push_back(quick_sel_range);
}
QUICK_INDEX_MERGE_SELECT::~QUICK_INDEX_MERGE_SELECT()
{
quick_selects.delete_elements();
free_root(&alloc,MYF(0));
}
QUICK_RANGE::QUICK_RANGE() QUICK_RANGE::QUICK_RANGE()
:min_key(0),max_key(0),min_length(0),max_length(0), :min_key(0),max_key(0),min_length(0),max_length(0),
flag(NO_MIN_RANGE | NO_MAX_RANGE) flag(NO_MIN_RANGE | NO_MAX_RANGE)
...@@ -581,6 +858,8 @@ int SQL_SELECT::test_quick_select(key_map keys_to_use, table_map prev_tables, ...@@ -581,6 +858,8 @@ int SQL_SELECT::test_quick_select(key_map keys_to_use, table_map prev_tables,
uint basflag; uint basflag;
uint idx; uint idx;
double scan_time; double scan_time;
QUICK_INDEX_MERGE_SELECT *quick_imerge;
THD *thd= current_thd;
DBUG_ENTER("test_quick_select"); DBUG_ENTER("test_quick_select");
DBUG_PRINT("enter",("keys_to_use: %lu prev_tables: %lu const_tables: %lu", DBUG_PRINT("enter",("keys_to_use: %lu prev_tables: %lu const_tables: %lu",
(ulong) keys_to_use, (ulong) prev_tables, (ulong) keys_to_use, (ulong) prev_tables,
...@@ -626,13 +905,13 @@ int SQL_SELECT::test_quick_select(key_map keys_to_use, table_map prev_tables, ...@@ -626,13 +905,13 @@ int SQL_SELECT::test_quick_select(key_map keys_to_use, table_map prev_tables,
param.keys=0; param.keys=0;
param.mem_root= &alloc; param.mem_root= &alloc;
current_thd->no_errors=1; // Don't warn about NULL thd->no_errors=1; // Don't warn about NULL
init_sql_alloc(&alloc,2048,0); init_sql_alloc(&alloc,2048,0);
if (!(param.key_parts = (KEY_PART*) alloc_root(&alloc, if (!(param.key_parts = (KEY_PART*) alloc_root(&alloc,
sizeof(KEY_PART)* sizeof(KEY_PART)*
head->key_parts))) head->key_parts)))
{ {
current_thd->no_errors=0; thd->no_errors=0;
free_root(&alloc,MYF(0)); // Return memory & allocator free_root(&alloc,MYF(0)); // Return memory & allocator
DBUG_RETURN(0); // Can't use range DBUG_RETURN(0); // Can't use range
} }
...@@ -673,70 +952,212 @@ int SQL_SELECT::test_quick_select(key_map keys_to_use, table_map prev_tables, ...@@ -673,70 +952,212 @@ int SQL_SELECT::test_quick_select(key_map keys_to_use, table_map prev_tables,
read_time= (double) HA_POS_ERROR; read_time= (double) HA_POS_ERROR;
} }
else if (tree->type == SEL_TREE::KEY || else if (tree->type == SEL_TREE::KEY ||
tree->type == SEL_TREE::KEY_SMALLER) tree->type == SEL_TREE::KEY_SMALLER)
{ {
SEL_ARG **key,**end,**best_key=0; /*
It is possible to use a quick select (but maybe it would be slower
than 'all' table scan).
for (idx=0,key=tree->keys, end=key+param.keys ; */
key != end ; SEL_ARG **best_key= 0;
key++,idx++) ha_rows found_records;
{ double found_read_time= read_time;
ha_rows found_records;
double found_read_time; if (!get_quick_select_params(tree, param, needed_reg, head, true,
if (*key) &found_read_time, &found_records,
{ &best_key))
uint keynr= param.real_keynr[idx]; {
if ((*key)->type == SEL_ARG::MAYBE_KEY || /*
(*key)->maybe_flag) Ok, quick select is better than 'all' table scan and we have its
needed_reg|= (key_map) 1 << keynr; parameters, so construct it.
*/
found_records=check_quick_select(&param, idx, *key); read_time= found_read_time;
if (found_records != HA_POS_ERROR && found_records > 2 && records= found_records;
head->used_keys & ((table_map) 1 << keynr) &&
(head->file->index_flags(keynr) & HA_KEY_READ_ONLY)) if ((quick= get_quick_select(&param,(uint) (best_key-tree->keys),
{ *best_key)) && (!quick->init()))
/* {
We can resolve this by only reading through this key. quick->records= records;
Assume that we will read trough the whole key range quick->read_time= read_time;
and that all key blocks are half full (normally things are }
much better). }
*/
uint keys_per_block= (head->file->block_size/2/ /*
(head->key_info[keynr].key_length+ btw, tree type SEL_TREE::INDEX_MERGE was not introduced
head->file->ref_length) + 1); intentionally
found_read_time=((double) (found_records+keys_per_block-1)/ */
(double) keys_per_block);
} /* if no range select could be built, try using index_merge */
else if (!quick && !tree->merges.is_empty())
found_read_time= (head->file->read_time(keynr, {
param.range_count, DBUG_PRINT("info",("No range reads possible,"
found_records)+ " trying to construct index_merge"));
(double) found_records / TIME_FOR_COMPARE); SEL_IMERGE *imerge;
if (read_time > found_read_time) SEL_IMERGE *min_imerge= NULL;
{ double min_imerge_cost= DBL_MAX;
read_time=found_read_time; ha_rows min_imerge_records;
records=found_records;
best_key=key; List_iterator_fast<SEL_IMERGE> it(tree->merges);
} while ((imerge= it++))
} {
} double imerge_cost= 0;
if (best_key && records) ha_rows imerge_total_records= 0;
{ double tree_read_time;
if ((quick=get_quick_select(&param,(uint) (best_key-tree->keys), ha_rows tree_records;
*best_key))) imerge->best_keys=
{ (SEL_ARG***)alloc_root(&alloc,
quick->records=records; (imerge->trees_next - imerge->trees)*
quick->read_time=read_time; sizeof(void*));
} for (SEL_TREE **ptree= imerge->trees;
} ptree != imerge->trees_next;
ptree++)
{
tree_read_time= read_time;
if (get_quick_select_params(*ptree, param, needed_reg, head,
false,
&tree_read_time, &tree_records,
&(imerge->best_keys[ptree -
imerge->trees])))
goto imerge_fail;
imerge_cost += tree_read_time;
imerge_total_records += tree_records;
}
imerge_total_records= min(imerge_total_records,
head->file->records);
imerge_cost += imerge_total_records / TIME_FOR_COMPARE;
if (imerge_cost < min_imerge_cost)
{
min_imerge= imerge;
min_imerge_cost= imerge_cost;
min_imerge_records= imerge_total_records;
}
imerge_fail:;
}
if (!min_imerge)
goto end_free;
records= min_imerge_records;
/* ok, got minimal imerge, *min_imerge, with cost min_imerge_cost */
if (head->used_keys)
{
/* check if "ALL" +"using index" read would be faster */
int key_for_use= find_shortest_key(head, head->used_keys);
ha_rows total_table_records= (0 == head->file->records)? 1 :
head->file->records;
uint keys_per_block= (head->file->block_size/2/
(head->key_info[key_for_use].key_length+
head->file->ref_length) + 1);
double all_index_scan_read_time= ((double)(total_table_records+
keys_per_block-1)/
(double) keys_per_block);
DBUG_PRINT("info",
("'all' scan will be using key %d, read time %g",
key_for_use, all_index_scan_read_time));
if (all_index_scan_read_time < min_imerge_cost)
{
DBUG_PRINT("info",
("index merge would be slower, "
"will do full 'index' scan"));
goto end_free;
}
}
else
{
/* check if "ALL" would be faster */
if (read_time < min_imerge_cost)
{
DBUG_PRINT("info",
("index merge would be slower, "
"will do full table scan"));
goto end_free;
}
}
if (!(quick= quick_imerge= new QUICK_INDEX_MERGE_SELECT(thd, head)))
goto end_free;
quick->records= min_imerge_records;
quick->read_time= min_imerge_cost;
my_pthread_setspecific_ptr(THR_MALLOC, &quick_imerge->alloc);
QUICK_RANGE_SELECT *new_quick;
for (SEL_TREE **ptree = min_imerge->trees;
ptree != min_imerge->trees_next;
ptree++)
{
SEL_ARG **tree_best_key=
min_imerge->best_keys[ptree - min_imerge->trees];
if ((new_quick= get_quick_select(&param,
(uint)(tree_best_key-
(*ptree)->keys),
*tree_best_key,
&quick_imerge->alloc)))
{
new_quick->records= min_imerge_records;
new_quick->read_time= min_imerge_cost;
/*
QUICK_RANGE_SELECT::QUICK_RANGE_SELECT leaves THR_MALLOC
pointing to its allocator, restore it back
*/
//my_pthread_setspecific_ptr(THR_MALLOC,old_root);
quick_imerge->last_quick_select= new_quick;
if (quick_imerge->push_quick_back(new_quick))
{
delete new_quick;
delete quick;
quick= quick_imerge= NULL;
goto end_free;
}
}
else
{
delete quick;
quick= quick_imerge= NULL;
goto end_free;
}
}
free_root(&alloc,MYF(0));
my_pthread_setspecific_ptr(THR_MALLOC,old_root);
if (quick->init())
{
delete quick;
quick= quick_imerge= NULL;
DBUG_PRINT("error",
("Failed to allocate index merge structures,"
"falling back to full scan."));
}
else
{
/* with 'using filesort' quick->reset() is not called */
quick->reset();
}
goto end;
}
} }
} }
end_free:
free_root(&alloc,MYF(0)); // Return memory & allocator free_root(&alloc,MYF(0)); // Return memory & allocator
my_pthread_setspecific_ptr(THR_MALLOC,old_root); my_pthread_setspecific_ptr(THR_MALLOC,old_root);
current_thd->no_errors=0; end:
thd->no_errors=0;
} }
DBUG_EXECUTE("info",print_quick(quick,needed_reg););
DBUG_EXECUTE("info",
{
if (quick_imerge)
print_quick_sel_imerge(quick_imerge, needed_reg);
else
print_quick_sel_range((QUICK_RANGE_SELECT*)quick, needed_reg);
}
);
/* /*
Assume that if the user is using 'limit' we will only need to scan Assume that if the user is using 'limit' we will only need to scan
limit rows if we are using a key limit rows if we are using a key
...@@ -744,6 +1165,77 @@ int SQL_SELECT::test_quick_select(key_map keys_to_use, table_map prev_tables, ...@@ -744,6 +1165,77 @@ int SQL_SELECT::test_quick_select(key_map keys_to_use, table_map prev_tables,
DBUG_RETURN(records ? test(quick) : -1); DBUG_RETURN(records ? test(quick) : -1);
} }
/*
Calculate quick select read time, # of records, and best key to use
without constructing QUICK_SELECT
*/
static int get_quick_select_params(SEL_TREE *tree, PARAM& param,
key_map& needed_reg, TABLE *head,
bool index_read_can_be_used,
double* read_time, ha_rows* records,
SEL_ARG*** key_to_read)
{
int idx;
int result = 1;
/*
Note that there may be trees that have type SEL_TREE::KEY but contain
no key reads at all. For example, tree for expression "key1 is not null"
where key1 is defined as "not null".
*/
SEL_ARG **key,**end;
for (idx= 0,key=tree->keys, end=key+param.keys ;
key != end ;
key++,idx++)
{
ha_rows found_records;
double found_read_time;
if (*key)
{
uint keynr= param.real_keynr[idx];
if ((*key)->type == SEL_ARG::MAYBE_KEY ||
(*key)->maybe_flag)
needed_reg|= (key_map) 1 << keynr;
key_map usable_keys = index_read_can_be_used?
(head->used_keys & ((key_map) 1 << keynr)) : 0;
found_records=check_quick_select(&param, idx, *key);
if (found_records != HA_POS_ERROR && found_records > 2 &&
usable_keys &&
(head->file->index_flags(keynr) & HA_KEY_READ_ONLY))
{
/*
We can resolve this by only reading through this key.
Assume that we will read trough the whole key range
and that all key blocks are half full (normally things are
much better).
*/
uint keys_per_block= (head->file->block_size/2/
(head->key_info[keynr].key_length+
head->file->ref_length) + 1);
found_read_time=((double) (found_records+keys_per_block-1)/
(double) keys_per_block);
}
else
found_read_time= (head->file->read_time(keynr,
param.range_count,
found_records)+
(double) found_records / TIME_FOR_COMPARE);
if (*read_time > found_read_time)
{
*read_time= found_read_time;
*records= found_records;
*key_to_read= key;
result = 0;
}
}
}
return result;
}
/* make a select tree of all keys in condition */ /* make a select tree of all keys in condition */
static SEL_TREE *get_mm_tree(PARAM *param,COND *cond) static SEL_TREE *get_mm_tree(PARAM *param,COND *cond)
...@@ -911,6 +1403,7 @@ get_mm_parts(PARAM *param,Field *field, Item_func::Functype type,Item *value, ...@@ -911,6 +1403,7 @@ get_mm_parts(PARAM *param,Field *field, Item_func::Functype type,Item *value,
sel_arg=new SEL_ARG(SEL_ARG::MAYBE_KEY);// This key may be used later sel_arg=new SEL_ARG(SEL_ARG::MAYBE_KEY);// This key may be used later
sel_arg->part=(uchar) key_part->part; sel_arg->part=(uchar) key_part->part;
tree->keys[key_part->key]=sel_add(tree->keys[key_part->key],sel_arg); tree->keys[key_part->key]=sel_add(tree->keys[key_part->key],sel_arg);
tree->keys_map |= 1 << key_part->key;
} }
} }
DBUG_RETURN(tree); DBUG_RETURN(tree);
...@@ -1176,6 +1669,8 @@ tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) ...@@ -1176,6 +1669,8 @@ tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2)
DBUG_RETURN(tree1); DBUG_RETURN(tree1);
} }
bool trees_have_key = false;
key_map result_keys= 0;
/* Join the trees key per key */ /* Join the trees key per key */
SEL_ARG **key1,**key2,**end; SEL_ARG **key1,**key2,**end;
for (key1= tree1->keys,key2= tree2->keys,end=key1+param->keys ; for (key1= tree1->keys,key2= tree2->keys,end=key1+param->keys ;
...@@ -1184,6 +1679,7 @@ tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) ...@@ -1184,6 +1679,7 @@ tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2)
uint flag=0; uint flag=0;
if (*key1 || *key2) if (*key1 || *key2)
{ {
trees_have_key = true;
if (*key1 && !(*key1)->simple_key()) if (*key1 && !(*key1)->simple_key())
flag|=CLONE_KEY1_MAYBE; flag|=CLONE_KEY1_MAYBE;
if (*key2 && !(*key2)->simple_key()) if (*key2 && !(*key2)->simple_key())
...@@ -1192,17 +1688,57 @@ tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) ...@@ -1192,17 +1688,57 @@ tree_and(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2)
if ((*key1)->type == SEL_ARG::IMPOSSIBLE) if ((*key1)->type == SEL_ARG::IMPOSSIBLE)
{ {
tree1->type= SEL_TREE::IMPOSSIBLE; tree1->type= SEL_TREE::IMPOSSIBLE;
break; DBUG_RETURN(tree1);
} }
result_keys |= 1 << (key1 - tree1->keys);
#ifdef EXTRA_DEBUG #ifdef EXTRA_DEBUG
(*key1)->test_use_count(*key1); (*key1)->test_use_count(*key1);
#endif #endif
} }
} }
tree1->keys_map= result_keys;
/* dispose index_merge if there is a "range" option */
if (trees_have_key)
{
tree1->merges.empty();
DBUG_RETURN(tree1);
}
/* ok, both trees are index_merge trees */
imerge_list_and_list(&tree1->merges, &tree2->merges);
DBUG_RETURN(tree1); DBUG_RETURN(tree1);
} }
/*
Check if two SEL_TREES can be combined into one without using index_merge
*/
bool sel_trees_can_be_ored(SEL_TREE *tree1, SEL_TREE *tree2, PARAM* param)
{
key_map common_keys= tree1->keys_map & tree2->keys_map;
DBUG_ENTER("sel_trees_can_be_ored");
if (!common_keys)
DBUG_RETURN(false);
/* trees have a common key, check if they refer to same key part */
SEL_ARG **key1,**key2;
for (uint key_no=0; key_no < param->keys; key_no++, common_keys= common_keys >> 1)
{
if (common_keys & 1)
{
key1= tree1->keys + key_no;
key2= tree2->keys + key_no;
if ((*key1)->part == (*key2)->part)
{
DBUG_RETURN(true);
}
}
}
DBUG_RETURN(false);
}
static SEL_TREE * static SEL_TREE *
tree_or(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) tree_or(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2)
...@@ -1219,19 +1755,61 @@ tree_or(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2) ...@@ -1219,19 +1755,61 @@ tree_or(PARAM *param,SEL_TREE *tree1,SEL_TREE *tree2)
if (tree2->type == SEL_TREE::MAYBE) if (tree2->type == SEL_TREE::MAYBE)
DBUG_RETURN(tree2); DBUG_RETURN(tree2);
/* Join the trees key per key */ SEL_TREE *result= 0;
SEL_ARG **key1,**key2,**end; key_map result_keys= 0;
SEL_TREE *result=0; if (sel_trees_can_be_ored(tree1, tree2, param))
for (key1= tree1->keys,key2= tree2->keys,end=key1+param->keys ;
key1 != end ; key1++,key2++)
{ {
*key1=key_or(*key1,*key2); /* Join the trees key per key */
if (*key1) SEL_ARG **key1,**key2,**end;
for (key1= tree1->keys,key2= tree2->keys,end= key1+param->keys ;
key1 != end ; key1++,key2++)
{ {
result=tree1; // Added to tree1 *key1=key_or(*key1,*key2);
if (*key1)
{
result=tree1; // Added to tree1
result_keys |= 1 << (key1 - tree1->keys);
#ifdef EXTRA_DEBUG #ifdef EXTRA_DEBUG
(*key1)->test_use_count(*key1); (*key1)->test_use_count(*key1);
#endif #endif
}
}
if (result)
result->keys_map= result_keys;
}
else
{
/* ok, two trees have KEY type but cannot be used without index merge */
if (tree1->merges.is_empty() && tree2->merges.is_empty())
{
SEL_IMERGE *merge;
/* both trees are "range" trees, produce new index merge structure */
if (!(result= new SEL_TREE()) || !(merge= new SEL_IMERGE()) ||
(result->merges.push_back(merge)) ||
(merge->or_sel_tree(param, tree1)) ||
(merge->or_sel_tree(param, tree2)))
result= NULL;
else
result->type= tree1->type;
}
else if (!tree1->merges.is_empty() && !tree2->merges.is_empty())
{
if (imerge_list_or_list(param, &tree1->merges, &tree2->merges))
result= new SEL_TREE(SEL_TREE::ALWAYS);
else
result= tree1;
}
else
{
/* one tree is index merge tree and another is range tree */
if (tree1->merges.is_empty())
swap(SEL_TREE*, tree1, tree2);
/* add tree2 to tree1->merges, checking if it collapses to ALWAYS */
if (imerge_list_or_tree(param, &tree1->merges, tree2))
result= new SEL_TREE(SEL_TREE::ALWAYS);
else
result= tree1;
} }
} }
DBUG_RETURN(result); DBUG_RETURN(result);
...@@ -2201,14 +2779,17 @@ check_quick_keys(PARAM *param,uint idx,SEL_ARG *key_tree, ...@@ -2201,14 +2779,17 @@ check_quick_keys(PARAM *param,uint idx,SEL_ARG *key_tree,
/**************************************************************************** /****************************************************************************
** change a tree to a structure to be used by quick_select ** change a tree to a structure to be used by quick_select
** This uses it's own malloc tree ** This uses it's own malloc tree
** The caller should call QUICK_SELCT::init for returned quick select
****************************************************************************/ ****************************************************************************/
QUICK_RANGE_SELECT *
static QUICK_SELECT * get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree,
get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree) MEM_ROOT *parent_alloc)
{ {
QUICK_SELECT *quick; QUICK_RANGE_SELECT *quick;
DBUG_ENTER("get_quick_select"); DBUG_ENTER("get_quick_select");
if ((quick=new QUICK_SELECT(param->table,param->real_keynr[idx]))) if ((quick=new QUICK_RANGE_SELECT(param->table,param->real_keynr[idx],
test(parent_alloc),
parent_alloc)))
{ {
if (quick->error || if (quick->error ||
get_quick_keys(param,quick,param->key[idx],key_tree,param->min_key,0, get_quick_keys(param,quick,param->key[idx],key_tree,param->min_key,0,
...@@ -2220,9 +2801,10 @@ get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree) ...@@ -2220,9 +2801,10 @@ get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree)
else else
{ {
quick->key_parts=(KEY_PART*) quick->key_parts=(KEY_PART*)
memdup_root(&quick->alloc,(char*) param->key[idx], memdup_root(parent_alloc? parent_alloc : &quick->alloc,
sizeof(KEY_PART)* (char*) param->key[idx],
param->table->key_info[param->real_keynr[idx]].key_parts); sizeof(KEY_PART)*
param->table->key_info[param->real_keynr[idx]].key_parts);
} }
} }
DBUG_RETURN(quick); DBUG_RETURN(quick);
...@@ -2232,9 +2814,8 @@ get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree) ...@@ -2232,9 +2814,8 @@ get_quick_select(PARAM *param,uint idx,SEL_ARG *key_tree)
/* /*
** Fix this to get all possible sub_ranges ** Fix this to get all possible sub_ranges
*/ */
bool
static bool get_quick_keys(PARAM *param,QUICK_RANGE_SELECT *quick,KEY_PART *key,
get_quick_keys(PARAM *param,QUICK_SELECT *quick,KEY_PART *key,
SEL_ARG *key_tree,char *min_key,uint min_key_flag, SEL_ARG *key_tree,char *min_key,uint min_key_flag,
char *max_key, uint max_key_flag) char *max_key, uint max_key_flag)
{ {
...@@ -2343,7 +2924,7 @@ get_quick_keys(PARAM *param,QUICK_SELECT *quick,KEY_PART *key, ...@@ -2343,7 +2924,7 @@ get_quick_keys(PARAM *param,QUICK_SELECT *quick,KEY_PART *key,
Return 1 if there is only one range and this uses the whole primary key Return 1 if there is only one range and this uses the whole primary key
*/ */
bool QUICK_SELECT::unique_key_range() bool QUICK_RANGE_SELECT::unique_key_range()
{ {
if (ranges.elements == 1) if (ranges.elements == 1)
{ {
...@@ -2380,16 +2961,22 @@ static bool null_part_in_key(KEY_PART *key_part, const char *key, uint length) ...@@ -2380,16 +2961,22 @@ static bool null_part_in_key(KEY_PART *key_part, const char *key, uint length)
** Create a QUICK RANGE based on a key ** Create a QUICK RANGE based on a key
****************************************************************************/ ****************************************************************************/
QUICK_SELECT *get_quick_select_for_ref(TABLE *table, TABLE_REF *ref) QUICK_RANGE_SELECT *get_quick_select_for_ref(TABLE *table, TABLE_REF *ref)
{ {
table->file->index_end(); // Remove old cursor table->file->index_end(); // Remove old cursor
QUICK_SELECT *quick=new QUICK_SELECT(table, ref->key, 1); QUICK_RANGE_SELECT *quick=new QUICK_RANGE_SELECT(table, ref->key, 1);
KEY *key_info = &table->key_info[ref->key]; KEY *key_info = &table->key_info[ref->key];
KEY_PART *key_part; KEY_PART *key_part;
uint part; uint part;
if (!quick) if (!quick)
return 0; return 0;
if (quick->init())
{
delete quick;
return 0;
}
if (cp_buffer_from_ref(ref)) if (cp_buffer_from_ref(ref))
{ {
if (current_thd->is_fatal_error) if (current_thd->is_fatal_error)
...@@ -2427,11 +3014,204 @@ err: ...@@ -2427,11 +3014,204 @@ err:
return 0; return 0;
} }
INDEX_MERGE::INDEX_MERGE(THD *thd_arg) :
dont_save(false), thd(thd_arg)
{}
String *INDEX_MERGE::Item_rowid::val_str(String *str)
{
str->set_quick((char*)head->file->ref, head->file->ref_length, collation.collation);
return str;
}
/*
Initialize index_merge operation.
RETURN
0 - OK
other - error.
*/
int INDEX_MERGE::init(TABLE *table)
{
DBUG_ENTER("INDEX_MERGE::init");
head= table;
if (!(rowid_item= new Item_rowid(table)))
DBUG_RETURN(1);
tmp_table_param.copy_field= 0;
tmp_table_param.end_write_records= HA_POS_ERROR;
tmp_table_param.group_length= table->file->ref_length;
tmp_table_param.group_parts= 1;
tmp_table_param.group_null_parts= 0;
tmp_table_param.hidden_field_count= 0;
tmp_table_param.field_count= 0;
tmp_table_param.func_count= 1;
tmp_table_param.sum_func_count= 0;
tmp_table_param.quick_group= 1;
bzero(&order, sizeof(ORDER));
order.item= (Item**)&rowid_item;
order.asc= 1;
fields.push_back(rowid_item);
temp_table= create_tmp_table(thd,
&tmp_table_param,
fields,
&order,
false,
0,
SELECT_DISTINCT,
HA_POS_ERROR,
(char *)"");
DBUG_RETURN(!temp_table);
}
/*
Check if record with ROWID record_pos has already been processed and
if not - store the ROWID value.
RETURN
0 - record has not been processed yet
1 - record has already been processed.
-1 - an error occurred and query processing should be terminated.
Error code is stored in INDEX_MERGE::error
*/
int INDEX_MERGE::check_record_in()
{
return (dont_save)?
check_record() :
put_record();
}
/*
Stop remembering records in check().
(this should be called just before the last key scan)
RETURN
0 - OK
1 - error occurred initializing table index.
*/
int INDEX_MERGE::start_last_quick_select()
{
int result= 0;
if (!temp_table->uniques)
{
dont_save= true;
result= temp_table->file->index_init(0);
}
return result;
}
inline int INDEX_MERGE::put_record()
{
DBUG_ENTER("INDEX_MERGE::put_record");
copy_funcs(tmp_table_param.items_to_copy);
if ((error= temp_table->file->write_row(temp_table->record[0])))
{
if (error == HA_ERR_FOUND_DUPP_KEY ||
error == HA_ERR_FOUND_DUPP_UNIQUE)
DBUG_RETURN(1);
DBUG_PRINT("info",
("Error writing row to temp. table: %d, converting to myisam",
error));
if (create_myisam_from_heap(current_thd, temp_table, &tmp_table_param,
error,1))
{
DBUG_PRINT("info", ("Table conversion failed, bailing out"));
DBUG_RETURN(-1);
}
}
DBUG_RETURN(0);
}
inline int INDEX_MERGE::check_record()
{
int result= 1;
DBUG_ENTER("INDEX_MERGE::check_record");
if ((error= temp_table->file->index_read(temp_table->record[0],
head->file->ref,
head->file->ref_length,
HA_READ_KEY_EXACT)))
{
if (error != HA_ERR_KEY_NOT_FOUND)
result= -1;
else
result= 0;
}
DBUG_RETURN(result);
}
INDEX_MERGE::~INDEX_MERGE()
{
if (temp_table)
{
DBUG_PRINT("info", ("Freeing temp. table"));
free_tmp_table(current_thd, temp_table);
}
/* rowid_item is freed automatically */
list_node* node;
node= fields.first_node();
fields.remove(&node);
}
int QUICK_INDEX_MERGE_SELECT::get_next()
{
int result;
int put_result;
DBUG_ENTER("QUICK_INDEX_MERGE_SELECT::get_next");
do
{
while ((result= cur_quick_select->get_next()) == HA_ERR_END_OF_FILE)
{
cur_quick_select= cur_quick_it++;
if (!cur_quick_select)
break;
cur_quick_select->init();
cur_quick_select->reset();
if (last_quick_select == cur_quick_select)
{
if ((result= index_merge.start_last_quick_select()))
DBUG_RETURN(result);
}
}
if (result)
{
/*
table read error (including HA_ERR_END_OF_FILE on last quick select
in index_merge)
*/
DBUG_RETURN(result);
}
cur_quick_select->file->position(cur_quick_select->record);
put_result= index_merge.check_record_in();
}while(put_result == 1); /* While record is processed */
DBUG_RETURN((put_result != -1) ? result : index_merge.error);
}
/* get next possible record using quick-struct */ /* get next possible record using quick-struct */
int QUICK_SELECT::get_next() int QUICK_RANGE_SELECT::get_next()
{ {
DBUG_ENTER("get_next"); DBUG_ENTER("QUICK_RANGE_SELECT::get_next");
for (;;) for (;;)
{ {
...@@ -2518,7 +3298,7 @@ int QUICK_SELECT::get_next() ...@@ -2518,7 +3298,7 @@ int QUICK_SELECT::get_next()
Returns 0 if key <= range->max_key Returns 0 if key <= range->max_key
*/ */
int QUICK_SELECT::cmp_next(QUICK_RANGE *range_arg) int QUICK_RANGE_SELECT::cmp_next(QUICK_RANGE *range_arg)
{ {
if (range_arg->flag & NO_MAX_RANGE) if (range_arg->flag & NO_MAX_RANGE)
return 0; /* key can't be to large */ return 0; /* key can't be to large */
...@@ -2559,8 +3339,9 @@ int QUICK_SELECT::cmp_next(QUICK_RANGE *range_arg) ...@@ -2559,8 +3339,9 @@ int QUICK_SELECT::cmp_next(QUICK_RANGE *range_arg)
for now, this seems to work right at least. for now, this seems to work right at least.
*/ */
QUICK_SELECT_DESC::QUICK_SELECT_DESC(QUICK_SELECT *q, uint used_key_parts) QUICK_SELECT_DESC::QUICK_SELECT_DESC(QUICK_RANGE_SELECT *q,
: QUICK_SELECT(*q), rev_it(rev_ranges) uint used_key_parts)
: QUICK_RANGE_SELECT(*q), rev_it(rev_ranges)
{ {
bool not_read_after_key = file->table_flags() & HA_NOT_READ_AFTER_KEY; bool not_read_after_key = file->table_flags() & HA_NOT_READ_AFTER_KEY;
QUICK_RANGE *r; QUICK_RANGE *r;
...@@ -2827,7 +3608,23 @@ print_key(KEY_PART *key_part,const char *key,uint used_length) ...@@ -2827,7 +3608,23 @@ print_key(KEY_PART *key_part,const char *key,uint used_length)
} }
} }
static void print_quick(QUICK_SELECT *quick,key_map needed_reg) void print_quick_sel_imerge(QUICK_INDEX_MERGE_SELECT *quick,
key_map needed_reg)
{
DBUG_ENTER("print_param");
if (! _db_on_ || !quick)
DBUG_VOID_RETURN;
List_iterator_fast<QUICK_RANGE_SELECT> it(quick->quick_selects);
QUICK_RANGE_SELECT* quick_range_sel;
while ((quick_range_sel= it++))
{
print_quick_sel_range(quick_range_sel, needed_reg);
}
DBUG_VOID_RETURN;
}
void print_quick_sel_range(QUICK_RANGE_SELECT *quick,key_map needed_reg)
{ {
QUICK_RANGE *range; QUICK_RANGE *range;
DBUG_ENTER("print_param"); DBUG_ENTER("print_param");
......
...@@ -65,41 +65,198 @@ class QUICK_RANGE :public Sql_alloc { ...@@ -65,41 +65,198 @@ class QUICK_RANGE :public Sql_alloc {
} }
}; };
class INDEX_MERGE;
class QUICK_SELECT { /*
Quick select interface.
This class is parent for all QUICK_*_SELECT and FT_SELECT classes.
*/
class QUICK_SELECT_I
{
public: public:
ha_rows records; /* estimate of # of records to be retrieved */
double read_time; /* time to perform this retrieval */
TABLE *head;
/*
the only index this quick select uses, or MAX_KEY for
QUICK_INDEX_MERGE_SELECT
*/
uint index;
uint max_used_key_length, used_key_parts;
QUICK_SELECT_I();
virtual ~QUICK_SELECT_I(){};
virtual int init() = 0;
virtual void reset(void) = 0;
virtual int get_next() = 0; /* get next record to retrieve */
virtual bool reverse_sorted() = 0;
virtual bool unique_key_range() { return false; }
enum {
QS_TYPE_RANGE = 0,
QS_TYPE_INDEX_MERGE = 1,
QS_TYPE_RANGE_DESC = 2,
QS_TYPE_FULLTEXT = 3
};
/* Get type of this quick select - one of the QS_* values */
virtual int get_type() = 0;
};
struct st_qsel_param;
class SEL_ARG;
class QUICK_RANGE_SELECT : public QUICK_SELECT_I
{
protected:
bool next,dont_free; bool next,dont_free;
public:
int error; int error;
uint index, max_used_key_length, used_key_parts;
TABLE *head;
handler *file; handler *file;
byte *record; byte *record;
protected:
friend void print_quick_sel_range(QUICK_RANGE_SELECT *quick,
key_map needed_reg);
friend QUICK_RANGE_SELECT *get_quick_select_for_ref(TABLE *table,
struct st_table_ref *ref);
friend bool get_quick_keys(struct st_qsel_param *param,
QUICK_RANGE_SELECT *quick,KEY_PART *key,
SEL_ARG *key_tree,char *min_key,uint min_key_flag,
char *max_key, uint max_key_flag);
friend QUICK_RANGE_SELECT *get_quick_select(struct st_qsel_param*,uint idx,
SEL_ARG *key_tree,
MEM_ROOT *alloc);
friend class QUICK_SELECT_DESC;
List<QUICK_RANGE> ranges; List<QUICK_RANGE> ranges;
List_iterator<QUICK_RANGE> it; List_iterator<QUICK_RANGE> it;
QUICK_RANGE *range; QUICK_RANGE *range;
MEM_ROOT alloc; MEM_ROOT alloc;
KEY_PART *key_parts; KEY_PART *key_parts;
ha_rows records;
double read_time;
QUICK_SELECT(TABLE *table,uint index_arg,bool no_alloc=0);
virtual ~QUICK_SELECT();
void reset(void) { next=0; it.rewind(); }
int init() { return error=file->index_init(index); }
virtual int get_next();
virtual bool reverse_sorted() { return 0; }
int cmp_next(QUICK_RANGE *range); int cmp_next(QUICK_RANGE *range);
public:
QUICK_RANGE_SELECT(TABLE *table,uint index_arg,bool no_alloc=0,
MEM_ROOT *parent_alloc=NULL);
~QUICK_RANGE_SELECT();
void reset(void) { next=0; it.rewind(); }
int init();
int get_next();
bool reverse_sorted() { return 0; }
bool unique_key_range(); bool unique_key_range();
int get_type() { return QS_TYPE_RANGE; }
};
/*
Helper class for keeping track of rows that have been passed to output
in index_merge access method.
NOTES
Current implementation uses a temporary table to store ROWIDs of rows that
have been passed to output. In the future it might be changed to use more
efficient mechanisms, like Unique class.
*/
class INDEX_MERGE
{
public:
INDEX_MERGE(THD *thd_arg);
~INDEX_MERGE();
int init(TABLE *table);
int check_record_in();
int start_last_quick_select();
int error;
private:
/* The only field in temporary table */
class Item_rowid : public Item_str_func
{
TABLE *head; /* source table */
public:
Item_rowid(TABLE *table) : head(table)
{
max_length= table->file->ref_length;
collation.set(&my_charset_bin);
};
const char *func_name() const { return "rowid"; }
bool const_item() const { return 0; }
String *val_str(String *);
void fix_length_and_dec()
{}
};
/* Check if record has been processed and save it if it wasn't */
inline int put_record();
/* Check if record has been processed without saving it */
inline int check_record();
/* If true, check_record_in does't store ROWIDs it is passed. */
bool dont_save;
THD *thd;
TABLE *head; /* source table */
TABLE *temp_table; /* temp. table used for values storage */
TMP_TABLE_PARAM tmp_table_param; /* temp. table creation parameters */
Item_rowid *rowid_item; /* the only field in temp. table */
List<Item> fields; /* temp. table fields list
(the only element is rowid_item) */
ORDER order; /* key for temp. table (rowid_item) */
}; };
class QUICK_SELECT_DESC: public QUICK_SELECT /*
Index merge quick select.
It is implemented as a container for several QUICK_RANGE_SELECTs.
*/
class QUICK_INDEX_MERGE_SELECT : public QUICK_SELECT_I
{
public:
QUICK_INDEX_MERGE_SELECT(THD *thd, TABLE *table);
~QUICK_INDEX_MERGE_SELECT();
int init();
void reset(void);
int get_next();
bool reverse_sorted() { return false; }
bool unique_key_range() { return false; }
int get_type() { return QS_TYPE_INDEX_MERGE; }
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 which is currently used for rows retrieval */
List_iterator_fast<QUICK_RANGE_SELECT> cur_quick_it;
QUICK_RANGE_SELECT* cur_quick_select;
/*
Last element in quick_selects list.
INDEX_MERGE::start_last_quick_select is called before retrieving
rows for it.
*/
QUICK_RANGE_SELECT* last_quick_select;
/*
Used to keep track of what records have been already passed to output
when doing index_merge access (NULL means no index_merge)
*/
INDEX_MERGE index_merge;
MEM_ROOT alloc;
};
class QUICK_SELECT_DESC: public QUICK_RANGE_SELECT
{ {
public: public:
QUICK_SELECT_DESC(QUICK_SELECT *q, uint used_key_parts); QUICK_SELECT_DESC(QUICK_RANGE_SELECT *q, uint used_key_parts);
int get_next(); int get_next();
bool reverse_sorted() { return 1; } bool reverse_sorted() { return 1; }
int get_type() { return QS_TYPE_RANGE_DESC; }
private: private:
int cmp_prev(QUICK_RANGE *range); int cmp_prev(QUICK_RANGE *range);
bool range_reads_after_key(QUICK_RANGE *range); bool range_reads_after_key(QUICK_RANGE *range);
...@@ -114,7 +271,7 @@ private: ...@@ -114,7 +271,7 @@ private:
class SQL_SELECT :public Sql_alloc { class SQL_SELECT :public Sql_alloc {
public: public:
QUICK_SELECT *quick; // If quick-select used QUICK_SELECT_I *quick; // If quick-select used
COND *cond; // where condition COND *cond; // where condition
TABLE *head; TABLE *head;
IO_CACHE file; // Positions to used records IO_CACHE file; // Positions to used records
...@@ -134,6 +291,6 @@ class SQL_SELECT :public Sql_alloc { ...@@ -134,6 +291,6 @@ class SQL_SELECT :public Sql_alloc {
bool force_quick_range=0); bool force_quick_range=0);
}; };
QUICK_SELECT *get_quick_select_for_ref(TABLE *table, struct st_table_ref *ref); QUICK_RANGE_SELECT *get_quick_select_for_ref(TABLE *table, struct st_table_ref *ref);
#endif #endif
...@@ -537,8 +537,8 @@ int THD::send_explain_fields(select_result *result) ...@@ -537,8 +537,8 @@ int THD::send_explain_fields(select_result *result)
item->maybe_null=1; item->maybe_null=1;
field_list.push_back(item=new Item_empty_string("key",NAME_LEN)); field_list.push_back(item=new Item_empty_string("key",NAME_LEN));
item->maybe_null=1; item->maybe_null=1;
field_list.push_back(item=new Item_return_int("key_len",3, field_list.push_back(item=new Item_empty_string("key_len",
MYSQL_TYPE_LONGLONG)); NAME_LEN*MAX_KEY));
item->maybe_null=1; item->maybe_null=1;
field_list.push_back(item=new Item_empty_string("ref", field_list.push_back(item=new Item_empty_string("ref",
NAME_LEN*MAX_REF_PARTS)); NAME_LEN*MAX_REF_PARTS));
......
...@@ -135,6 +135,12 @@ public: ...@@ -135,6 +135,12 @@ public:
last= &first; last= &first;
return tmp->info; return tmp->info;
} }
inline void concat(base_list *list)
{
*last= list->first;
last= list->last;
elements+= list->elements;
}
inline list_node* last_node() { return *last; } inline list_node* last_node() { return *last; }
inline list_node* first_node() { return first;} inline list_node* first_node() { return first;}
inline void *head() { return first->info; } inline void *head() { return first->info; }
...@@ -255,6 +261,7 @@ public: ...@@ -255,6 +261,7 @@ public:
} }
empty(); empty();
} }
inline void concat(List<T> *list) { base_list::concat(list); }
}; };
......
...@@ -32,7 +32,8 @@ ...@@ -32,7 +32,8 @@
const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref", const char *join_type_str[]={ "UNKNOWN","system","const","eq_ref","ref",
"MAYBE_REF","ALL","range","index","fulltext", "MAYBE_REF","ALL","range","index","fulltext",
"ref_or_null","simple_in","index_in" "ref_or_null","simple_in","index_in",
"index_merge"
}; };
static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array); static void optimize_keyuse(JOIN *join, DYNAMIC_ARRAY *keyuse_array);
...@@ -114,7 +115,6 @@ static int join_read_next_same_or_null(READ_RECORD *info); ...@@ -114,7 +115,6 @@ static int join_read_next_same_or_null(READ_RECORD *info);
static COND *make_cond_for_table(COND *cond,table_map table, static COND *make_cond_for_table(COND *cond,table_map table,
table_map used_table); table_map used_table);
static Item* part_of_refkey(TABLE *form,Field *field); static Item* part_of_refkey(TABLE *form,Field *field);
static uint find_shortest_key(TABLE *table, key_map usable_keys);
static bool test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order, static bool test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,
ha_rows select_limit, bool no_changes); ha_rows select_limit, bool no_changes);
static int create_sort_index(THD *thd, JOIN *join, ORDER *order, static int create_sort_index(THD *thd, JOIN *join, ORDER *order,
...@@ -3285,7 +3285,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond) ...@@ -3285,7 +3285,7 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
with key reading */ with key reading */
if (tab->needed_reg == 0 && tab->type != JT_EQ_REF if (tab->needed_reg == 0 && tab->type != JT_EQ_REF
&& tab->type != JT_FT && (tab->type != JT_REF || && tab->type != JT_FT && (tab->type != JT_REF ||
(uint) tab->ref.key == tab->quick->index)) (uint) tab->ref.key == tab->quick->index))
{ {
sel->quick=tab->quick; // Use value from get_quick_... sel->quick=tab->quick; // Use value from get_quick_...
sel->quick_keys=0; sel->quick_keys=0;
...@@ -6473,7 +6473,7 @@ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx, ...@@ -6473,7 +6473,7 @@ static int test_if_order_by_key(ORDER *order, TABLE *table, uint idx,
return reverse; return reverse;
} }
static uint find_shortest_key(TABLE *table, key_map usable_keys) uint find_shortest_key(TABLE *table, key_map usable_keys)
{ {
uint min_length= (uint) ~0; uint min_length= (uint) ~0;
uint best= MAX_KEY; uint best= MAX_KEY;
...@@ -6601,6 +6601,9 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, ...@@ -6601,6 +6601,9 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit,
} }
else if (select && select->quick) // Range found by opt_range else if (select && select->quick) // Range found by opt_range
{ {
/* assume results are not ordered when index merge is used */
if (select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
DBUG_RETURN(0);
ref_key= select->quick->index; ref_key= select->quick->index;
ref_key_parts= select->quick->used_key_parts; ref_key_parts= select->quick->used_key_parts;
} }
...@@ -6635,6 +6638,10 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, ...@@ -6635,6 +6638,10 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit,
} }
else else
{ {
/*
We have verified above that select->quick is not
index_merge quick select.
*/
select->quick->index= new_ref_key; select->quick->index= new_ref_key;
select->quick->init(); select->quick->init();
} }
...@@ -6656,10 +6663,13 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, ...@@ -6656,10 +6663,13 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit,
*/ */
if (!select->quick->reverse_sorted()) if (!select->quick->reverse_sorted())
{ {
if (table->file->index_flags(ref_key) & HA_NOT_READ_PREFIX_LAST) if (table->file->index_flags(ref_key) & HA_NOT_READ_PREFIX_LAST ||
(select->quick->get_type() ==
QUICK_SELECT_I::QS_TYPE_INDEX_MERGE))
DBUG_RETURN(0); // Use filesort DBUG_RETURN(0); // Use filesort
// ORDER BY range_key DESC
QUICK_SELECT_DESC *tmp=new QUICK_SELECT_DESC(select->quick, // ORDER BY range_key DESC
QUICK_SELECT_DESC *tmp=new QUICK_SELECT_DESC((QUICK_RANGE_SELECT*)(select->quick),
used_key_parts); used_key_parts);
if (!tmp || tmp->error) if (!tmp || tmp->error)
{ {
...@@ -6794,8 +6804,11 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order, ...@@ -6794,8 +6804,11 @@ create_sort_index(THD *thd, JOIN *join, ORDER *order,
{ {
select->quick=tab->quick; select->quick=tab->quick;
tab->quick=0; tab->quick=0;
/* We can only use 'Only index' if quick key is same as ref_key */ /*
if (table->key_read && (uint) tab->ref.key != select->quick->index) We can only use 'Only index' if quick key is same as ref_key
and in index_merge 'Only index' cannot be used
*/
if (table->key_read && ((uint) tab->ref.key != select->quick->index))
{ {
table->key_read=0; table->key_read=0;
table->file->extra(HA_EXTRA_NO_KEYREAD); table->file->extra(HA_EXTRA_NO_KEYREAD);
...@@ -8598,12 +8611,15 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, ...@@ -8598,12 +8611,15 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
JOIN_TAB *tab=join->join_tab+i; JOIN_TAB *tab=join->join_tab+i;
TABLE *table=tab->table; TABLE *table=tab->table;
char buff[512],*buff_ptr=buff; char buff[512],*buff_ptr=buff;
char buff1[512], buff2[512]; char buff1[512], buff2[512], buff3[512];
char keylen_str_buf[64];
char derived_name[64]; char derived_name[64];
String tmp1(buff1,sizeof(buff1),cs); String tmp1(buff1,sizeof(buff1),cs);
String tmp2(buff2,sizeof(buff2),cs); String tmp2(buff2,sizeof(buff2),cs);
String tmp3(buff3,sizeof(buff3),cs);
tmp1.length(0); tmp1.length(0);
tmp2.length(0); tmp2.length(0);
tmp3.length(0);
item_list.empty(); item_list.empty();
item_list.push_back(new Item_int((int32) item_list.push_back(new Item_int((int32)
...@@ -8612,7 +8628,13 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, ...@@ -8612,7 +8628,13 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
strlen(join->select_lex->type), strlen(join->select_lex->type),
cs)); cs));
if (tab->type == JT_ALL && tab->select && tab->select->quick) if (tab->type == JT_ALL && tab->select && tab->select->quick)
tab->type= JT_RANGE; {
if (tab->select->quick->get_type() ==
QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
tab->type = JT_INDEX_MERGE;
else
tab->type = JT_RANGE;
}
if (table->derived_select_number) if (table->derived_select_number)
{ {
/* Derived table name generation */ /* Derived table name generation */
...@@ -8646,10 +8668,14 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, ...@@ -8646,10 +8668,14 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
if (tab->ref.key_parts) if (tab->ref.key_parts)
{ {
KEY *key_info=table->key_info+ tab->ref.key; KEY *key_info=table->key_info+ tab->ref.key;
register uint length;
item_list.push_back(new Item_string(key_info->name, item_list.push_back(new Item_string(key_info->name,
strlen(key_info->name), strlen(key_info->name),
system_charset_info)); system_charset_info));
item_list.push_back(new Item_int((int32) tab->ref.key_length)); length= longlong2str(tab->ref.key_length, keylen_str_buf, 10) -
keylen_str_buf;
item_list.push_back(new Item_string(keylen_str_buf, length,
system_charset_info));
for (store_key **ref=tab->ref.key_copy ; *ref ; ref++) for (store_key **ref=tab->ref.key_copy ; *ref ; ref++)
{ {
if (tmp2.length()) if (tmp2.length())
...@@ -8661,18 +8687,60 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, ...@@ -8661,18 +8687,60 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
else if (tab->type == JT_NEXT) else if (tab->type == JT_NEXT)
{ {
KEY *key_info=table->key_info+ tab->index; KEY *key_info=table->key_info+ tab->index;
register uint length;
item_list.push_back(new Item_string(key_info->name, item_list.push_back(new Item_string(key_info->name,
strlen(key_info->name),cs)); strlen(key_info->name),cs));
item_list.push_back(new Item_int((int32) key_info->key_length)); length= longlong2str(key_info->key_length, keylen_str_buf, 10) -
keylen_str_buf;
item_list.push_back(new Item_string(keylen_str_buf,
length,
system_charset_info));
item_list.push_back(item_null); item_list.push_back(item_null);
} }
else if (tab->select && tab->select->quick) else if (tab->select && tab->select->quick)
{ {
KEY *key_info=table->key_info+ tab->select->quick->index; if (tab->select->quick->get_type() ==
item_list.push_back(new Item_string(key_info->name, QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
strlen(key_info->name),cs)); {
item_list.push_back(new Item_int((int32) tab->select->quick-> QUICK_INDEX_MERGE_SELECT *quick_imerge=
max_used_key_length)); (QUICK_INDEX_MERGE_SELECT*)tab->select->quick;
QUICK_RANGE_SELECT *quick;
List_iterator_fast<QUICK_RANGE_SELECT> it(quick_imerge->
quick_selects);
while ((quick= it++))
{
KEY *key_info= table->key_info + quick->index;
register uint length;
if (tmp3.length())
tmp3.append(',');
tmp3.append(key_info->name);
if (tmp2.length())
tmp2.append(',');
length= longlong2str(quick->max_used_key_length, keylen_str_buf,
10) -
keylen_str_buf;
tmp2.append(keylen_str_buf, length);
}
}
else
{
KEY *key_info= table->key_info + tab->select->quick->index;
register uint length;
tmp3.append(key_info->name);
length= longlong2str(tab->select->quick->max_used_key_length,
keylen_str_buf, 10) -
keylen_str_buf;
tmp2.append(keylen_str_buf, length);
}
item_list.push_back(new Item_string(tmp3.ptr(),tmp3.length(),cs));
item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs));
item_list.push_back(item_null); item_list.push_back(item_null);
} }
else else
......
...@@ -76,7 +76,7 @@ typedef struct st_join_cache { ...@@ -76,7 +76,7 @@ typedef struct st_join_cache {
enum join_type { JT_UNKNOWN,JT_SYSTEM,JT_CONST,JT_EQ_REF,JT_REF,JT_MAYBE_REF, enum join_type { JT_UNKNOWN,JT_SYSTEM,JT_CONST,JT_EQ_REF,JT_REF,JT_MAYBE_REF,
JT_ALL, JT_RANGE, JT_NEXT, JT_FT, JT_REF_OR_NULL, JT_ALL, JT_RANGE, JT_NEXT, JT_FT, JT_REF_OR_NULL,
JT_SIMPLE_IN, JT_INDEX_IN}; JT_SIMPLE_IN, JT_INDEX_IN, JT_INDEX_MERGE};
class JOIN; class JOIN;
...@@ -85,7 +85,7 @@ typedef struct st_join_table { ...@@ -85,7 +85,7 @@ typedef struct st_join_table {
KEYUSE *keyuse; /* pointer to first used key */ KEYUSE *keyuse; /* pointer to first used key */
SQL_SELECT *select; SQL_SELECT *select;
COND *select_cond; COND *select_cond;
QUICK_SELECT *quick; QUICK_SELECT_I *quick;
Item *on_expr; Item *on_expr;
const char *info; const char *info;
byte *null_ref_key; byte *null_ref_key;
...@@ -307,6 +307,7 @@ void copy_fields(TMP_TABLE_PARAM *param); ...@@ -307,6 +307,7 @@ void copy_fields(TMP_TABLE_PARAM *param);
void copy_funcs(Item **func_ptr); void copy_funcs(Item **func_ptr);
bool create_myisam_from_heap(THD *thd, TABLE *table, TMP_TABLE_PARAM *param, bool create_myisam_from_heap(THD *thd, TABLE *table, TMP_TABLE_PARAM *param,
int error, bool ignore_last_dupp_error); int error, bool ignore_last_dupp_error);
uint find_shortest_key(TABLE *table, key_map usable_keys);
/* functions from opt_sum.cc */ /* functions from opt_sum.cc */
int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds); int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds);
......
...@@ -179,9 +179,39 @@ TEST_join(JOIN *join) ...@@ -179,9 +179,39 @@ TEST_join(JOIN *join)
" quick select checked for each record (keys: %d)\n", " quick select checked for each record (keys: %d)\n",
(int) tab->select->quick_keys); (int) tab->select->quick_keys);
else if (tab->select->quick) else if (tab->select->quick)
fprintf(DBUG_FILE," quick select used on key %s, length: %d\n", {
int quick_type= tab->select->quick->get_type();
if ((quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) ||
(quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC))
{
fprintf(DBUG_FILE,
" quick select used on key %s, length: %d\n",
form->key_info[tab->select->quick->index].name, form->key_info[tab->select->quick->index].name,
tab->select->quick->max_used_key_length); tab->select->quick->max_used_key_length);
}
else if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
{
QUICK_INDEX_MERGE_SELECT *quick_imerge=
(QUICK_INDEX_MERGE_SELECT*)tab->select->quick;
QUICK_RANGE_SELECT *quick;
fprintf(DBUG_FILE,
" index_merge quick select used\n");
List_iterator_fast<QUICK_RANGE_SELECT> it(quick_imerge->quick_selects);
while ((quick = it++))
{
fprintf(DBUG_FILE,
" range quick select: key %s, length: %d\n",
form->key_info[quick->index].name,
quick->max_used_key_length);
}
}
else
{
fprintf(DBUG_FILE,
" quick select of unknown nature used\n");
}
}
else else
VOID(fputs(" select used\n",DBUG_FILE)); VOID(fputs(" select used\n",DBUG_FILE));
} }
......
...@@ -117,7 +117,8 @@ int st_select_lex_unit::prepare(THD *thd, select_result *sel_result, ...@@ -117,7 +117,8 @@ int st_select_lex_unit::prepare(THD *thd, select_result *sel_result,
{ {
SELECT_LEX *lex_select_save= thd->lex.current_select; SELECT_LEX *lex_select_save= thd->lex.current_select;
SELECT_LEX *select_cursor; SELECT_LEX *select_cursor;
DBUG_ENTER("st_select_lex_unit::prepare"); SELECT_LEX *sl;
DBUG_ENTER("st_select_lex_unit::prepare");
if (prepared) if (prepared)
DBUG_RETURN(0); DBUG_RETURN(0);
...@@ -185,7 +186,7 @@ int st_select_lex_unit::prepare(THD *thd, select_result *sel_result, ...@@ -185,7 +186,7 @@ int st_select_lex_unit::prepare(THD *thd, select_result *sel_result,
union_result->not_describe=1; union_result->not_describe=1;
union_result->tmp_table_param=tmp_table_param; union_result->tmp_table_param=tmp_table_param;
for (SELECT_LEX *sl= select_cursor; sl; sl= sl->next_select()) for (sl= select_cursor; sl; sl= sl->next_select())
{ {
JOIN *join= new JOIN(thd, sl->item_list, JOIN *join= new JOIN(thd, sl->item_list,
sl->options | thd->options | SELECT_NO_UNLOCK, sl->options | thd->options | SELECT_NO_UNLOCK,
......
...@@ -171,10 +171,18 @@ int mysql_update(THD *thd, ...@@ -171,10 +171,18 @@ int mysql_update(THD *thd,
init_ftfuncs(thd, &thd->lex.select_lex, 1); init_ftfuncs(thd, &thd->lex.select_lex, 1);
/* Check if we are modifying a key that we are used to search with */ /* Check if we are modifying a key that we are used to search with */
if (select && select->quick) if (select && select->quick)
used_key_is_modified= (!select->quick->unique_key_range() && {
check_if_key_used(table, if (select->quick->get_type() != QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
(used_index=select->quick->index), {
fields)); used_index= select->quick->index;
used_key_is_modified= (!select->quick->unique_key_range() &&
check_if_key_used(table,used_index,fields));
}
else
{
used_key_is_modified= true;
}
}
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
...@@ -688,8 +696,26 @@ static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields) ...@@ -688,8 +696,26 @@ static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields)
case JT_ALL: case JT_ALL:
/* If range search on index */ /* If range search on index */
if (join_tab->quick) if (join_tab->quick)
return !check_if_key_used(table, join_tab->quick->index, {
*fields); if (join_tab->quick->get_type() != QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
{
return !check_if_key_used(table,join_tab->quick->index,*fields);
}
else
{
QUICK_INDEX_MERGE_SELECT *qsel_imerge=
(QUICK_INDEX_MERGE_SELECT*)(join_tab->quick);
List_iterator_fast<QUICK_RANGE_SELECT> it(qsel_imerge->quick_selects);
QUICK_RANGE_SELECT *quick;
while ((quick= it++))
{
if (check_if_key_used(table, quick->index, *fields))
return 0;
}
return 1;
}
}
/* If scanning in clustered key */ /* If scanning in clustered key */
if ((table->file->table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) && if ((table->file->table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) &&
table->primary_key < MAX_KEY) table->primary_key < MAX_KEY)
......
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