Bug#102586: multi-table DELETE with ON DELETE CASCADE breaks row-based replication - #719
Bug#102586: multi-table DELETE with ON DELETE CASCADE breaks row-based replication#719matanbaruch wants to merge 3 commits into
Conversation
A multi-table DELETE that names both a foreign key parent table and a child table with a cascading delete rule breaks row-based replication. The replica applier stops with ER_KEY_NOT_FOUND. The parent row is deleted while the join is still scanning, so the cascade removes the child rows and logs row events for them. The statement logs row events for the child rows it deletes itself as well. On the replica the parent delete is applied first, its own cascade removes the child rows, and the logged child events then cannot find them. Exclude a delete target from immediate deletion when deleting from it cascades to another table in the same query, which defers the delete until the join has finished. The check is added to both the classic optimizer (GetImmediateDeleteTables) and the hypergraph optimizer (IsImmediateDeleteCandidate). Only ON DELETE CASCADE is considered. ON DELETE SET NULL updates the child rows rather than deleting them, so they stay findable for the logged events and replicate correctly. Deferring those deletes as well would change which rows the statement removes. This is the approach Zsolt Parragi contributed on Bug#80821 in 2019, adapted to the current code: get_cascade_foreign_key_table_list() no longer exists, so the cascade dependency is resolved from TABLE_SHARE::foreign_key_parent instead.
|
I confirm the code being submitted is offered under the terms of the OCA, and that I am authorized to contribute it |
|
The three red checks here are CI-side and all of them reproduce without this patch. Writing up what I found, in case it is useful: 1. Build, MTR and Format Check never see the code. All three workflows trigger on Checkout fails in about 5 seconds and every later step then fails with 2. Format Check fails on trunk itself. It runs 3. MTR cannot finish inside the budget. Since CI here cannot run the code, I ran your workflows unmodified on a fork-internal PR, where checkout is allowed:
Local Debug build of 26.7.0, same commit, before and after:
Both optimizer paths decide immediate deletion in different places and trunk fails in both, which is why the patch touches both. If you want the format gate green on this PR I can add a separate commit that reformats those three files, but it is 2306 unrelated lines and I would rather not unless you ask for it. |
|
@matanbaruch You are totally right regarding about the github actions failures. I have described the issue in more details in the issue #715. I have a patch for it that will be deployed early next week, it took a bit of time to deploy it as I also tried to stabilize the MTRs. Sorry about the noise ! |
|
@matanbaruch The fix for issue #715 have been merged in trunk. Can you please rebase your branch to trigger the pipelines ? |
… change Format Check runs clang-format-18 over whole changed files. These three are not clean under 18 on trunk, so the gate fails for any PR touching them. Cosmetic only: a label space in sql_base.cc, one DBUG_LOG argument wrap in sql_delete.cc, and two string literal joins in join_optimizer.cc.
|
@RidhaOracle MTR (replication) failed only on |
What does this change do?
A multi-table DELETE that names both a foreign key parent table and its
ON DELETE CASCADEchild breaks row-based replication: the replica applier stops with ER_KEY_NOT_FOUND. This defers the delete until the join has finished when a cascade dependency exists between tables inside the query, on both the classic and the hypergraph optimizer path.BUG#102586, BUG#80821
Why is it needed?
Both bugs are Verified and still unfixed. #80821 was reported in 2016, #102586 in 2021, and neither has moved since October 2023. #102586 is S1.
What happens:
A single-table DELETE that relies on the cascade is fine. Only the multi-table form is affected.
We hit this in production on 8.4.8 LTS, which is listed on neither bug report. Three replicas broke inside the same five minute window and stayed broken for three weeks, because Amazon RDS kept restarting the applier and the lag looked normal. A local harness reproduces it identically on 8.0.35, so this is not a regression in a recent version, it has just never been fixed.
The design is Zsolt Parragi's, contributed on #80821 in 2019 and acknowledged by Oracle at the time. That patch no longer applies:
get_cascade_foreign_key_table_list()was removed. This version resolves the cascade dependency fromTABLE_SHARE::foreign_key_parentinstead, and covers the hypergraph optimizer, which did not exist in 2019.How was it tested?
mysql-test/scripts/ci/mtr.shpasses locallyrpl, 1100 tests,--parallel=8 --forceNew test:
rpl.rpl_multi_table_delete_fk_cascade. Debug build, 26.7.0, Linux, before and after the same commit:HA_ERR_KEY_NOT_FOUNDontest.t2Both optimizer paths were checked separately because they decide immediate deletion in different places, and trunk fails in both.
Also passing on this branch:
main.foreign_key_cascade,innodb.innodb,innodb.innodb_misc1(the existing tests that combine cascading foreign keys with multi-table DELETE), and the 25 tests matching--do-test=delete.The test keeps an
ON DELETE SET NULLcase as negative coverage. That one replicates correctly on trunk, so it is deliberately left in immediate mode: deferring it would change which rows the statement removes.Two tests failed in the full
rplrun and both pass in isolation on the same binary, so I read them as flaky under--parallel=8rather than caused by this change:rpl_crash_on_pfs_worker_table_against_replica_stopandrpl_parallel_alter_db_table. Neither involves DELETE or foreign keys. If they are known-stable in your CI, say so and I will dig further.Contributor checklist
scripts/ci/format.sh)AI assistance
Claude Code wrote the patch and the MTR test. The fix design is not new, it is Parragi's 2019 approach from #80821 rebased onto current trunk. Everything claimed above was verified by running it, not by inspection: the before and after matrix comes from real MTR runs on a local Debug build, in both optimizer modes. The production root cause behind it was diagnosed separately against a Docker reproduction of our schema.
Areas touched
optimizer, replication