Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
M
MariaDB
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexedi
MariaDB
Commits
144f82a9
Commit
144f82a9
authored
Dec 19, 2010
by
Igor Babaev
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Addressed the feedback from the review of Monty on the cumulative patch for
mwl#21.
parent
ac984f41
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
527 additions
and
412 deletions
+527
-412
include/my_tree.h
include/my_tree.h
+9
-0
sql/filesort.cc
sql/filesort.cc
+44
-38
sql/mysql_priv.h
sql/mysql_priv.h
+3
-0
sql/opt_range.cc
sql/opt_range.cc
+409
-313
sql/opt_range.h
sql/opt_range.h
+60
-60
sql/sql_class.h
sql/sql_class.h
+2
-1
No files found.
include/my_tree.h
View file @
144f82a9
...
...
@@ -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
...
...
sql/filesort.cc
View file @
144f82a9
...
...
@@ -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:
...
...
sql/mysql_priv.h
View file @
144f82a9
...
...
@@ -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
...
...
sql/opt_range.cc
View file @
144f82a9
...
...
@@ -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_INTERSEC
T_SELECT
()
QUICK_INDEX_
SORT_SELECT
::~
QUICK_INDEX_SOR
T_SELECT
()
{
List_iterator_fast
<
QUICK_RANGE_SELECT
>
quick_it
(
quick_selects
);
QUICK_RANGE_SELECT
*
quick
;
DBUG_ENTER
(
"QUICK_INDEX_
INTERSECT_SELECT::~QUICK_INDEX_INTERSEC
T_SELECT"
);
DBUG_ENTER
(
"QUICK_INDEX_
SORT_SELECT::~QUICK_INDEX_SOR
T_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_intersect
ion
_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_INTERSECT
ION
_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_intersect
ion
_info
typedef
struct
st_partial_index_intersect_info
{
COMMON_INDEX_INTERSECT
ION_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_INTERSECT
ION
_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_INTERSECT
ION
_INFO
*
common
,
PARTIAL_INDEX_INTERSECT
ION
_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
;
uin
t
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_INTERSECT
ION
_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 f
i
r the extended plan.
the case, the function fills in the structure f
o
r 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_INTERSECT
ION
_INFO
*
curr
,
bool
check_index_intersect_extension
(
PARTIAL_INDEX_INTERSECT_INFO
*
curr
,
INDEX_SCAN_INFO
*
ext_index_scan
,
PARTIAL_INDEX_INTERSECT
ION
_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_INTERSECT
ION
_INFO
*
curr
)
void
find_index_intersect_best_extension
(
PARTIAL_INDEX_INTERSECT_INFO
*
curr
)
{
PARTIAL_INDEX_INTERSECT
ION
_INFO
next
;
COMMON_INDEX_INTERSECT
ION
_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_INTERSECT
ION
_INFO
common
;
PARTIAL_INDEX_INTERSECT
ION
_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
);
...
...
sql/opt_range.h
View file @
144f82a9
...
...
@@ -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
);
};
...
...
sql/sql_class.h
View file @
144f82a9
...
...
@@ -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
);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment