Skip to content

Commit

Permalink
MDEV-31441 BLOB corruption on UPDATE of PRIMARY KEY with FOREIGN KEY
Browse files Browse the repository at this point in the history
row_upd_clust_rec_by_insert(): If we are resuming from a lock wait,
reset the 'disowned' flag of the BLOB pointers in 'entry' that we
copied from 'rec' on which we had invoked btr_cur_disown_inherited_fields()
before the lock wait started. In this way, the inserted record with
the updated PRIMARY KEY value will have the BLOB ownership associated
with itself, like it is supposed to be.

Note: If the lock wait had been aborted, then rollback would have
invoked btr_cur_unmark_extern_fields() and no corruption would be possible.

Reviewed by: Vladislav Lesin
Tested by: Matthias Leich
  • Loading branch information
dr-m committed Nov 29, 2023
1 parent e996f77 commit cd79f10
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 5 deletions.
27 changes: 25 additions & 2 deletions mysql-test/suite/innodb/r/foreign_key.result
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,9 @@ pk a b
13 0 1
14 0 1
15 1 0
disconnect con1;
connection con1;
COMMIT;
connection default;
InnoDB 0 transactions not purged
CHECK TABLE t1;
Table Op Msg_type Msg_text
Expand Down Expand Up @@ -906,5 +908,26 @@ CONSTRAINT FK_t1_id FOREIGN KEY (t1_id) REFERENCES t1 (id)
ALTER TABLE t1 MODIFY id INT unsigned AUTO_INCREMENT;
DROP TABLE t1,t2;
#
# End of 10.4 tests
# MDEV-31441 BLOB corruption on UPDATE of PRIMARY KEY with FOREIGN KEY
#
CREATE TABLE t1 (pk INT PRIMARY KEY, t TEXT) ENGINE=InnoDB;
CREATE TABLE t2 (pk INT PRIMARY KEY, FOREIGN KEY (pk) REFERENCES t1(pk))
ENGINE=InnoDB;
SET @blob = REPEAT('A', @@innodb_page_size / 2);
INSERT INTO t1 SET pk=1, t=@blob;
INSERT INTO t2 SET pk=1;
connection con1;
BEGIN;
DELETE FROM t2;
connection default;
UPDATE t1 SET pk=12;
connection con1;
COMMIT;
disconnect con1;
connection default;
UPDATE t1 SET pk=1;
SELECT pk,t=@blob FROM t1;
pk t=@blob
1 1
DROP TABLE t2, t1;
# End of 10.4 tests
34 changes: 32 additions & 2 deletions mysql-test/suite/innodb/t/foreign_key.test
Original file line number Diff line number Diff line change
Expand Up @@ -732,7 +732,9 @@ SELECT a FROM t1 FORCE INDEX(a);
# the "goto rollback_to_savept" in row_mysql_handle_errors() is reverted.
SELECT * FROM t1;
# Allow purge to continue by closing the read view.
disconnect con1;
connection con1;
COMMIT;
connection default;

# Wait for purge. With the fix reverted, the server would crash here.
--source include/wait_all_purged.inc
Expand Down Expand Up @@ -954,7 +956,35 @@ ALTER TABLE t1 MODIFY id INT unsigned AUTO_INCREMENT;
DROP TABLE t1,t2;

--echo #
--echo # End of 10.4 tests
--echo # MDEV-31441 BLOB corruption on UPDATE of PRIMARY KEY with FOREIGN KEY
--echo #

CREATE TABLE t1 (pk INT PRIMARY KEY, t TEXT) ENGINE=InnoDB;
CREATE TABLE t2 (pk INT PRIMARY KEY, FOREIGN KEY (pk) REFERENCES t1(pk))
ENGINE=InnoDB;

SET @blob = REPEAT('A', @@innodb_page_size / 2);
INSERT INTO t1 SET pk=1, t=@blob;
INSERT INTO t2 SET pk=1;
--connection con1
BEGIN;
DELETE FROM t2;
--connection default
# The following will be blocked by a FOREIGN KEY check on pk=1 in t2.
--send
UPDATE t1 SET pk=12;
--connection con1
let $wait_condition=
SELECT count(*) > 0 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE state='Updating';
--source include/wait_condition.inc
COMMIT;
--disconnect con1
--connection default
--reap
UPDATE t1 SET pk=1;
SELECT pk,t=@blob FROM t1;
DROP TABLE t2, t1;

--echo # End of 10.4 tests

--source include/wait_until_count_sessions.inc
25 changes: 24 additions & 1 deletion storage/innobase/row/row0upd.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2698,6 +2698,25 @@ row_upd_clust_rec_by_insert_inherit_func(
return(inherit);
}

/** Mark 'disowned' BLOBs as 'owned' and 'inherited' again,
after resuming from a lock wait.
@param entry clustered index entry */
static ATTRIBUTE_COLD void row_upd_reown_inherited_fields(dtuple_t *entry)
{
for (ulint i= 0; i < entry->n_fields; i++)
{
const dfield_t *dfield= dtuple_get_nth_field(entry, i);
if (dfield_is_ext(dfield))
{
byte *blob_len= static_cast<byte*>(dfield->data) +
dfield->len - (BTR_EXTERN_FIELD_REF_SIZE - BTR_EXTERN_LEN);
ut_ad(*blob_len & BTR_EXTERN_OWNER_FLAG);
*blob_len= byte(*blob_len & ~BTR_EXTERN_OWNER_FLAG) |
BTR_EXTERN_INHERITED_FLAG;
}
}
}

/***********************************************************//**
Marks the clustered index record deleted and inserts the updated version
of the record to the index. This function should be used when the ordering
Expand Down Expand Up @@ -2776,12 +2795,16 @@ row_upd_clust_rec_by_insert(
/* If the clustered index record is already delete
marked, then we are here after a DB_LOCK_WAIT.
Skip delete marking clustered index and disowning
its blobs. */
its blobs. Mark the BLOBs in the index entry
(which we copied from the already "disowned" rec)
as "owned", like it was on the previous call of
row_upd_clust_rec_by_insert(). */
ut_ad(row_get_rec_trx_id(rec, index, offsets)
== trx->id);
ut_ad(!trx_undo_roll_ptr_is_insert(
row_get_rec_roll_ptr(rec, index,
offsets)));
row_upd_reown_inherited_fields(entry);
goto check_fk;
}

Expand Down

0 comments on commit cd79f10

Please sign in to comment.