Commit cd79f102 authored by Marko Mäkelä's avatar Marko Mäkelä

MDEV-31441 BLOB corruption on UPDATE of PRIMARY KEY with FOREIGN KEY

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
parent e996f77c
......@@ -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
......@@ -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
......@@ -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
......@@ -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
......@@ -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
......@@ -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;
}
......
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