Skip to content

Bug#121193 InnoDB: rollback of an interrupted BLOB update frees a still-referenced LOB - #739

Open
JoeJRW wants to merge 1 commit into
mysql:trunkfrom
JoeJRW:bug_lob_rollback_frees_referenced_zblob
Open

Bug#121193 InnoDB: rollback of an interrupted BLOB update frees a still-referenced LOB#739
JoeJRW wants to merge 1 commit into
mysql:trunkfrom
JoeJRW:bug_lob_rollback_frees_referenced_zblob

Conversation

@JoeJRW

@JoeJRW JoeJRW commented Sep 1, 2026

Copy link
Copy Markdown

What does this change do?

Stops lob::purge() from destroying an old-format LOB during rollback when the
record's reference still designates the value that row_undo_mod_clust() is
about 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 UPDATE is storing a new externally
stored 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 store
is 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) in buf0buf.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_freed is itself under UNIV_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 of
the NEW value is immaterial.

Of the three branches in lob::purge(), only FIL_PAGE_TYPE_ZBLOB/ZBLOB2/BLOB
ignores is_rollback; LOB_FIRST and ZLOB_FIRST are rollback aware.

How was it tested?

  • Added/updated MTR tests under mysql-test/
  • scripts/ci/mtr.sh passes locally
  • Ran the relevant full suite (name it): innodb (via the default MTR selection)

Added innodb.lob_rollback_frees_referenced_zblob: it stops inside the LOB store
at the blob_write_middle sync point, kills the server, and checks after recovery
that 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) fail
identically with and without this patch.

The relevant subset was verified by A/B comparison — same tests, same MTR
invocation, only the patch toggled:

Test patch reverted patch applied
innodb.lob_rollback_frees_referenced_zblob fail pass
main.skip_records_in_range fail fail
perfschema.transaction_nested_events fail fail
innodb_undo.truncate_recover_e01..e11 pass (11) pass (11)
main.histograms, innodb.missing_redologs, innodb.check_sector_size pass pass
binlog.binlog_error_action 'row' pass pass

Identical on both sides except the new test, which flips from fail to pass. All
innodb tests whose name contains lob or blob pass.

Contributor checklist

  • Code is formatted (scripts/ci/format.sh)
  • Commits are focused with descriptive messages

AI assistance

  • I did not use AI assistance for this contribution
  • I used AI assistance for this contribution

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 MTR
test 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_TYPE of every page in the .ibd file, after
INFORMATION_SCHEMA.INNODB_BUFFER_PAGE turned out not to report compressed BLOB
pages. 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

…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.
@JoeJRW
JoeJRW requested a review from a team September 1, 2026 03:47
@oracle-contributor-agreement oracle-contributor-agreement Bot added the OCA Verified All contributors have signed the Oracle Contributor Agreement. label Sep 1, 2026
@github-actions github-actions Bot added InnoDB Changes touching InnoDB storage engine code Tests Changes touching test code or test data Review Requested Review requested from code owners labels Sep 1, 2026
@github-actions github-actions Bot added Build Passed PR build passed MTR Failed MTR suite failed labels Sep 2, 2026
@gopshank
gopshank requested review from mayprasa and removed request for gopshank and seemasundara September 4, 2026 07:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Build Passed PR build passed InnoDB Changes touching InnoDB storage engine code MTR Failed MTR suite failed OCA Verified All contributors have signed the Oracle Contributor Agreement. Review Requested Review requested from code owners Tests Changes touching test code or test data

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant