MDEV-12255 innodb_prefix_index_cluster_optimization hits debug build

		assert on UTF-8 columns

Problem:
=======
(1) Multi-byte character cases are not considered during prefix index
cluster optimization check. It leads to fetch of improper results during
read operation.
(2) Strict assert in row_sel_field_store_in_mysql_format_func and it asserts
for prefix index record to mysql conversion.

Solution:
========
(1) Consider the case of multi-byte character during prefix index
cluster optimization check.
(2) Relax the assert in row_sel_field_store_in_mysql_format_func to allow
prefix index record to mysql format conversion.

The patch is taken from
https://github.com/laurynas-biveinis/percona-server/commit/1eee538087ffcf121c37f844b447ba5480faf081
parent bc2e7d78
......@@ -2707,7 +2707,9 @@ row_sel_field_store_in_mysql_format_func(
|| !(templ->mysql_col_len % templ->mbmaxlen));
ut_ad(len * templ->mbmaxlen >= templ->mysql_col_len
|| (field_no == templ->icp_rec_field_no
&& field->prefix_len > 0));
&& field->prefix_len > 0)
|| templ->rec_field_is_prefix);
ut_ad(!(field->prefix_len % templ->mbmaxlen));
if (templ->mbminlen == 1 && templ->mbmaxlen != 1) {
......@@ -3667,6 +3669,118 @@ row_search_idx_cond_check(
return(result);
}
/** Return the record field length in characters.
@param[in] col table column of the field
@param[in] field_no field number
@param[in] rec physical record
@param[in] offsets field offsets in the physical record
@return field length in characters. */
static
size_t
rec_field_len_in_chars(
const dict_col_t* col,
const ulint field_no,
const rec_t* rec,
const ulint* offsets)
{
const ulint cset = dtype_get_charset_coll(col->prtype);
const CHARSET_INFO* cs = all_charsets[cset];
ulint rec_field_len;
const char* rec_field = reinterpret_cast<const char *>(
rec_get_nth_field(
rec, offsets, field_no, &rec_field_len));
if (UNIV_UNLIKELY(!cs)) {
ib_logf(IB_LOG_LEVEL_WARN, "Missing collation " ULINTPF, cset);
return SIZE_T_MAX;
}
return(cs->cset->numchars(cs, rec_field, rec_field + rec_field_len));
}
/** Avoid the clustered index lookup if all the following conditions
are true:
1) all columns are in secondary index
2) all values for columns that are prefix-only indexes are shorter
than the prefix size. This optimization can avoid many IOs for certain schemas.
@return true, to avoid clustered index lookup. */
static
bool row_search_with_covering_prefix(
row_prebuilt_t* prebuilt,
const rec_t* rec,
const ulint* offsets)
{
const dict_index_t* index = prebuilt->index;
ut_ad(!dict_index_is_clust(index));
if (!srv_prefix_index_cluster_optimization) {
return false;
}
/** Optimization only applicable if there the number of secondary index
fields are greater than or equal to number of clustered index fields. */
if (prebuilt->n_template > index->n_fields) {
return false;
}
for (ulint i = 0; i < prebuilt->n_template; i++) {
mysql_row_templ_t* templ = prebuilt->mysql_template + i;
ulint j = templ->rec_prefix_field_no;
/** Condition (1) : is the field in the index. */
if (j == ULINT_UNDEFINED) {
return false;
}
/** Condition (2): If this is a prefix index then
row's value size shorter than prefix length. */
if (!templ->rec_field_is_prefix) {
continue;
}
ulint rec_size = rec_offs_nth_size(offsets, j);
const dict_field_t* field = dict_index_get_nth_field(index, j);
ulint max_chars = field->prefix_len / templ->mbmaxlen;
ut_a(field->prefix_len > 0);
if (rec_size < max_chars) {
/* Record in bytes shorter than the index
prefix length in char. */
continue;
}
if (rec_size * templ->mbminlen >= field->prefix_len) {
/* Shortest representation string by the
byte length of the record is longer than the
maximum possible index prefix. */
return false;
}
size_t num_chars = rec_field_len_in_chars(
field->col, j, rec, offsets);
if (num_chars >= max_chars) {
/* No of chars to store the record exceeds
the index prefix character length. */
return false;
}
}
/* If prefix index optimization condition satisfied then
for all columns above, use rec_prefix_field_no instead of
rec_field_no, and skip the clustered lookup below. */
for (ulint i = 0; i < prebuilt->n_template; i++) {
mysql_row_templ_t* templ = prebuilt->mysql_template + i;
templ->rec_field_no = templ->rec_prefix_field_no;
ut_a(templ->rec_field_no != ULINT_UNDEFINED);
}
srv_stats.n_sec_rec_cluster_reads_avoided.inc();
return true;
}
/********************************************************************//**
Searches for rows in the database. This is used in the interface to
MySQL. This function opens a cursor, and also implements fetch next
......@@ -3729,7 +3843,6 @@ row_search_for_mysql(
ulint* offsets = offsets_;
ibool table_lock_waited = FALSE;
byte* next_buf = 0;
ibool use_clustered_index = FALSE;
rec_offs_init(offsets_);
......@@ -4790,69 +4903,10 @@ row_search_for_mysql(
break;
}
/* Get the clustered index record if needed, if we did not do the
search using the clustered index... */
use_clustered_index =
(index != clust_index && prebuilt->need_to_access_clustered);
if (use_clustered_index && srv_prefix_index_cluster_optimization
&& prebuilt->n_template <= index->n_fields) {
/* ...but, perhaps avoid the clustered index lookup if
all of the following are true:
1) all columns are in the secondary index
2) all values for columns that are prefix-only
indexes are shorter than the prefix size
This optimization can avoid many IOs for certain schemas.
*/
ibool row_contains_all_values = TRUE;
int i;
for (i = 0; i < prebuilt->n_template; i++) {
/* Condition (1) from above: is the field in the
index (prefix or not)? */
mysql_row_templ_t* templ =
prebuilt->mysql_template + i;
ulint secondary_index_field_no =
templ->rec_prefix_field_no;
if (secondary_index_field_no == ULINT_UNDEFINED) {
row_contains_all_values = FALSE;
break;
}
/* Condition (2) from above: if this is a
prefix, is this row's value size shorter
than the prefix? */
if (templ->rec_field_is_prefix) {
ulint record_size = rec_offs_nth_size(
offsets,
secondary_index_field_no);
const dict_field_t *field =
dict_index_get_nth_field(
index,
secondary_index_field_no);
ut_a(field->prefix_len > 0);
if (record_size >= field->prefix_len) {
row_contains_all_values = FALSE;
break;
}
}
}
/* If (1) and (2) were true for all columns above, use
rec_prefix_field_no instead of rec_field_no, and skip
the clustered lookup below. */
if (row_contains_all_values) {
for (i = 0; i < prebuilt->n_template; i++) {
mysql_row_templ_t* templ =
prebuilt->mysql_template + i;
templ->rec_field_no =
templ->rec_prefix_field_no;
ut_a(templ->rec_field_no != ULINT_UNDEFINED);
}
use_clustered_index = FALSE;
srv_stats.n_sec_rec_cluster_reads_avoided.inc();
if (index != clust_index && prebuilt->need_to_access_clustered) {
if (row_search_with_covering_prefix(prebuilt, rec, offsets)) {
goto use_covering_index;
}
}
if (use_clustered_index) {
requires_clust_rec:
ut_ad(index != clust_index);
/* We use a 'goto' to the preceding label if a consistent
......@@ -4938,6 +4992,7 @@ row_search_for_mysql(
}
}
} else {
use_covering_index:
result_rec = rec;
}
......
......@@ -3685,6 +3685,117 @@ row_search_idx_cond_check(
return(result);
}
/** Return the record field length in characters.
@param[in] col table column of the field
@param[in] field_no field number
@param[in] rec physical record
@param[in] offsets field offsets in the physical record
@return field length in characters. */
static
size_t
rec_field_len_in_chars(
const dict_col_t* col,
const ulint field_no,
const rec_t* rec,
const ulint* offsets)
{
const ulint cset = dtype_get_charset_coll(col->prtype);
const CHARSET_INFO* cs = all_charsets[cset];
ulint rec_field_len;
const char* rec_field = reinterpret_cast<const char *>(
rec_get_nth_field(
rec, offsets, field_no, &rec_field_len));
if (UNIV_UNLIKELY(!cs)) {
ib_logf(IB_LOG_LEVEL_WARN, "Missing collation " ULINTPF, cset);
return SIZE_T_MAX;
}
return(cs->cset->numchars(cs, rec_field, rec_field + rec_field_len));
}
/** Avoid the clustered index lookup if all the following conditions
are true:
1) all columns are in secondary index
2) all values for columns that are prefix-only indexes are shorter
than the prefix size. This optimization can avoid many IOs for certain schemas.
@return true, to avoid clustered index lookup. */
static
bool row_search_with_covering_prefix(
row_prebuilt_t* prebuilt,
const rec_t* rec,
const ulint* offsets)
{
const dict_index_t* index = prebuilt->index;
ut_ad(!dict_index_is_clust(index));
if (!srv_prefix_index_cluster_optimization) {
return false;
}
/** Optimization only applicable if the number of secondary index
fields are greater than or equal to number of clustered index fields. */
if (prebuilt->n_template > index->n_fields) {
return false;
}
for (ulint i = 0; i < prebuilt->n_template; i++) {
mysql_row_templ_t* templ = prebuilt->mysql_template + i;
ulint j = templ->rec_prefix_field_no;
/** Condition (1) : is the field in the index. */
if (j == ULINT_UNDEFINED) {
return false;
}
/** Condition (2): If this is a prefix index then
row's value size shorter than prefix length. */
if (!templ->rec_field_is_prefix) {
continue;
}
ulint rec_size = rec_offs_nth_size(offsets, j);
const dict_field_t* field = dict_index_get_nth_field(index, j);
ulint max_chars = field->prefix_len / templ->mbmaxlen;
ut_a(field->prefix_len > 0);
if (rec_size < max_chars) {
/* Record in bytes shorter than the index
prefix length in char. */
continue;
}
if (rec_size * templ->mbminlen >= field->prefix_len) {
/* Shortest representation string by the
byte length of the record is longer than the
maximum possible index prefix. */
return false;
}
size_t num_chars = rec_field_len_in_chars(
field->col, j, rec, offsets);
if (num_chars >= max_chars) {
/* No of chars to store the record exceeds
the index prefix character length. */
return false;
}
}
for (ulint i = 0; i < prebuilt->n_template; i++) {
mysql_row_templ_t* templ = prebuilt->mysql_template + i;
templ->rec_field_no = templ->rec_prefix_field_no;
ut_a(templ->rec_field_no != ULINT_UNDEFINED);
}
srv_stats.n_sec_rec_cluster_reads_avoided.inc();
return true;
}
/********************************************************************//**
Searches for rows in the database. This is used in the interface to
MySQL. This function opens a cursor, and also implements fetch next
......@@ -3748,7 +3859,6 @@ row_search_for_mysql(
ulint* offsets = offsets_;
ibool table_lock_waited = FALSE;
byte* next_buf = 0;
bool use_clustered_index = false;
rec_offs_init(offsets_);
......@@ -4810,71 +4920,10 @@ row_search_for_mysql(
break;
}
/* Get the clustered index record if needed, if we did not do the
search using the clustered index... */
use_clustered_index =
(index != clust_index && prebuilt->need_to_access_clustered);
if (use_clustered_index && srv_prefix_index_cluster_optimization
&& prebuilt->n_template <= index->n_fields) {
/* ...but, perhaps avoid the clustered index lookup if
all of the following are true:
1) all columns are in the secondary index
2) all values for columns that are prefix-only
indexes are shorter than the prefix size
This optimization can avoid many IOs for certain schemas.
*/
bool row_contains_all_values = true;
unsigned int i;
for (i = 0; i < prebuilt->n_template; i++) {
/* Condition (1) from above: is the field in the
index (prefix or not)? */
const mysql_row_templ_t* templ =
prebuilt->mysql_template + i;
ulint secondary_index_field_no =
templ->rec_prefix_field_no;
if (secondary_index_field_no == ULINT_UNDEFINED) {
row_contains_all_values = false;
break;
}
/* Condition (2) from above: if this is a
prefix, is this row's value size shorter
than the prefix? */
if (templ->rec_field_is_prefix) {
ulint record_size = rec_offs_nth_size(
offsets,
secondary_index_field_no);
const dict_field_t *field =
dict_index_get_nth_field(
index,
secondary_index_field_no);
ut_a(field->prefix_len > 0);
if (record_size >= field->prefix_len
/ templ->mbmaxlen) {
row_contains_all_values = false;
break;
}
}
}
/* If (1) and (2) were true for all columns above, use
rec_prefix_field_no instead of rec_field_no, and skip
the clustered lookup below. */
if (row_contains_all_values) {
for (i = 0; i < prebuilt->n_template; i++) {
mysql_row_templ_t* templ =
prebuilt->mysql_template + i;
templ->rec_field_no =
templ->rec_prefix_field_no;
ut_a(templ->rec_field_no != ULINT_UNDEFINED);
}
use_clustered_index = false;
srv_stats.n_sec_rec_cluster_reads_avoided.inc();
if (index != clust_index && prebuilt->need_to_access_clustered) {
if (row_search_with_covering_prefix(prebuilt, rec, offsets)) {
goto use_covering_index;
}
}
if (use_clustered_index) {
requires_clust_rec:
ut_ad(index != clust_index);
/* We use a 'goto' to the preceding label if a consistent
......@@ -4960,6 +5009,7 @@ row_search_for_mysql(
}
}
} else {
use_covering_index:
result_rec = rec;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment