Bug#121193 InnoDB: rollback of an interrupted BLOB update frees a still-referenced LOB - #739
Open
JoeJRW wants to merge 1 commit into
Open
Bug#121193 InnoDB: rollback of an interrupted BLOB update frees a still-referenced LOB#739JoeJRW wants to merge 1 commit into
JoeJRW wants to merge 1 commit into
Conversation
…ll-referenced LOB Problem: ======== On a compressed table, a crash while an UPDATE is storing a new externally stored BLOB corrupts the row. Recovery rollback frees the OLD LOB and then restores the record to point at the pages it has just freed, leaving a live row that references free pages. Root Cause: =========== btr_store_big_rec_extern_fields() leaves the record's LOB reference designating the pre-update LOB until the store is far enough along to redirect it. Crash in that window and recovery rollback reaches lob::purge(), which dispatches on the page type of the page the reference points at. FIL_PAGE_TYPE_LOB_FIRST and FIL_PAGE_TYPE_ZLOB_FIRST are handled by rollback aware code, but the FIL_PAGE_TYPE_ZBLOB/ZBLOB2/BLOB branch calls Deleter::destroy() unconditionally and never looks at is_rollback, so the old LOB is destroyed. Solution: ========= In that branch, when rolling back, do not destroy the LOB if the reference designates the very value that row_undo_mod_clust() is about to restore into the record (uf->new_val). Comparing page numbers keeps the check exact: once the reference has been redirected the two differ, so the LOB that was actually being stored is still freed.
gopshank
requested review from
mayprasa
and removed request for
gopshank and
seemasundara
September 4, 2026 07:51
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this change do?
Stops
lob::purge()from destroying an old-format LOB during rollback when therecord's reference still designates the value that
row_undo_mod_clust()isabout to restore.
Fixes BUG#121193 — https://bugs.mysql.com/bug.php?id=121193
Why is it needed?
On a compressed table, a crash while an
UPDATEis storing a new externallystored BLOB leaves the record's LOB reference still pointing at the pre-update
LOB —
btr_store_big_rec_extern_fields()does not redirect it until the storeis far enough along. Recovery rollback frees that LOB and then restores the
record to point at the pages it has just freed, so a live row references free
pages.
On a debug build the next read of that row trips the assertion
ut_ad(is_possibly_freed() || !block->page.file_page_was_freed)inbuf0buf.cc.Once the freed pages have been handed out to another row, the row reads back
data belonging to their new owner. A release build has no equivalent check —
file_page_was_freedis itself underUNIV_DEBUG.Reaching the faulty branch requires the OLD value to be in the old format, which
is why a compressed table is needed: there any LOB of at most
Z_CHUNK_SIZE(128K) is stored as a ZBLOB, see
ref_t::use_single_z_stream(). The format ofthe NEW value is immaterial.
Of the three branches in
lob::purge(), onlyFIL_PAGE_TYPE_ZBLOB/ZBLOB2/BLOBignores
is_rollback;LOB_FIRSTandZLOB_FIRSTare rollback aware.How was it tested?
mysql-test/scripts/ci/mtr.shpasses locallyAdded
innodb.lob_rollback_frees_referenced_zblob: it stops inside the LOB storeat the
blob_write_middlesync point, kills the server, and checks after recoverythat the row still owns its original LOB.
Verified on debug builds of 8.0.46, 8.4.11, 9.7.2 and 26.7.0 — the bug reproduces
identically on all four.
Ran the default MTR selection (39 suites, 6967 tests) on a 26.7.0 debug build:
107 failures, none related to this change. Most are keyring/encryption tests that
fail under high parallelism on this host and pass in isolation. Two
(
main.skip_records_in_range,perfschema.transaction_nested_events) failidentically with and without this patch.
The relevant subset was verified by A/B comparison — same tests, same MTR
invocation, only the patch toggled:
innodb.lob_rollback_frees_referenced_zblobmain.skip_records_in_rangeperfschema.transaction_nested_eventsinnodb_undo.truncate_recover_e01..e11main.histograms,innodb.missing_redologs,innodb.check_sector_sizebinlog.binlog_error_action 'row'Identical on both sides except the new test, which flips from fail to pass. All
innodbtests whose name containsloborblobpass.Contributor checklist
scripts/ci/format.sh)AI assistance
If AI assistance was used, describe the tool(s) and extent of use:
Tool: Claude Code (Anthropic).
Extent: substantial. AI assistance was used to locate the root cause in
lob::purge(), to draft the fix and its explanatory comment, to write the MTRtest case, and to draft the commit message.
Manually verified: the reproduction was run against unmodified debug builds of
8.0.46, 8.4.11, 9.7.2 and 26.7.0 and the resulting corruption observed directly.
The claim that the old value is stored as a ZBLOB was checked by reading the
FIL_PAGE_TYPEof every page in the.ibdfile, afterINFORMATION_SCHEMA.INNODB_BUFFER_PAGEturned out not to report compressed BLOBpages. Three variants of the test (small new value; ZLOB old value;
non-compressed table) were run to establish which parts of the reproduction are
actually load-bearing. The A/B comparison above was run to confirm that no other
test changes behaviour. The fix, the test and the commit message were reviewed
line by line and revised several times before submission.
Areas touched
innodb