Quelle multi_range_read.h
Sprache: C
/*
Copyright ( c ) 2009 , 2011 , Monty Program Ab
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; version 2 of the License .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA */
/**
@ defgroup DS - MRR declarations
@ {
*/
/**
A Disk - Sweep implementation of MRR Interface ( DS - MRR for short )
This is a " plugin " ( * ) for storage engines that allows to
1 . When doing index scans , read table rows in rowid order ;
2 . when making many index lookups , do them in key order and don ' t
lookup the same key value multiple times ;
3 . Do both # 1 and # 2 , when applicable .
These changes are expected to speed up query execution for disk - based
storage engines running io - bound loads and " big " queries ( ie . queries that
do joins and enumerate lots of records ) .
( * ) - only conceptually . No dynamic loading or binary compatibility of any
kind .
General scheme of things :
SQL Layer code
| | |
v v v
- | - - - | - - - | - - - - handler - > multi_range_read_XXX ( ) function calls
| | |
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/ DS - MRR module \
| ( order / de - duplicate lookup keys , |
| scan indexes in key order , |
| order / de - duplicate rowids , |
| retrieve full record reads in rowid |
| order ) |
\ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /
| | |
- | - - - | - - - | - - - - - handler - > read_range_first ( ) / read_range_next ( ) ,
| | | handler - > index_read ( ) , handler - > rnd_pos ( ) calls .
| | |
v v v
Storage engine internals
Currently DS - MRR is used by MyISAM , InnoDB and Maria storage engines .
Potentially it can be used with any table handler that has disk - based data
storage and has better performance when reading data in rowid order .
*/
#include "sql_lifo_buffer.h"
class DsMrr_impl;
class Mrr_ordered_index_reader;
/* A structure with key parameters that's shared among several classes */
class Key_parameters
{
public :
uint key_tuple_length; /* Length of index lookup tuple, in bytes */
key_part_map key_tuple_map; /* keyparts used in index lookup tuples */
/*
This is
= key_tuple_length if we copy keys to buffer
= sizeof ( void * ) if we ' re using pointers to materialized keys .
*/
uint key_size_in_keybuf;
/* TRUE <=> don't copy key values, use pointers to them instead. */
bool use_key_pointers;
/* TRUE <=> We can get at most one index tuple for a lookup key */
bool index_ranges_unique;
};
/**
A class to enumerate ( record , range_id ) pairs that match given key value .
@ note
The idea is that we have a Lifo_buffer which holds ( key , range_id ) pairs
ordered by key value . From the front of the buffer we see
( key_val1 , range_id1 ) , ( key_val1 , range_id2 ) . . . ( key_val2 , range_idN )
we take the first elements that have the same key value ( key_val1 in the
example above ) , and make lookup into the table . The table will have
multiple matches for key_val1 :
= = Table Index = =
. . .
key_val1 - > key_val1 , index_tuple1
key_val1 , index_tuple2
. . .
key_val1 , index_tupleN
. . .
Our goal is to produce all possible combinations , i . e . we need :
{ ( key_val1 , index_tuple1 ) , range_id1 }
{ ( key_val1 , index_tuple1 ) , range_id2 }
. . . . . . |
{ ( key_val1 , index_tuple1 ) , range_idN } ,
{ ( key_val1 , index_tuple2 ) , range_id1 }
{ ( key_val1 , index_tuple2 ) , range_id2 }
. . . . . . |
{ ( key_val1 , index_tuple2 ) , range_idN } ,
. . . . . . . . .
{ ( key_val1 , index_tupleK ) , range_idN }
*/
class Key_value_records_iterator
{
/* Use this to get table handler, key buffer and other parameters */
Mrr_ordered_index_reader *owner;
/* Iterator to get (key, range_id) pairs from */
Lifo_buffer_iterator identical_key_it;
/*
Last of the identical key values ( when we get this pointer from
identical_key_it , it will be time to stop ) .
*/
uchar *last_identical_key_ptr;
/*
FALSE < = > we ' re right after the init ( ) call , the record has been already
read with owner - > file - > index_read_map ( ) call
*/
bool get_next_row;
public :
int init(Mrr_ordered_index_reader *owner_arg);
int get_next(range_id_t *range_info);
void move_to_next_key_value();
};
/*
Buffer manager interface . Mrr_reader objects use it to inquire DsMrr_impl
to manage buffer space for them .
*/
typedef struct st_buffer_manager
{
public :
/* Opaque value to be passed as the first argument to all member functions */
void *arg;
/*
This is called when we ' ve freed more space from the rowid buffer . The
callee will get the unused space from the rowid buffer and give it to the
key buffer .
*/
void (*redistribute_buffer_space)(void *arg);
/*
This is called when both key and rowid buffers are empty , and so it ' s time
to reset them to their original size ( They ' ve lost their original size ,
because we were dynamically growing rowid buffer and shrinking key buffer ) .
*/
void (*reset_buffer_sizes)(void *arg);
} Buffer_manager;
/*
Mrr_reader - DS - MRR execution strategy abstraction
A reader produces ( [ index ] _ record , range_info ) pairs , and requires periodic
refill operations .
- one starts using the reader by calling reader - > get_next ( ) ,
- when a get_next ( ) call returns HA_ERR_END_OF_FILE , one must call
refill_buffer ( ) before they can make more get_next ( ) calls .
- when refill_buffer ( ) returns HA_ERR_END_OF_FILE , this means the real
end of stream and get_next ( ) should not be called anymore .
Both functions can return other error codes , these mean unrecoverable errors
after which one cannot continue .
*/
class Mrr_reader
{
public :
virtual int get_next(range_id_t *range_info) = 0 ;
virtual int refill_buffer(bool initial) = 0 ;
virtual ~Mrr_reader() = default ; /* just to remove compiler warning */
};
/*
A common base for readers that do index scans and produce index tuples
*/
class Mrr_index_reader : public Mrr_reader
{
protected :
handler *file; /* Handler object to use */
public :
virtual int init(handler *h_arg, RANGE_SEQ_IF *seq_funcs,
void *seq_init_param, uint n_ranges,
uint mode, Key_parameters *key_par,
Lifo_buffer *key_buffer,
Buffer_manager *buf_manager_arg) = 0 ;
/* Get pointer to place where every get_next() call will put rowid */
virtual uchar *get_rowid_ptr() = 0 ;
/* Get the rowid (call this after get_next() call) */
virtual void position();
virtual bool skip_record(range_id_t range_id, uchar *rowid) = 0 ;
virtual void interrupt_read() {}
virtual void resume_read() {}
};
/*
A " bypass " index reader that just does and index scan . The index scan is done
by calling default MRR implementation ( i . e . handler : : multi_range_read_XXX ( ) )
functions .
*/
class Mrr_simple_index_reader : public Mrr_index_reader
{
public :
int init(handler *h_arg, RANGE_SEQ_IF *seq_funcs,
void *seq_init_param, uint n_ranges,
uint mode, Key_parameters *key_par,
Lifo_buffer *key_buffer,
Buffer_manager *buf_manager_arg) override;
int get_next(range_id_t *range_info) override;
int refill_buffer(bool initial) override { return initial? 0 : HA_ERR_END_OF_FILE; }
uchar *get_rowid_ptr() override { return file->ref; }
bool skip_record(range_id_t range_id, uchar *rowid) override
{
return (file->mrr_funcs.skip_record &&
file->mrr_funcs.skip_record(file->mrr_iter, range_id, rowid));
}
};
/*
A reader that sorts the key values before it makes the index lookups .
*/
class Mrr_ordered_index_reader : public Mrr_index_reader
{
public :
int init(handler *h_arg, RANGE_SEQ_IF *seq_funcs,
void *seq_init_param, uint n_ranges,
uint mode, Key_parameters *key_par,
Lifo_buffer *key_buffer,
Buffer_manager *buf_manager_arg) override;
int get_next(range_id_t *range_info) override;
int refill_buffer(bool initial) override;
uchar *get_rowid_ptr() override { return file->ref; }
bool skip_record(range_id_t range_info, uchar *rowid) override
{
return (mrr_funcs.skip_record &&
mrr_funcs.skip_record(mrr_iter, range_info, rowid));
}
bool skip_index_tuple(range_id_t range_info)
{
return (mrr_funcs.skip_index_tuple &&
mrr_funcs.skip_index_tuple(mrr_iter, range_info));
}
bool set_interruption_temp_buffer(uint rowid_length, uint key_len,
uint saved_pk_len,
uchar **space_start, uchar *space_end);
void set_no_interruption_temp_buffer();
void interrupt_read() override;
void resume_read() override;
void position() override;
private :
Key_value_records_iterator kv_it;
bool scanning_key_val_iter;
/* Buffer to store (key, range_id) pairs */
Lifo_buffer *key_buffer;
/* This manages key buffer allocation and sizing for us */
Buffer_manager *buf_manager;
Key_parameters keypar; /* index scan and lookup tuple parameters */
/* TRUE <=> need range association, buffers hold {rowid, range_id} pairs */
bool is_mrr_assoc;
/* Range sequence iteration members */
RANGE_SEQ_IF mrr_funcs;
range_seq_t mrr_iter;
/* TRUE == reached eof when enumerating ranges */
bool source_exhausted;
/*
Following members are for interrupt_read ( ) / resume_read ( ) . The idea is that
in some cases index scan that is done by this object is interrupted by
rnd_pos ( ) calls made by Mrr_ordered_rndpos_reader . The problem is that
we ' re sharing handler - > record [ 0 ] with that object , and it destroys its
contents .
We need to save / restore our current
- index tuple ( for pushed index condition checks )
- clustered primary key values ( again , for pushed index condition checks )
- rowid of the last record we ' ve retrieved ( in case this rowid matches
multiple ranges and we ' ll need to return it again )
*/
bool support_scan_interruptions;
/* Space where we save the rowid of the last record we've returned */
uchar *saved_rowid;
/* TRUE <=> saved_rowid has the last saved rowid */
bool have_saved_rowid;
uchar *saved_key_tuple; /* Saved current key tuple */
uchar *saved_primary_key; /* Saved current primary key tuple */
/*
TRUE < = > saved_key_tuple ( and saved_primary_key when applicable ) have
valid values .
*/
bool read_was_interrupted;
static int compare_keys(void *arg, const void *key1, const void *key2);
static int compare_keys_reverse(void *arg, const void *key1,
const void *key2);
friend class Key_value_records_iterator;
friend class DsMrr_impl;
friend class Mrr_ordered_rndpos_reader;
};
/*
A reader that gets rowids from an Mrr_index_reader , and then sorts them
before getting full records with handler - > rndpos ( ) calls .
*/
class Mrr_ordered_rndpos_reader : public Mrr_reader
{
public :
int init(handler *file, Mrr_index_reader *index_reader, uint mode,
Lifo_buffer *buf, Rowid_filter *filter);
int get_next(range_id_t *range_info) override;
int refill_buffer(bool initial) override;
private :
handler *file; /* Handler to use */
/* This what we get (rowid, range_info) pairs from */
Mrr_index_reader *index_reader;
/* index_reader->get_next() puts rowid here */
uchar *index_rowid;
/* TRUE <=> index_reader->refill_buffer() call has returned EOF */
bool index_reader_exhausted;
/*
TRUE < = > We should call index_reader - > refill_buffer ( ) . This happens if
1 . we ' ve made index_reader - > get_next ( ) call which returned EOF
2 . we haven ' t made any index_reader calls ( and our first call should
be index_reader - > refill_buffer ( initial = TRUE )
*/
bool index_reader_needs_refill;
/* TRUE <=> need range association, buffers hold {rowid, range_id} pairs */
bool is_mrr_assoc;
/*
When reading from ordered rowid buffer : the rowid element of the last
buffer element that has rowid identical to this one .
*/
uchar *last_identical_rowid;
/* Buffer to store (rowid, range_id) pairs */
Lifo_buffer *rowid_buffer;
/* Rowid filter to be checked against (if any) */
Rowid_filter *rowid_filter;
int refill_from_index_reader();
};
/*
A primitive " factory " of various Mrr_ * _ reader classes ( the point is to
get various kinds of readers without having to allocate them on the heap )
*/
class Mrr_reader_factory
{
public :
Mrr_ordered_rndpos_reader ordered_rndpos_reader;
Mrr_ordered_index_reader ordered_index_reader;
Mrr_simple_index_reader simple_index_reader;
};
#define DSMRR_IMPL_SORT_KEYS HA_MRR_IMPLEMENTATION_FLAG1
#define DSMRR_IMPL_SORT_ROWIDS HA_MRR_IMPLEMENTATION_FLAG2
/*
DS - MRR implementation for one table . Create / use one object of this class for
each ha_ { myisam / innobase / etc } object . That object will be further referred to
as " the handler "
DsMrr_impl supports has the following execution strategies :
- Bypass DS - MRR , pass all calls to default MRR implementation , which is
an MRR - to - non - MRR call converter .
- Key - Ordered Retrieval
- Rowid - Ordered Retrieval
DsMrr_impl will use one of the above strategies , or a combination of them ,
according to the following diagram :
( mrr function calls )
|
+ - - - - - - - - - - - - - - - - - > - - - - - - - - - - - - - - - - - +
| |
_ _ _ _ _ _ _ _ _ _ _ v______________ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ v________________
/ default : use lookup keys \ / KEY - ORDERED RETRIEVAL : \
| ( or ranges ) in whatever | | sort lookup keys and then make |
| order they are supplied | | index lookups in index order |
\ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ / \ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /
| | | | |
+ - - - < - - - + | + - - - - - - - - - - - - - - - > - - - - - - - - - - - | - - - - +
| | | |
| | + - - - - - - - - - - - - - - - + |
| _ _ _ _ _ _ v___ _ _ _ _ _ _ | _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ v_______________
| / default : read \ | / ROWID - ORDERED RETRIEVAL : \
| | table records | | | Before reading table records , |
v | in random order | v | sort their rowids and then |
| \ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ / | | read them in rowid order |
| | | \ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ /
| | | |
| | | |
+ - - > - - - + | + - - - - < - - - - - - + - - - - - - - - - - - < - - - - - - - - +
| | |
v v v
( table records and range_ids )
The choice of strategy depends on MRR scan properties , table properties
( whether we ' re scanning clustered primary key ) , and @ @ optimizer_switch
settings .
Key - Ordered Retrieval
- - - - - - - - - - - - - - - - - - - - -
The idea is : if MRR scan is essentially a series of lookups on
tbl . key = value1 OR tbl . key = value2 OR . . . OR tbl . key = valueN
then it makes sense to collect and order the set of lookup values , i . e .
sort ( value1 , value2 , . . valueN )
and then do index lookups in index order . This results in fewer index page
fetch operations , and we also can avoid making multiple index lookups for the
same value . That is , if value1 = valueN we can easily discover that after
sorting and make one index lookup for them instead of two .
Rowid - Ordered Retrieval
- - - - - - - - - - - - - - - - - - - - - - -
If we do a regular index scan or a series of index lookups , we ' ll be hitting
table records at random . For disk - based engines , this is much slower than
reading the same records in disk order . We assume that disk ordering of
rows is the same as ordering of their rowids ( which is provided by
handler : : cmp_ref ( ) )
In order to retrieve records in different order , we must separate index
scanning and record fetching , that is , MRR scan uses the following steps :
1 . Scan the index ( and only index , that is , with HA_EXTRA_KEYREAD on ) and
fill a buffer with { rowid , range_id } pairs
2 . Sort the buffer by rowid value
3 . for each { rowid , range_id } pair in the buffer
get record by rowid and return the { record , range_id } pair
4 . Repeat the above steps until we ' ve exhausted the list of ranges we ' re
scanning .
Buffer space management considerations
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
With regards to buffer / memory management , MRR interface specifies that
- SQL layer provides multi_range_read_init ( ) with buffer of certain size .
- MRR implementation may use ( i . e . have at its disposal till the end of
the MRR scan ) all of the buffer , or return the unused end of the buffer
to SQL layer .
DS - MRR needs buffer in order to accumulate and sort rowids and / or keys . When
we need to accumulate / sort only keys ( or only rowids ) , it is fairly trivial .
When we need to accumulate / sort both keys and rowids , efficient buffer use
gets complicated . We need to :
- First , accumulate keys and sort them
- Then use the keys ( smaller values go first ) to obtain rowids . A key is not
needed after we ' ve got matching rowids for it .
- Make sure that rowids are accumulated at the front of the buffer , so that we
can return the end part of the buffer to SQL layer , should there be too
few rowid values to occupy the buffer .
All of these goals are achieved by using the following scheme :
| | We get an empty buffer from SQL layer .
| * - |
| * - - - - | First , we fill the buffer with keys . Key_buffer
| * - - - - - - - | part grows from end of the buffer space to start
| * - - - - - - - - - - | ( In this picture , the buffer is big enough to
| * - - - - - - - - - - - - - | accomodate all keys and even have some space left )
| * = = = = = = = = = = = = = | We want to do key - ordered index scan , so we sort
the keys
| - x * = = = = = = = = = = = | Then we use the keys get rowids . Rowids are
| - - - - x * = = = = = = = = | stored from start of buffer space towards the end .
| - - - - - - - - x * = = = = = | The part of the buffer occupied with keys
| - - - - - - - - - - - - x * = = = | gradually frees up space for rowids . In this
| - - - - - - - - - - - - - - x * = | picture we run out of keys before we ' ve ran out
| - - - - - - - - - - - - - - - - x | of buffer space ( it can be other way as well ) .
| = = = = = = = = = = = = = = = = x | Then we sort the rowids .
| | ~ ~ ~ | The unused part of the buffer is at the end , so
we can return it to the SQL layer .
| = = = = = = = = = = = = = = = = * Sorted rowids are then used to read table records
in disk order
*/
class DsMrr_impl
{
public :
typedef void (handler::*range_check_toggle_func_t)(bool on);
void init(handler *h_arg, TABLE *table_arg)
{
primary_file= h_arg;
table= table_arg;
}
int dsmrr_init(handler *h_arg, RANGE_SEQ_IF *seq_funcs,
void *seq_init_param, uint n_ranges, uint mode,
HANDLER_BUFFER *buf);
void dsmrr_close();
int dsmrr_next(range_id_t *range_info);
ha_rows dsmrr_info(uint keyno, uint n_ranges, uint keys, uint key_parts,
uint *bufsz, uint *flags, Cost_estimate *cost);
ha_rows dsmrr_info_const(uint keyno, RANGE_SEQ_IF *seq,
void *seq_init_param, uint n_ranges, uint *bufsz,
uint *flags, ha_rows limit, Cost_estimate *cost);
int dsmrr_explain_info(uint mrr_mode, char *str, size_t size);
private :
/* Buffer to store (key, range_id) pairs */
Lifo_buffer *key_buffer= nullptr;
/*
The " owner " handler object ( the one that is expected to " own " this object
and call its functions ) .
*/
handler *primary_file;
TABLE *table; /* Always equal to primary_file->table */
/*
Secondary handler object . ( created when needed , we need it when we need
to run both index scan and rnd_pos ( ) scan at the same time )
*/
handler *secondary_file= nullptr;
/*
The rowid filter that DS - MRR has " unpushed " from the storage engine .
If it ' s present , DS - MRR will use it .
*/
Rowid_filter *rowid_filter= nullptr;
uint keyno; /* index we're running the scan on */
/* TRUE <=> need range association, buffers hold {rowid, range_id} pairs */
bool is_mrr_assoc;
Mrr_reader_factory reader_factory;
Mrr_reader *strategy;
bool strategy_exhausted;
Mrr_index_reader *index_strategy;
/* The whole buffer space that we're using */
uchar *full_buf;
uchar *full_buf_end;
/*
When using both rowid and key buffers : the boundary between key and rowid
parts of the buffer . This is the " original " value , actual memory ranges
used by key and rowid parts may be different because of dynamic space
reallocation between them .
*/
uchar *rowid_buffer_end;
/*
One of the following two is used for key buffer : forward is used when
we only need key buffer , backward is used when we need both key and rowid
buffers .
*/
Forward_lifo_buffer forward_key_buf;
Backward_lifo_buffer backward_key_buf;
/*
Buffer to store ( rowid , range_id ) pairs , or just rowids if
is_mrr_assoc = = FALSE
*/
Forward_lifo_buffer rowid_buffer;
bool choose_mrr_impl(uint keyno, ha_rows rows, uint *flags, uint *bufsz,
Cost_estimate *cost);
bool get_disk_sweep_mrr_cost(uint keynr, ha_rows rows, uint flags,
uint *buffer_size, uint extra_mem_overhead,
Cost_estimate *cost);
bool check_cpk_scan(THD *thd, TABLE_SHARE *share, uint keyno, uint mrr_flags);
bool setup_buffer_sharing(uint key_size_in_keybuf, key_part_map key_tuple_map);
/* Buffer_manager and its member functions */
Buffer_manager buf_manager;
static void redistribute_buffer_space(void *dsmrr_arg);
static void reset_buffer_sizes(void *dsmrr_arg);
static void do_nothing(void *dsmrr_arg);
Lifo_buffer* get_key_buffer() { return key_buffer; }
friend class Key_value_records_iterator;
friend class Mrr_ordered_index_reader;
friend class Mrr_ordered_rndpos_reader;
int setup_two_handlers();
void close_second_handler();
};
/**
@ } ( end of group DS - MRR declarations )
*/
Messung V0.5 in Prozent C=91 H=100 G=95
¤ Dauer der Verarbeitung: 0.21 Sekunden
(vorverarbeitet am 2026-10-08)
¤
*© Formatika GbR, Deutschland
2026-10-09
Neuigkeiten
Aktuelles
Motto des Tages
Open Source Software
Jenseits des Üblichen ....
Besucherstatistik
Statistik
#Sources=1126438
#Domains=1867298