Commit b7b79efe authored by Vlad Lesin's avatar Vlad Lesin

MDEV-33802 Weird read view after ROLLBACK of other transactions.

In the case when all unique key fields are nullable, there can be
several records with all NULL key fields in unique index. When
transaction is resumed after waiting on the record with all key fields
equal to NULL, and stored in persistent cursor record is deleted,
persistent cursor can be restored on the record with all key fields equal
to NULL, and such record is wrongly treated as the record with the same
unique key as the stored in persistent cursor record, what is wrong.

The fix is to check if all unique fields are null when persistent cursor
position is restored and not all comparing fields are equal, and if so,
don't treat the record as a record with the same unique key as in the
stored in persistent cursor record key.

dict_index_t::nulls_equal was removed, as it was initially developed for
never existed in MariaDB "intrinsic tables", and there is no code, which
would set it to "true".

Reviewed by Marko Mäkelä.
parent ccb7a1e9
CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, UNIQUE KEY `b_c` (`b`,`c`))
ENGINE=InnoDB, STATS_PERSISTENT=0;
INSERT INTO t(a) VALUES (1);
connect con1,localhost,root;
connection con1;
BEGIN;
INSERT INTO t SET a=2;
connection default;
BEGIN;
SET DEBUG_SYNC="lock_wait_start SIGNAL select_locked";
SELECT * FROM t FORCE INDEX(b) FOR UPDATE;
connection con1;
SET DEBUG_SYNC="now WAIT_FOR select_locked";
ROLLBACK;
connection default;
# If the bug is not fixed, and the both unique index key fields are
# NULL, there will be two (1, NULL, NULL) rows in the result,
# because cursor will be restored to (NULL, NULL, 1) position for
# secondary key instead of "supremum".
a b c
1 NULL NULL
COMMIT;
connection con1;
BEGIN;
INSERT INTO t SET a=2, c=2;
connection default;
BEGIN;
SET DEBUG_SYNC="lock_wait_start SIGNAL select_locked";
SELECT * FROM t FORCE INDEX(b) FOR UPDATE;
connection con1;
SET DEBUG_SYNC="now WAIT_FOR select_locked";
ROLLBACK;
connection default;
a b c
1 NULL NULL
COMMIT;
SET DEBUG_SYNC="RESET";
disconnect con1;
DROP TABLE t;
--source include/have_innodb.inc
--source include/have_debug.inc
--source include/have_debug_sync.inc
--source include/count_sessions.inc
CREATE TABLE t(a INT PRIMARY KEY, b INT, c INT, UNIQUE KEY `b_c` (`b`,`c`))
ENGINE=InnoDB, STATS_PERSISTENT=0;
INSERT INTO t(a) VALUES (1);
--connect con1,localhost,root
--let $i = 2
while ($i) {
--connection con1
BEGIN;
if ($i == 2) {
INSERT INTO t SET a=2;
}
if ($i == 1) {
INSERT INTO t SET a=2, c=2;
}
--connection default
BEGIN;
SET DEBUG_SYNC="lock_wait_start SIGNAL select_locked";
--send SELECT * FROM t FORCE INDEX(b) FOR UPDATE
--connection con1
SET DEBUG_SYNC="now WAIT_FOR select_locked";
ROLLBACK;
--connection default
if ($i == 2) {
--echo # If the bug is not fixed, and the both unique index key fields are
--echo # NULL, there will be two (1, NULL, NULL) rows in the result,
--echo # because cursor will be restored to (NULL, NULL, 1) position for
--echo # secondary key instead of "supremum".
}
--reap
COMMIT;
--dec $i
}
SET DEBUG_SYNC="RESET";
--disconnect con1
DROP TABLE t;
--source include/wait_until_count_sessions.inc
......@@ -310,6 +310,7 @@ struct optimistic_latch_leaves
}
};
/** Restores the stored position of a persistent cursor bufferfixing
the page and obtaining the specified latches. If the cursor position
was saved when the
......@@ -493,8 +494,22 @@ btr_pcur_t::restore_position(btr_latch_mode restore_latch_mode, mtr_t *mtr)
return restore_status::SAME_ALL;
}
if (n_matched_fields >= index->n_uniq)
ret_val= restore_status::SAME_UNIQ;
if (n_matched_fields >= index->n_uniq) {
/* Unique indexes can contain "NULL" keys, and if all
unique fields are NULL and not all tuple
fields match to record fields, then treat it as if
restored cursor position points to the record with
not the same unique key. */
if (!index->n_nullable)
ret_val= restore_status::SAME_UNIQ;
else
for (uint i= 0; i < index->n_uniq; ++i)
if (!dfield_is_null(
dtuple_get_nth_field(tuple, i))) {
ret_val= restore_status::SAME_UNIQ;
break;
}
}
}
mem_heap_free(heap);
......
......@@ -1987,7 +1987,6 @@ dict_index_add_to_cache(
new_index->n_fields = new_index->n_def;
new_index->trx_id = index->trx_id;
new_index->set_committed(index->is_committed());
new_index->nulls_equal = index->nulls_equal;
n_ord = new_index->n_uniq;
/* Flag the ordering columns and also set column max_prefix */
......
......@@ -1019,8 +1019,6 @@ struct dict_index_t {
/*!< number of columns the user defined to
be in the index: in the internal
representation we add more columns */
unsigned nulls_equal:1;
/*!< if true, SQL NULL == SQL NULL */
unsigned n_uniq:10;/*!< number of fields from the beginning
which are enough to determine an index
entry uniquely */
......
......@@ -63,6 +63,5 @@ dict_mem_fill_index_struct(
& index->MAX_N_FIELDS;
/* The '1 +' above prevents allocation
of an empty mem block */
index->nulls_equal = false;
ut_d(index->magic_n = DICT_INDEX_MAGIC_N);
}
......@@ -2000,7 +2000,7 @@ row_ins_dupl_error_with_rec(
/* In a unique secondary index we allow equal key values if they
contain SQL NULLs */
if (!dict_index_is_clust(index) && !index->nulls_equal) {
if (!dict_index_is_clust(index)) {
for (i = 0; i < n_unique; i++) {
if (dfield_is_null(dtuple_get_nth_field(entry, i))) {
......@@ -2103,13 +2103,11 @@ row_ins_scan_sec_index_for_duplicate(
n_unique first fields is NULL, a unique key violation cannot occur,
since we define NULL != NULL in this case */
if (!index->nulls_equal) {
for (ulint i = 0; i < n_unique; i++) {
if (UNIV_SQL_NULL == dfield_get_len(
dtuple_get_nth_field(entry, i))) {
for (ulint i = 0; i < n_unique; i++) {
if (UNIV_SQL_NULL == dfield_get_len(
dtuple_get_nth_field(entry, i))) {
DBUG_RETURN(DB_SUCCESS);
}
DBUG_RETURN(DB_SUCCESS);
}
}
......
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