Commit 2317da0d authored by Marko Mäkelä's avatar Marko Mäkelä

Bug#58912 InnoDB unnecessarily avoids update-in-place on column prefix indexes

row_upd_changes_ord_field_binary(): Do not return TRUE if the update
vector changes a column that is covered by a prefix index, but does
not change the column prefix. Add the row_ext_t parameter for
determining whether the prefixes of externally stored columns match.

dfield_datas_are_binary_equal(): Add the parameter len, for comparing
column prefixes when len > 0.

innodb.test: Add a test case where the patch of Bug #55284 failed
without this fix.

rb:537 approved by Jimmy Yang
parent 66c518dc
drop table if exists t1,t2,t3,t4; drop table if exists t1,t2,t3,t4;
drop database if exists mysqltest; drop database if exists mysqltest;
CREATE TABLE bug58912 (a BLOB, b TEXT, PRIMARY KEY(a(1))) ENGINE=InnoDB;
INSERT INTO bug58912 VALUES(REPEAT('a',8000),REPEAT('b',8000));
UPDATE bug58912 SET a=REPEAT('a',7999);
create table t1 (id int unsigned not null auto_increment, code tinyint unsigned not null, name char(20) not null, primary key (id), key (code), unique (name)) engine=innodb; create table t1 (id int unsigned not null auto_increment, code tinyint unsigned not null, name char(20) not null, primary key (id), key (code), unique (name)) engine=innodb;
insert into t1 (code, name) values (1, 'Tim'), (1, 'Monty'), (2, 'David'), (2, 'Erik'), (3, 'Sasha'), (3, 'Jeremy'), (4, 'Matt'); insert into t1 (code, name) values (1, 'Tim'), (1, 'Monty'), (2, 'David'), (2, 'Erik'), (3, 'Sasha'), (3, 'Jeremy'), (4, 'Matt');
select id, code, name from t1 order by id; select id, code, name from t1 order by id;
...@@ -1676,10 +1679,10 @@ variable_value - @innodb_rows_deleted_orig ...@@ -1676,10 +1679,10 @@ variable_value - @innodb_rows_deleted_orig
71 71
SELECT variable_value - @innodb_rows_inserted_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_inserted'; SELECT variable_value - @innodb_rows_inserted_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_inserted';
variable_value - @innodb_rows_inserted_orig variable_value - @innodb_rows_inserted_orig
1066 1067
SELECT variable_value - @innodb_rows_updated_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_updated'; SELECT variable_value - @innodb_rows_updated_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_rows_updated';
variable_value - @innodb_rows_updated_orig variable_value - @innodb_rows_updated_orig
865 866
SELECT variable_value - @innodb_row_lock_waits_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_row_lock_waits'; SELECT variable_value - @innodb_row_lock_waits_orig FROM information_schema.global_status WHERE LOWER(variable_name) = 'innodb_row_lock_waits';
variable_value - @innodb_row_lock_waits_orig variable_value - @innodb_row_lock_waits_orig
0 0
...@@ -3239,3 +3242,4 @@ Variable_name Value ...@@ -3239,3 +3242,4 @@ Variable_name Value
Handler_update 1 Handler_update 1
Variable_name Value Variable_name Value
Handler_delete 1 Handler_delete 1
DROP TABLE bug58912;
...@@ -43,6 +43,15 @@ drop table if exists t1,t2,t3,t4; ...@@ -43,6 +43,15 @@ drop table if exists t1,t2,t3,t4;
drop database if exists mysqltest; drop database if exists mysqltest;
--enable_warnings --enable_warnings
# Bug#58912 InnoDB unnecessarily avoids update-in-place on column prefixes
CREATE TABLE bug58912 (a BLOB, b TEXT, PRIMARY KEY(a(1))) ENGINE=InnoDB;
INSERT INTO bug58912 VALUES(REPEAT('a',8000),REPEAT('b',8000));
UPDATE bug58912 SET a=REPEAT('a',7999);
# The above statements used to trigger a failure during purge when
# Bug#55284 was fixed while Bug#58912 was not. Defer the DROP TABLE,
# so that purge gets a chance to run (and a double free of the
# off-page column can be detected, if one is to occur.)
# #
# Small basic test with ignore # Small basic test with ignore
# #
...@@ -2541,6 +2550,9 @@ SET GLOBAL innodb_thread_concurrency = @innodb_thread_concurrency_orig; ...@@ -2541,6 +2550,9 @@ SET GLOBAL innodb_thread_concurrency = @innodb_thread_concurrency_orig;
-- enable_query_log -- enable_query_log
# Clean up after the Bug#55284/Bug#58912 test case.
DROP TABLE bug58912;
####################################################################### #######################################################################
# # # #
# Please, DO NOT TOUCH this file as well as the innodb.result file. # # Please, DO NOT TOUCH this file as well as the innodb.result file. #
......
2010-12-21 The InnoDB Team
* include/data0data.h, include/data0data.ic, include/row0upd.h,
btr/btr0cur.c, row/row0purge.c, row/row0umod.c, row/row0upd.c,
innodb.result, innodb.test:
Fix Bug#58912 InnoDB unnecessarily avoids update-in-place
on column prefix indexes
2010-12-09 The InnoDB Team 2010-12-09 The InnoDB Team
* buf/buf0lru.c: * buf/buf0lru.c:
Fix Bug#57600 output of I/O sum[%lu] can go negative Fix Bug#57600 output of I/O sum[%lu] can go negative
2010-11-11 The InnoDB Team 2010-11-11 The InnoDB Team
* thr/thr0loc.c, trx/trx0i_s.c: * thr/thr0loc.c, trx/trx0i_s.c:
Fix Bug#57802 Empty ASSERTION parameter passed to the HASH_SEARCH macro Fix Bug#57802 Empty ASSERTION parameter passed to the HASH_SEARCH macro
......
...@@ -1756,7 +1756,8 @@ btr_cur_update_in_place( ...@@ -1756,7 +1756,8 @@ btr_cur_update_in_place(
NOT call it if index is secondary */ NOT call it if index is secondary */
if (!dict_index_is_clust(index) if (!dict_index_is_clust(index)
|| row_upd_changes_ord_field_binary(NULL, index, update)) { || row_upd_changes_ord_field_binary(NULL, NULL,
index, update)) {
/* Remove possible hash index pointer to this record */ /* Remove possible hash index pointer to this record */
btr_search_update_hash_on_delete(cursor); btr_search_update_hash_on_delete(cursor);
......
...@@ -154,14 +154,19 @@ dfield_dup( ...@@ -154,14 +154,19 @@ dfield_dup(
dfield_t* field, /*!< in/out: data field */ dfield_t* field, /*!< in/out: data field */
mem_heap_t* heap); /*!< in: memory heap where allocated */ mem_heap_t* heap); /*!< in: memory heap where allocated */
/*********************************************************************//** /*********************************************************************//**
Tests if data length and content is equal for two dfields. Tests if two data fields are equal.
@return TRUE if equal */ If len==0, tests the data length and content for equality.
If len>0, tests the first len bytes of the content for equality.
@return TRUE if both fields are NULL or if they are equal */
UNIV_INLINE UNIV_INLINE
ibool ibool
dfield_datas_are_binary_equal( dfield_datas_are_binary_equal(
/*==========================*/ /*==========================*/
const dfield_t* field1, /*!< in: field */ const dfield_t* field1, /*!< in: field */
const dfield_t* field2);/*!< in: field */ const dfield_t* field2, /*!< in: field */
ulint len) /*!< in: maximum prefix to compare,
or 0 to compare the whole field length */
__attribute__((nonnull, warn_unused_result));
/*********************************************************************//** /*********************************************************************//**
Tests if dfield data length and content is equal to the given. Tests if dfield data length and content is equal to the given.
@return TRUE if equal */ @return TRUE if equal */
......
...@@ -229,20 +229,30 @@ dfield_dup( ...@@ -229,20 +229,30 @@ dfield_dup(
} }
/*********************************************************************//** /*********************************************************************//**
Tests if data length and content is equal for two dfields. Tests if two data fields are equal.
@return TRUE if equal */ If len==0, tests the data length and content for equality.
If len>0, tests the first len bytes of the content for equality.
@return TRUE if both fields are NULL or if they are equal */
UNIV_INLINE UNIV_INLINE
ibool ibool
dfield_datas_are_binary_equal( dfield_datas_are_binary_equal(
/*==========================*/ /*==========================*/
const dfield_t* field1, /*!< in: field */ const dfield_t* field1, /*!< in: field */
const dfield_t* field2) /*!< in: field */ const dfield_t* field2, /*!< in: field */
ulint len) /*!< in: maximum prefix to compare,
or 0 to compare the whole field length */
{ {
ulint len; ulint len2 = len;
len = field1->len; if (field1->len == UNIV_SQL_NULL || len == 0 || field1->len < len) {
len = field1->len;
}
if (field2->len == UNIV_SQL_NULL || len2 == 0 || field2->len < len2) {
len2 = field2->len;
}
return(len == field2->len return(len == len2
&& (len == UNIV_SQL_NULL && (len == UNIV_SQL_NULL
|| !memcmp(field1->data, field2->data, len))); || !memcmp(field1->data, field2->data, len)));
} }
......
...@@ -286,10 +286,13 @@ row_upd_changes_ord_field_binary( ...@@ -286,10 +286,13 @@ row_upd_changes_ord_field_binary(
row and the data values in update are not row and the data values in update are not
known when this function is called, e.g., at known when this function is called, e.g., at
compile time */ compile time */
const row_ext_t*ext, /*!< NULL, or prefixes of the externally
stored columns in the old row */
dict_index_t* index, /*!< in: index of the record */ dict_index_t* index, /*!< in: index of the record */
const upd_t* update);/*!< in: update vector for the row; NOTE: the const upd_t* update) /*!< in: update vector for the row; NOTE: the
field numbers in this MUST be clustered index field numbers in this MUST be clustered index
positions! */ positions! */
__attribute__((nonnull(3,4), warn_unused_result));
/***********************************************************//** /***********************************************************//**
Checks if an update vector changes an ordering field of an index record. Checks if an update vector changes an ordering field of an index record.
This function is fast if the update vector is short or the number of ordering This function is fast if the update vector is short or the number of ordering
......
...@@ -413,7 +413,7 @@ row_purge_upd_exist_or_extern( ...@@ -413,7 +413,7 @@ row_purge_upd_exist_or_extern(
while (node->index != NULL) { while (node->index != NULL) {
index = node->index; index = node->index;
if (row_upd_changes_ord_field_binary(NULL, node->index, if (row_upd_changes_ord_field_binary(NULL, NULL, node->index,
node->update)) { node->update)) {
/* Build the older version of the index entry */ /* Build the older version of the index entry */
entry = row_build_index_entry(node->row, NULL, entry = row_build_index_entry(node->row, NULL,
......
...@@ -668,8 +668,8 @@ row_undo_mod_upd_exist_sec( ...@@ -668,8 +668,8 @@ row_undo_mod_upd_exist_sec(
while (node->index != NULL) { while (node->index != NULL) {
index = node->index; index = node->index;
if (row_upd_changes_ord_field_binary(node->row, node->index, if (row_upd_changes_ord_field_binary(
node->update)) { node->row, node->ext, node->index, node->update)) {
/* Build the newest version of the index entry */ /* Build the newest version of the index entry */
entry = row_build_index_entry(node->row, node->ext, entry = row_build_index_entry(node->row, node->ext,
......
...@@ -1198,20 +1198,21 @@ row_upd_changes_ord_field_binary( ...@@ -1198,20 +1198,21 @@ row_upd_changes_ord_field_binary(
row and the data values in update are not row and the data values in update are not
known when this function is called, e.g., at known when this function is called, e.g., at
compile time */ compile time */
const row_ext_t*ext, /*!< NULL, or prefixes of the externally
stored columns in the old row */
dict_index_t* index, /*!< in: index of the record */ dict_index_t* index, /*!< in: index of the record */
const upd_t* update) /*!< in: update vector for the row; NOTE: the const upd_t* update) /*!< in: update vector for the row; NOTE: the
field numbers in this MUST be clustered index field numbers in this MUST be clustered index
positions! */ positions! */
{ {
ulint n_unique; ulint n_unique;
ulint n_upd_fields; ulint i;
ulint i, j; const dict_index_t* clust_index;
dict_index_t* clust_index;
ut_ad(update && index); ut_ad(update);
ut_ad(index);
n_unique = dict_index_get_n_unique(index); n_unique = dict_index_get_n_unique(index);
n_upd_fields = upd_get_n_fields(update);
clust_index = dict_table_get_first_index(index->table); clust_index = dict_table_get_first_index(index->table);
...@@ -1219,33 +1220,72 @@ row_upd_changes_ord_field_binary( ...@@ -1219,33 +1220,72 @@ row_upd_changes_ord_field_binary(
const dict_field_t* ind_field; const dict_field_t* ind_field;
const dict_col_t* col; const dict_col_t* col;
ulint col_pos;
ulint col_no; ulint col_no;
const upd_field_t* upd_field;
const dfield_t* dfield;
dfield_t dfield_ext;
ulint dfield_len;
const byte* buf;
ind_field = dict_index_get_nth_field(index, i); ind_field = dict_index_get_nth_field(index, i);
col = dict_field_get_col(ind_field); col = dict_field_get_col(ind_field);
col_pos = dict_col_get_clust_pos(col, clust_index);
col_no = dict_col_get_no(col); col_no = dict_col_get_no(col);
for (j = 0; j < n_upd_fields; j++) { upd_field = upd_get_field_by_field_no(
update, dict_col_get_clust_pos(col, clust_index));
const upd_field_t* upd_field if (upd_field == NULL) {
= upd_get_nth_field(update, j); continue;
}
/* Note that if the index field is a column prefix if (row == NULL) {
then it may be that row does not contain an externally ut_ad(ext == NULL);
stored part of the column value, and we cannot compare return(TRUE);
the datas */ }
if (col_pos == upd_field->field_no dfield = dtuple_get_nth_field(row, col_no);
&& (row == NULL
|| ind_field->prefix_len > 0
|| !dfield_datas_are_binary_equal(
dtuple_get_nth_field(row, col_no),
&(upd_field->new_val)))) {
return(TRUE); /* This treatment of column prefix indexes is loosely
based on row_build_index_entry(). */
if (UNIV_LIKELY(ind_field->prefix_len == 0)
|| dfield_is_null(dfield)) {
/* do nothing special */
} else if (UNIV_LIKELY_NULL(ext)) {
/* See if the column is stored externally. */
buf = row_ext_lookup(ext, col_no, &dfield_len);
ut_ad(col->ord_part);
if (UNIV_LIKELY_NULL(buf)) {
if (UNIV_UNLIKELY(buf == field_ref_zero)) {
/* This should never happen, but
we try to fail safe here. */
ut_ad(0);
return(TRUE);
}
goto copy_dfield;
} }
} else if (dfield_is_ext(dfield)) {
dfield_len = dfield_get_len(dfield);
ut_a(dfield_len > BTR_EXTERN_FIELD_REF_SIZE);
dfield_len -= BTR_EXTERN_FIELD_REF_SIZE;
ut_a(dict_index_is_clust(index)
|| ind_field->prefix_len <= dfield_len);
buf = dfield_get_data(dfield);
copy_dfield:
ut_a(dfield_len > 0);
dfield_copy(&dfield_ext, dfield);
dfield_set_data(&dfield_ext, buf, dfield_len);
dfield = &dfield_ext;
}
if (!dfield_datas_are_binary_equal(
dfield, &upd_field->new_val,
ind_field->prefix_len)) {
return(TRUE);
} }
} }
...@@ -1329,7 +1369,7 @@ row_upd_changes_first_fields_binary( ...@@ -1329,7 +1369,7 @@ row_upd_changes_first_fields_binary(
if (col_pos == upd_field->field_no if (col_pos == upd_field->field_no
&& !dfield_datas_are_binary_equal( && !dfield_datas_are_binary_equal(
dtuple_get_nth_field(entry, i), dtuple_get_nth_field(entry, i),
&(upd_field->new_val))) { &upd_field->new_val, 0)) {
return(TRUE); return(TRUE);
} }
...@@ -1568,8 +1608,8 @@ row_upd_sec_step( ...@@ -1568,8 +1608,8 @@ row_upd_sec_step(
ut_ad(!dict_index_is_clust(node->index)); ut_ad(!dict_index_is_clust(node->index));
if (node->state == UPD_NODE_UPDATE_ALL_SEC if (node->state == UPD_NODE_UPDATE_ALL_SEC
|| row_upd_changes_ord_field_binary(node->row, node->index, || row_upd_changes_ord_field_binary(node->row, node->ext,
node->update)) { node->index, node->update)) {
return(row_upd_sec_index_entry(node, thr)); return(row_upd_sec_index_entry(node, thr));
} }
...@@ -1973,7 +2013,8 @@ exit_func: ...@@ -1973,7 +2013,8 @@ exit_func:
row_upd_store_row(node); row_upd_store_row(node);
if (row_upd_changes_ord_field_binary(node->row, index, node->update)) { if (row_upd_changes_ord_field_binary(node->row, node->ext, index,
node->update)) {
/* Update causes an ordering field (ordering fields within /* Update causes an ordering field (ordering fields within
the B-tree) of the clustered index record to change: perform the B-tree) of the clustered index record to change: perform
......
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