Skip to content

DM: remove redundant DELETE and force non-cascade REPLACE in safe mode to support Foreign Key constraint with non-key updates (#12351)#12541

Merged
ti-chi-bot[bot] merged 21 commits intopingcap:release-8.5from
ti-chi-bot:cherry-pick-12351-to-release-8.5
Mar 16, 2026
Merged

DM: remove redundant DELETE and force non-cascade REPLACE in safe mode to support Foreign Key constraint with non-key updates (#12351)#12541
ti-chi-bot[bot] merged 21 commits intopingcap:release-8.5from
ti-chi-bot:cherry-pick-12351-to-release-8.5

Conversation

@ti-chi-bot
Copy link
Member

This is an automated cherry-pick of #12351

What problem does this PR solve?

Issue Number: ref #12350

What is changed and how it works?

  1. Remove redundant Delete statement from safe mode rewrite of Update when row identity(pk and uks) are not changed during the Update.
  2. Force replace into from safe mode to be executed without triggering cascade behaviors.
  3. Add integration test to verify safe mode under insert and non-key updates with FK and on cascade delete enabled.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No code

Questions

Will it cause performance regression or break compatibility?
Do you need to update user documentation, design documentation or monitoring documentation?

Release note

Please refer to [Release Notes Language Style Guide](https://pingcap.github.io/tidb-dev-guide/contribute-to-tidb/release-notes-style-guide.html) to write a quality release note.

If you don't think this PR needs a release note then fill it with `None`.

Remove redundant DELETE and force non-cascade REPLACE in safe mode to support Foreign Key constraint with non-key updates.

1. Remove redundant Delete statement from safe mode rewrite of Update
   when row identity(pk and uks) are not changed during the Update.
2. Force replace into from safe mode to be executed without triggering
   cascade behaviors.
3. Add integration test to verify safe mode under insert and non-key
   updates with FK and on cascade delete enabled.
…l be triggered and the test case will function as intended.
…efactor defer function in executeBatchJobs for better adaption to errors from genSQLs.
…abled." because the original check without FKinfo would be too strict and an overkill.

This reverts commit 9ae52f1.
…er be triggered under the current setup, even after after the attempt to make the test modify PK value as well (will result in len(queries) == len(jobs) in two queues). Therefore DM_DML_EXECUTE_ERROR is commented out for now.
@ti-chi-bot ti-chi-bot added area/dm Issues or PRs related to DM. lgtm ok-to-test Indicates a PR is ready to be tested. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. type/cherry-pick-for-release-8.5 This PR is cherry-picked to release-8.5 from a source PR. labels Mar 9, 2026
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the Data Migration (DM) tool's handling of DML operations in safe mode, particularly in scenarios involving Foreign Key constraints. The changes streamline the update process by intelligently omitting redundant DELETE operations when only non-key columns are modified. Furthermore, it introduces mechanisms to prevent unintended cascade actions during REPLACE operations, ensuring data consistency and integrity. These improvements are validated through a new, dedicated integration test case.

Highlights

  • Optimized DML Generation for Updates in Safe Mode: Removed redundant DELETE statements from safe mode rewrite of UPDATE operations when row identity (primary or unique keys) remains unchanged, improving efficiency and reducing unnecessary database operations.
  • Forced Non-Cascade REPLACE in Safe Mode: Ensured that REPLACE INTO statements generated in safe mode are executed without triggering cascade behaviors, which is critical for maintaining data integrity with Foreign Key constraints during non-key updates.
  • New Integration Test for Foreign Key Constraints: Added a comprehensive integration test to verify the correct behavior of safe mode under INSERT and non-key UPDATE operations, specifically with Foreign Key constraints and ON DELETE CASCADE enabled.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • dm/syncer/dml.go
    • Modified DML generation logic to conditionally include DELETE statements for UPDATE operations in safe mode, only when primary or unique keys are updated.
  • dm/syncer/dml_test.go
    • Updated expected queries and arguments in TestGenDMLWithSameOp to reflect the removal of redundant DELETE statements for non-key updates in safe mode.
  • dm/syncer/dml_worker.go
    • Added foreignKeyChecksEnabled field to DMLWorker to track the session's foreign key check status.
    • Introduced isForeignKeyChecksEnabled function to parse the foreign_key_checks session variable.
    • Implemented shouldDisableForeignKeyChecksForJob to determine if foreign key checks should be temporarily disabled for specific jobs (INSERT/UPDATE in safe mode).
    • Added setForeignKeyChecks function to programmatically enable or disable foreign key checks.
    • Modified executeBatchJobs to conditionally disable and re-enable foreign key checks around batch execution based on job requirements.
    • Updated genSQLs to conditionally generate DELETE statements for RowChangeUpdate in safe mode, based on whether primary or unique keys are updated.
  • dm/syncer/dml_worker_test.go
    • Imported necessary packages for advanced testing, including context, database/sql/driver, regexp, go-sqlmock, config, connpkg, tcontext, log, and dbconn.
    • Added a new test case to TestGenSQL for safe mode updates that do not modify primary/unique keys, expecting only a REPLACE statement.
    • Added TestShouldDisableForeignKeyChecksForJob to verify the logic for disabling foreign key checks for specific job types.
    • Added TestIsForeignKeyChecksEnabled to test the parsing of the foreign_key_checks session variable from configuration.
    • Added TestShouldDisableForeignKeyChecks to further validate the conditions under which foreign key checks should be disabled.
    • Added TestExecuteBatchJobsWithForeignKey to simulate batch job execution with foreign key checks, including mocking SQL interactions to verify SET SESSION foreign_key_checks commands.
  • dm/tests/all_mode/run.sh
    • Removed redundant table creation statements for dtest2.dtable2 and dtest4.dtable4.
  • dm/tests/run_group.sh
    • Added safe_mode_foreign_key to the G06 test group to include the new integration test.
  • dm/tests/safe_mode_foreign_key/conf/diff_config.toml
    • Added a new configuration file for the diff utility, specifying fk_demo.?* as target check tables for the foreign key test.
  • dm/tests/safe_mode_foreign_key/conf/dm-master.toml
    • Added a new configuration file for the DM master component specific to the foreign key test.
  • dm/tests/safe_mode_foreign_key/conf/dm-task.yaml
    • Added a new DM task configuration file, enabling safe-mode and explicitly setting foreign_key_checks: 1 in the target database session for the foreign key test.
  • dm/tests/safe_mode_foreign_key/conf/dm-worker1.toml
    • Added a new configuration file for the DM worker component specific to the foreign key test.
  • dm/tests/safe_mode_foreign_key/conf/source1.yaml
    • Added a new source configuration file for the foreign key integration test.
  • dm/tests/safe_mode_foreign_key/data/db1.increment.sql
    • Added SQL statements for incremental updates, including non-key updates, cascading deletes, and idempotent inserts, specifically designed to test foreign key behavior.
  • dm/tests/safe_mode_foreign_key/data/db1.prepare.sql
    • Added SQL statements to prepare the database schema with parent and child tables, including foreign key constraints with ON DELETE CASCADE for the test.
  • dm/tests/safe_mode_foreign_key/run.sh
    • Added a new integration test script to set up DM master/worker, start a task with foreign key checks enabled, perform DML operations, and verify data consistency using check_sync_diff.
  • dm/tests/shardddl1/run.sh
    • Commented out the DM_DML_EXECUTE_ERROR function call.
  • pkg/sqlmodel/reduce.go
    • Imported the timodel package.
    • Refactored IdentityValues to utilize a new helper function identityValuesByIndex.
    • Added IsPrimaryOrUniqueKeyUpdated method to determine if primary or unique key values have changed during an update operation.
    • Added identityValuesByIndex helper function to extract column values for a given index.
  • pkg/sqlmodel/reduce_test.go
    • Added TestIdentityUpdatedWithUniqueKeys to thoroughly test the new IsPrimaryOrUniqueKeyUpdated method with various update scenarios.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces an optimization for safe mode by avoiding redundant DELETE statements for non-key updates, and correctly handles foreign key constraints by temporarily disabling foreign_key_checks. The changes are well-structured and include comprehensive unit and integration tests. I've found a logic issue in the new IsPrimaryOrUniqueKeyUpdated function that could be improved for correctness and clarity. Overall, a great improvement.

Note: Security Review did not run due to the size of the PR.

Comment on lines +103 to +144
func (r *RowChange) IsPrimaryOrUniqueKeyUpdated() bool {
if r.tp != RowChangeUpdate {
return false
}

r.lazyInitWhereHandle()
identityUpdated := func(pre, post []interface{}) bool {
if len(pre) != len(post) {
// should not happen
return true
}
for i := range pre {
if pre[i] != post[i] {
return true
}
}
return false
}

if idx := r.whereHandle.UniqueNotNullIdx; idx != nil {
pre, post := r.identityValuesByIndex(idx)
if identityUpdated(pre, post) {
return true
}
} else {
if identityUpdated(r.preValues, r.postValues) {
return true
}
}

for _, idx := range r.whereHandle.UniqueIdxs {
if idx == nil || idx == r.whereHandle.UniqueNotNullIdx {
continue
}
pre, post := r.identityValuesByIndex(idx)
if identityUpdated(pre, post) {
return true
}
}

return false
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The logic in IsPrimaryOrUniqueKeyUpdated can be simplified and made more correct. The current implementation has a flaw: if a table only has nullable unique keys (UniqueNotNullIdx is nil), the else block at line 127 will treat any update as a key update by checking all columns. This prevents the optimization for such tables, as the loop at line 133 is never reached.

The logic should be to iterate through all available unique keys. If any key is updated, return true. If no unique keys exist, then any update is considered a key update.

I suggest simplifying the function as follows:

func (r *RowChange) IsPrimaryOrUniqueKeyUpdated() bool {
	if r.tp != RowChangeUpdate {
		return false
	}

	r.lazyInitWhereHandle()
	identityUpdated := func(pre, post []interface{}) bool {
		if len(pre) != len(post) {
			// should not happen
			return true
		}
		for i := range pre {
			if pre[i] != post[i] {
				return true
			}
		}
		return false
	}

	if len(r.whereHandle.UniqueIdxs) == 0 {
		return identityUpdated(r.preValues, r.postValues)
	}

	for _, idx := range r.whereHandle.UniqueIdxs {
		if idx == nil {
			continue
		}
		pre, post := r.identityValuesByIndex(idx)
		if identityUpdated(pre, post) {
			return true
		}
	}

	return false
}

@codecov
Copy link

codecov bot commented Mar 13, 2026

Codecov Report

❌ Patch coverage is 67.34694% with 48 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (release-8.5@aa5c32c). Learn more about missing BASE report.

Additional details and impacted files
Components Coverage Δ
cdc 57.5949% <0.0000%> (?)
dm 48.9347% <0.0000%> (?)
engine 50.7106% <0.0000%> (?)
Flag Coverage Δ
cdc 57.5949% <75.5555%> (?)
unit 53.4111% <67.3469%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

@@               Coverage Diff                @@
##             release-8.5     #12541   +/-   ##
================================================
  Coverage               ?   53.4111%           
================================================
  Files                  ?       1026           
  Lines                  ?     137507           
  Branches               ?          0           
================================================
  Hits                   ?      73444           
  Misses                 ?      58593           
  Partials               ?       5470           
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@OliverS929
Copy link
Contributor

/retest

@ti-chi-bot ti-chi-bot bot added the approved label Mar 13, 2026
@ti-chi-bot
Copy link
Contributor

ti-chi-bot bot commented Mar 13, 2026

@OliverS929: adding LGTM is restricted to approvers and reviewers in OWNERS files.

Details

In response to this:

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@ti-chi-bot
Copy link
Contributor

ti-chi-bot bot commented Mar 13, 2026

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: GMHDBJD, OliverS929

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@OliverS929
Copy link
Contributor

/retest

@OliverS929
Copy link
Contributor

/test pull-cdc-integration-storage-test

@OliverS929
Copy link
Contributor

/test pull-dm-compatibility-test

@OliverS929
Copy link
Contributor

/test pull-dm-integration-test

@OliverS929
Copy link
Contributor

/retest

3 similar comments
@OliverS929
Copy link
Contributor

/retest

@wuhuizuo
Copy link
Contributor

/retest

@wuhuizuo
Copy link
Contributor

/retest

@ti-chi-bot ti-chi-bot bot added cherry-pick-approved Cherry pick PR approved by release team. and removed do-not-merge/cherry-pick-not-approved labels Mar 16, 2026
@OliverS929
Copy link
Contributor

/retest

1 similar comment
@OliverS929
Copy link
Contributor

/retest

@ti-chi-bot ti-chi-bot bot merged commit 28fc694 into pingcap:release-8.5 Mar 16, 2026
27 checks passed
@ti-chi-bot ti-chi-bot bot deleted the cherry-pick-12351-to-release-8.5 branch March 16, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved area/dm Issues or PRs related to DM. cherry-pick-approved Cherry pick PR approved by release team. lgtm ok-to-test Indicates a PR is ready to be tested. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. type/cherry-pick-for-release-8.5 This PR is cherry-picked to release-8.5 from a source PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants