Browse Source

Remove row_prebuilt_t::fetch_cache[]

bb-10.11-MDEV-16232
Marko Mäkelä 2 years ago
parent
commit
6eaa34e664
  1. 21
      storage/innobase/include/row0mysql.h
  2. 21
      storage/innobase/row/row0mysql.cc
  3. 341
      storage/innobase/row/row0sel.cc

21
storage/innobase/include/row0mysql.h

@ -439,10 +439,6 @@ struct mysql_row_templ_t {
ulint is_virtual; /*!< if a column is a virtual column */
};
#define MYSQL_FETCH_CACHE_SIZE 8
/* After fetching this many rows, we start caching them in fetch_cache */
#define MYSQL_FETCH_CACHE_THRESHOLD 4
#define ROW_PREBUILT_ALLOCATED 78540783
#define ROW_PREBUILT_FREED 26423527
@ -719,27 +715,10 @@ struct row_prebuilt_t {
the last requested column */
ulint mysql_row_len; /*!< length in bytes of a row in the
MySQL format */
ulint n_rows_fetched; /*!< number of rows fetched after
positioning the current cursor */
ulint fetch_direction;/*!< ROW_SEL_NEXT or ROW_SEL_PREV */
byte* fetch_cache[MYSQL_FETCH_CACHE_SIZE];
/*!< a cache for fetched rows if we
fetch many rows from the same cursor:
it saves CPU time to fetch them in a
batch; we reserve mysql_row_len
bytes for each such row; these
pointers point 4 bytes past the
allocated mem buf start, because
there is a 4 byte magic number at the
start and at the end */
bool keep_other_fields_on_keyread; /*!< when using fetch
cache with HA_EXTRA_KEYREAD, don't
overwrite other fields in mysql row
row buffer.*/
ulint fetch_cache_first;/*!< position of the first not yet
fetched row in fetch_cache */
ulint n_fetch_cached; /*!< number of not yet fetched rows
in fetch_cache */
mem_heap_t* blob_heap; /*!< in SELECTS BLOB fields are copied
to this heap */
mem_heap_t* old_vers_heap; /*!< memory heap where a previous

21
storage/innobase/row/row0mysql.cc

@ -948,27 +948,6 @@ void row_prebuilt_free(row_prebuilt_t *prebuilt)
mem_heap_free(prebuilt->old_vers_heap);
}
if (prebuilt->fetch_cache[0] != NULL) {
byte* base = prebuilt->fetch_cache[0] - 4;
byte* ptr = base;
for (ulint i = 0; i < MYSQL_FETCH_CACHE_SIZE; i++) {
ulint magic1 = mach_read_from_4(ptr);
ut_a(magic1 == ROW_PREBUILT_FETCH_MAGIC_N);
ptr += 4;
byte* row = ptr;
ut_a(row == prebuilt->fetch_cache[i]);
ptr += prebuilt->mysql_row_len;
ulint magic2 = mach_read_from_4(ptr);
ut_a(magic2 == ROW_PREBUILT_FETCH_MAGIC_N);
ptr += 4;
}
ut_free(base);
}
if (prebuilt->rtr_info) {
rtr_clean_rtr_info(prebuilt->rtr_info, true);
}

341
storage/innobase/row/row0sel.cc

@ -3822,142 +3822,6 @@ row_sel_copy_cached_fields_for_mysql(
}
}
/********************************************************************//**
Pops a cached row for MySQL from the fetch cache. */
UNIV_INLINE
void
row_sel_dequeue_cached_row_for_mysql(
/*=================================*/
byte* buf, /*!< in/out: buffer where to copy the
row */
row_prebuilt_t* prebuilt) /*!< in: prebuilt struct */
{
ulint i;
const mysql_row_templ_t*templ;
const byte* cached_rec;
ut_ad(prebuilt->n_fetch_cached > 0);
ut_ad(prebuilt->mysql_prefix_len <= prebuilt->mysql_row_len);
MEM_CHECK_ADDRESSABLE(buf, prebuilt->mysql_row_len);
cached_rec = prebuilt->fetch_cache[prebuilt->fetch_cache_first];
if (UNIV_UNLIKELY(prebuilt->keep_other_fields_on_keyread)) {
row_sel_copy_cached_fields_for_mysql(buf, cached_rec, prebuilt);
} else if (prebuilt->mysql_prefix_len > 63) {
/* The record is long. Copy it field by field, in case
there are some long VARCHAR column of which only a
small length is being used. */
MEM_UNDEFINED(buf, prebuilt->mysql_prefix_len);
/* First copy the NULL bits. */
memcpy(buf, cached_rec, prebuilt->null_bitmap_len);
/* Then copy the requested fields. */
for (i = 0; i < prebuilt->n_template; i++) {
templ = prebuilt->mysql_template + i;
/* Skip virtual columns */
if (templ->is_virtual
&& !(dict_index_has_virtual(prebuilt->index)
&& prebuilt->read_just_key)) {
continue;
}
row_sel_copy_cached_field_for_mysql(
buf, cached_rec, templ);
}
} else {
memcpy(buf, cached_rec, prebuilt->mysql_prefix_len);
}
prebuilt->n_fetch_cached--;
prebuilt->fetch_cache_first++;
if (prebuilt->n_fetch_cached == 0) {
prebuilt->fetch_cache_first = 0;
}
}
/********************************************************************//**
Initialise the prefetch cache. */
UNIV_INLINE
void
row_sel_prefetch_cache_init(
/*========================*/
row_prebuilt_t* prebuilt) /*!< in/out: prebuilt struct */
{
ulint i;
ulint sz;
byte* ptr;
/* Reserve space for the magic number. */
sz = UT_ARR_SIZE(prebuilt->fetch_cache) * (prebuilt->mysql_row_len + 8);
ptr = static_cast<byte*>(ut_malloc_nokey(sz));
for (i = 0; i < UT_ARR_SIZE(prebuilt->fetch_cache); i++) {
/* A user has reported memory corruption in these
buffers in Linux. Put magic numbers there to help
to track a possible bug. */
mach_write_to_4(ptr, ROW_PREBUILT_FETCH_MAGIC_N);
ptr += 4;
prebuilt->fetch_cache[i] = ptr;
ptr += prebuilt->mysql_row_len;
mach_write_to_4(ptr, ROW_PREBUILT_FETCH_MAGIC_N);
ptr += 4;
}
}
/********************************************************************//**
Get the last fetch cache buffer from the queue.
@return pointer to buffer. */
UNIV_INLINE
byte*
row_sel_fetch_last_buf(
/*===================*/
row_prebuilt_t* prebuilt) /*!< in/out: prebuilt struct */
{
ut_ad(!prebuilt->templ_contains_blob);
ut_ad(prebuilt->n_fetch_cached < MYSQL_FETCH_CACHE_SIZE);
if (prebuilt->fetch_cache[0] == NULL) {
/* Allocate memory for the fetch cache */
ut_ad(prebuilt->n_fetch_cached == 0);
row_sel_prefetch_cache_init(prebuilt);
}
ut_ad(prebuilt->fetch_cache_first == 0);
MEM_UNDEFINED(prebuilt->fetch_cache[prebuilt->n_fetch_cached],
prebuilt->mysql_row_len);
return(prebuilt->fetch_cache[prebuilt->n_fetch_cached]);
}
/********************************************************************//**
Pushes a row for MySQL to the fetch cache. */
UNIV_INLINE
void
row_sel_enqueue_cache_row_for_mysql(
/*================================*/
byte* mysql_rec, /*!< in/out: MySQL record */
row_prebuilt_t* prebuilt) /*!< in/out: prebuilt struct */
{
/* For non ICP code path the row should already exist in the
next fetch cache slot. */
if (prebuilt->pk_filter || prebuilt->idx_cond) {
memcpy(row_sel_fetch_last_buf(prebuilt), mysql_rec,
prebuilt->mysql_row_len);
}
++prebuilt->n_fetch_cached;
}
#ifdef BTR_CUR_HASH_ADAPT
/*********************************************************************//**
Tries to do a shortcut to fetch a clustered index record with a unique key,
@ -4385,7 +4249,6 @@ row_search_mvcc(
ulint next_offs;
bool same_user_rec;
ibool table_lock_waited = FALSE;
byte* next_buf = 0;
bool spatial_search = false;
btr_latch_mode batch_latch_mode = BTR_SEARCH_LEAF;
@ -4437,59 +4300,12 @@ row_search_mvcc(
if (UNIV_UNLIKELY(direction == 0)) {
trx->op_info = "starting index read";
prebuilt->n_rows_fetched = 0;
prebuilt->n_fetch_cached = 0;
prebuilt->fetch_cache_first = 0;
if (prebuilt->sel_graph == NULL) {
/* Build a dummy select query graph */
row_prebuild_sel_graph(prebuilt);
}
} else {
trx->op_info = "fetching rows";
if (prebuilt->n_rows_fetched == 0) {
prebuilt->fetch_direction = direction;
}
if (UNIV_UNLIKELY(direction != prebuilt->fetch_direction)) {
if (UNIV_UNLIKELY(prebuilt->n_fetch_cached > 0)) {
ut_error;
/* TODO: scrollable cursor: restore cursor to
the place of the latest returned row,
or better: prevent caching for a scroll
cursor! */
}
prebuilt->n_rows_fetched = 0;
prebuilt->n_fetch_cached = 0;
prebuilt->fetch_cache_first = 0;
} else if (UNIV_LIKELY(prebuilt->n_fetch_cached > 0)) {
row_sel_dequeue_cached_row_for_mysql(buf, prebuilt);
prebuilt->n_rows_fetched++;
trx->op_info = "";
DBUG_RETURN(DB_SUCCESS);
}
if (prebuilt->fetch_cache_first > 0
&& prebuilt->fetch_cache_first < MYSQL_FETCH_CACHE_SIZE) {
early_not_found:
/* The previous returned row was popped from the fetch
cache, but the cache was not full at the time of the
popping: no more rows can exist in the result set */
trx->op_info = "";
DBUG_RETURN(DB_RECORD_NOT_FOUND);
}
prebuilt->n_rows_fetched++;
if (prebuilt->n_rows_fetched > 1000000000) {
/* Prevent wrap-over */
prebuilt->n_rows_fetched = 500000000;
}
mode = pcur->search_mode;
}
@ -4524,7 +4340,8 @@ early_not_found:
if (UNIV_UNLIKELY(direction != 0
&& !prebuilt->used_in_HANDLER)) {
goto early_not_found;
trx->op_info = "";
DBUG_RETURN(DB_RECORD_NOT_FOUND);
}
}
@ -5649,116 +5466,35 @@ use_covering_index:
offsets));
ut_ad(!rec_get_deleted_flag(result_rec, comp));
/* Decide whether to prefetch extra rows.
At this point, the clustered index record is protected
by a page latch that was acquired when pcur was positioned.
The latch will not be released until mtr.commit(). */
if ((match_mode == ROW_SEL_EXACT
|| prebuilt->n_rows_fetched >= MYSQL_FETCH_CACHE_THRESHOLD)
&& prebuilt->select_lock_type == LOCK_NONE
&& !prebuilt->templ_contains_blob
&& !prebuilt->clust_index_was_generated
&& !prebuilt->used_in_HANDLER
&& !prebuilt->in_fts_query) {
/* Inside an update, for example, we do not cache rows,
since we may use the cursor position to do the actual
update, that is why we require ...lock_type == LOCK_NONE.
Since we keep space in prebuilt only for the BLOBs of
a single row, we cannot cache rows in the case there
are BLOBs in the fields to be fetched. In HANDLER we do
not cache rows because there the cursor is a scrollable
cursor. */
ut_a(prebuilt->n_fetch_cached < MYSQL_FETCH_CACHE_SIZE);
/* We only convert from InnoDB row format to MySQL row
format when ICP is disabled. */
if (!prebuilt->pk_filter && !prebuilt->idx_cond) {
/* We use next_buf to track the allocation of buffers
where we store and enqueue the buffers for our
pre-fetch optimisation.
If next_buf == 0 then we store the converted record
directly into the MySQL record buffer (buf). If it is
!= 0 then we allocate a pre-fetch buffer and store the
converted record there.
If the conversion fails and the MySQL record buffer
was not written to then we reset next_buf so that
we can re-use the MySQL record buffer in the next
iteration. */
next_buf = next_buf
? row_sel_fetch_last_buf(prebuilt) : buf;
if (!row_sel_store_mysql_rec(
next_buf, prebuilt, result_rec, vrow,
result_rec != rec,
result_rec != rec ? clust_index : index,
offsets)) {
if (next_buf == buf) {
ut_a(prebuilt->n_fetch_cached == 0);
next_buf = 0;
}
/* Only fresh inserts may contain incomplete
externally stored columns. Pretend that such
records do not exist. Such records may only be
accessed at the READ UNCOMMITTED isolation
level or when rolling back a recovered
transaction. Rollback happens at a lower
level, not here. */
goto next_rec;
}
if (next_buf != buf) {
row_sel_enqueue_cache_row_for_mysql(
next_buf, prebuilt);
}
} else {
row_sel_enqueue_cache_row_for_mysql(buf, prebuilt);
}
if (prebuilt->n_fetch_cached < MYSQL_FETCH_CACHE_SIZE) {
if (!prebuilt->pk_filter && !prebuilt->idx_cond) {
/* The record was not yet converted to MySQL format. */
if (!row_sel_store_mysql_rec(
buf, prebuilt, result_rec, vrow,
result_rec != rec,
result_rec != rec ? clust_index : index,
offsets)) {
/* Only fresh inserts may contain incomplete
externally stored columns. Pretend that such
records do not exist. Such records may only be
accessed at the READ UNCOMMITTED isolation
level or when rolling back a recovered
transaction. Rollback happens at a lower
level, not here. */
goto next_rec;
}
} else {
if (!prebuilt->pk_filter && !prebuilt->idx_cond) {
/* The record was not yet converted to MySQL format. */
if (!row_sel_store_mysql_rec(
buf, prebuilt, result_rec, vrow,
result_rec != rec,
result_rec != rec ? clust_index : index,
offsets)) {
/* Only fresh inserts may contain
incomplete externally stored
columns. Pretend that such records do
not exist. Such records may only be
accessed at the READ UNCOMMITTED
isolation level or when rolling back a
recovered transaction. Rollback
happens at a lower level, not here. */
goto next_rec;
}
}
}
if (!prebuilt->clust_index_was_generated) {
} else if (result_rec != rec || index->is_primary()) {
memcpy(prebuilt->row_id, result_rec, DATA_ROW_ID_LEN);
} else {
ulint len;
const byte* data = rec_get_nth_field(
result_rec, offsets, index->n_fields - 1,
&len);
ut_ad(dict_index_get_nth_col(index,
index->n_fields - 1)
->prtype == (DATA_ROW_ID | DATA_NOT_NULL));
ut_ad(len == DATA_ROW_ID_LEN);
memcpy(prebuilt->row_id, data, DATA_ROW_ID_LEN);
}
if (!prebuilt->clust_index_was_generated) {
} else if (result_rec != rec || index->is_primary()) {
memcpy(prebuilt->row_id, result_rec, DATA_ROW_ID_LEN);
} else {
ulint len;
const byte* data = rec_get_nth_field(
result_rec, offsets, index->n_fields - 1, &len);
ut_ad(dict_index_get_nth_col(index, index->n_fields - 1)
->prtype == (DATA_ROW_ID | DATA_NOT_NULL));
ut_ad(len == DATA_ROW_ID_LEN);
memcpy(prebuilt->row_id, data, DATA_ROW_ID_LEN);
}
/* From this point on, 'offsets' are invalid. */
@ -5961,27 +5697,6 @@ normal_return:
DEBUG_SYNC_C("row_search_for_mysql_before_return");
if (prebuilt->pk_filter || prebuilt->idx_cond) {
/* When ICP is active we don't write to the MySQL buffer
directly, only to buffers that are enqueued in the pre-fetch
queue. We need to dequeue the first buffer and copy the contents
to the record buffer that was passed in by MySQL. */
if (prebuilt->n_fetch_cached > 0) {
row_sel_dequeue_cached_row_for_mysql(buf, prebuilt);
err = DB_SUCCESS;
}
} else if (next_buf != 0) {
/* We may or may not have enqueued some buffers to the
pre-fetch queue, but we definitely wrote to the record
buffer passed to use by MySQL. */
DEBUG_SYNC_C("row_search_cached_row");
err = DB_SUCCESS;
}
#ifdef UNIV_DEBUG
if (dict_index_is_spatial(index) && err != DB_SUCCESS
&& err != DB_END_OF_INDEX && err != DB_INTERRUPTED) {

Loading…
Cancel
Save