Skip to content

Commit cd79f10

Browse files
committed
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
1 parent e996f77 commit cd79f10

File tree

3 files changed

+81
-5
lines changed

3 files changed

+81
-5
lines changed

mysql-test/suite/innodb/r/foreign_key.result

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,9 @@ pk a b
727727
13 0 1
728728
14 0 1
729729
15 1 0
730-
disconnect con1;
730+
connection con1;
731+
COMMIT;
732+
connection default;
731733
InnoDB 0 transactions not purged
732734
CHECK TABLE t1;
733735
Table Op Msg_type Msg_text
@@ -906,5 +908,26 @@ CONSTRAINT FK_t1_id FOREIGN KEY (t1_id) REFERENCES t1 (id)
906908
ALTER TABLE t1 MODIFY id INT unsigned AUTO_INCREMENT;
907909
DROP TABLE t1,t2;
908910
#
909-
# End of 10.4 tests
911+
# MDEV-31441 BLOB corruption on UPDATE of PRIMARY KEY with FOREIGN KEY
910912
#
913+
CREATE TABLE t1 (pk INT PRIMARY KEY, t TEXT) ENGINE=InnoDB;
914+
CREATE TABLE t2 (pk INT PRIMARY KEY, FOREIGN KEY (pk) REFERENCES t1(pk))
915+
ENGINE=InnoDB;
916+
SET @blob = REPEAT('A', @@innodb_page_size / 2);
917+
INSERT INTO t1 SET pk=1, t=@blob;
918+
INSERT INTO t2 SET pk=1;
919+
connection con1;
920+
BEGIN;
921+
DELETE FROM t2;
922+
connection default;
923+
UPDATE t1 SET pk=12;
924+
connection con1;
925+
COMMIT;
926+
disconnect con1;
927+
connection default;
928+
UPDATE t1 SET pk=1;
929+
SELECT pk,t=@blob FROM t1;
930+
pk t=@blob
931+
1 1
932+
DROP TABLE t2, t1;
933+
# End of 10.4 tests

mysql-test/suite/innodb/t/foreign_key.test

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,9 @@ SELECT a FROM t1 FORCE INDEX(a);
732732
# the "goto rollback_to_savept" in row_mysql_handle_errors() is reverted.
733733
SELECT * FROM t1;
734734
# Allow purge to continue by closing the read view.
735-
disconnect con1;
735+
connection con1;
736+
COMMIT;
737+
connection default;
736738

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

956958
--echo #
957-
--echo # End of 10.4 tests
959+
--echo # MDEV-31441 BLOB corruption on UPDATE of PRIMARY KEY with FOREIGN KEY
958960
--echo #
959961

962+
CREATE TABLE t1 (pk INT PRIMARY KEY, t TEXT) ENGINE=InnoDB;
963+
CREATE TABLE t2 (pk INT PRIMARY KEY, FOREIGN KEY (pk) REFERENCES t1(pk))
964+
ENGINE=InnoDB;
965+
966+
SET @blob = REPEAT('A', @@innodb_page_size / 2);
967+
INSERT INTO t1 SET pk=1, t=@blob;
968+
INSERT INTO t2 SET pk=1;
969+
--connection con1
970+
BEGIN;
971+
DELETE FROM t2;
972+
--connection default
973+
# The following will be blocked by a FOREIGN KEY check on pk=1 in t2.
974+
--send
975+
UPDATE t1 SET pk=12;
976+
--connection con1
977+
let $wait_condition=
978+
SELECT count(*) > 0 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE state='Updating';
979+
--source include/wait_condition.inc
980+
COMMIT;
981+
--disconnect con1
982+
--connection default
983+
--reap
984+
UPDATE t1 SET pk=1;
985+
SELECT pk,t=@blob FROM t1;
986+
DROP TABLE t2, t1;
987+
988+
--echo # End of 10.4 tests
989+
960990
--source include/wait_until_count_sessions.inc

storage/innobase/row/row0upd.cc

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2698,6 +2698,25 @@ row_upd_clust_rec_by_insert_inherit_func(
26982698
return(inherit);
26992699
}
27002700

2701+
/** Mark 'disowned' BLOBs as 'owned' and 'inherited' again,
2702+
after resuming from a lock wait.
2703+
@param entry clustered index entry */
2704+
static ATTRIBUTE_COLD void row_upd_reown_inherited_fields(dtuple_t *entry)
2705+
{
2706+
for (ulint i= 0; i < entry->n_fields; i++)
2707+
{
2708+
const dfield_t *dfield= dtuple_get_nth_field(entry, i);
2709+
if (dfield_is_ext(dfield))
2710+
{
2711+
byte *blob_len= static_cast<byte*>(dfield->data) +
2712+
dfield->len - (BTR_EXTERN_FIELD_REF_SIZE - BTR_EXTERN_LEN);
2713+
ut_ad(*blob_len & BTR_EXTERN_OWNER_FLAG);
2714+
*blob_len= byte(*blob_len & ~BTR_EXTERN_OWNER_FLAG) |
2715+
BTR_EXTERN_INHERITED_FLAG;
2716+
}
2717+
}
2718+
}
2719+
27012720
/***********************************************************//**
27022721
Marks the clustered index record deleted and inserts the updated version
27032722
of the record to the index. This function should be used when the ordering
@@ -2776,12 +2795,16 @@ row_upd_clust_rec_by_insert(
27762795
/* If the clustered index record is already delete
27772796
marked, then we are here after a DB_LOCK_WAIT.
27782797
Skip delete marking clustered index and disowning
2779-
its blobs. */
2798+
its blobs. Mark the BLOBs in the index entry
2799+
(which we copied from the already "disowned" rec)
2800+
as "owned", like it was on the previous call of
2801+
row_upd_clust_rec_by_insert(). */
27802802
ut_ad(row_get_rec_trx_id(rec, index, offsets)
27812803
== trx->id);
27822804
ut_ad(!trx_undo_roll_ptr_is_insert(
27832805
row_get_rec_roll_ptr(rec, index,
27842806
offsets)));
2807+
row_upd_reown_inherited_fields(entry);
27852808
goto check_fk;
27862809
}
27872810

0 commit comments

Comments
 (0)