Skip to content

planner, expression: reuse ODKU expressions#69936

Open
windtalker wants to merge 8 commits into
pingcap:feature/release-8.5-materialized-viewfrom
windtalker:cse_for_odku
Open

planner, expression: reuse ODKU expressions#69936
windtalker wants to merge 8 commits into
pingcap:feature/release-8.5-materialized-viewfrom
windtalker:cse_for_odku

Conversation

@windtalker

@windtalker windtalker commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: ref #65003

Problem Summary:

INSERT ... ON DUPLICATE KEY UPDATE statements with many repeated assignment expressions can spend excessive CPU time and memory allocation in the planner compile path. In large ODKU statements, common sub-expressions are repeatedly rewritten, resolved, and cloned even when they are semantically identical across assignments.

What changed and how does it work?

This PR optimizes the planner compile path for repeated ODKU expressions:

  • Add tidb_enable_on_duplicate_expression_reuse to control the optimization, including SET_VAR support.
  • Reuse eligible repeated ODKU AST sub-expressions during expression rewrite.
  • Keep the optimization guarded by a complexity gate, function-like node filtering, unsafe expression filtering, and a depth guard to avoid spending extra work on simple or overly deep expressions.
  • Preserve expression sharing during ResolveIndices with a memoized resolve path.
  • Add explicit CloneWithArgs support for scalar functions to avoid deep-cloning already shared child expressions during memoized resolve.
  • Include the new switch in the plan cache key.
  • Add unit tests, planner benchmark coverage, and a design/benchmark note under docs/note.

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No need to test
    • I checked and no code files have been changed.

Manual test:

tools/bin/failpoint-ctl enable pkg/planner/core && GOCACHE=/tmp/go-build go test ./pkg/planner/core -run '^$' -bench '^BenchmarkInsertOnDuplicateUpdateCompile' -benchmem -tags=intest,deadlock -count=5; rc=$?; tools/bin/failpoint-ctl disable pkg/planner/core; exit $rc
GOCACHE=/tmp/go-build make bazel_lint_changed

Side effects

  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Breaking backward compatibility

Documentation

  • Affects user behaviors
  • Contains syntax changes
  • Contains variable changes
  • Contains experimental features
  • Changes MySQL compatibility

Release note

Improve planner compilation performance for INSERT ... ON DUPLICATE KEY UPDATE statements with many repeated expressions.

Summary by CodeRabbit

  • 新功能 / 性能优化
    • 在满足条件时优化 INSERT ... ON DUPLICATE KEY UPDATE:复用公共子表达式,降低编译开销;并提供复杂度门控避免不适用场景。
  • 配置
    • 新增并提供开关 tidb_enable_on_duplicate_expression_reuse(默认开启),支持会话/全局/语句提示;启用/禁用会影响计划缓存键。
  • 文档
    • 补充机制说明、适用范围、收益边界与注意事项。
  • Bug Fixes
    • 修复区间表达式克隆时可空属性未正确保留的问题。
  • 测试
    • 增加表达式复用/门控、计划缓存键差异与跨表/跨库场景正确性测试;补充编译基准与集成用例。

@ti-chi-bot ti-chi-bot Bot added the release-note Denotes a PR that will be considered when it comes time to generate release notes. label Jul 19, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jul 19, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign 0xpoe, yudongusa, zanmato1984 for approval. For more information see the Code Review Process.
Please ensure that each of them provides their approval before proceeding.

The full list of commands accepted by this bot can be found 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

@ti-chi-bot ti-chi-bot Bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. sig/planner SIG: Planner labels Jul 19, 2026
@coderabbitai

coderabbitai Bot commented Jul 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 14771d4f-f63e-499a-a4ce-fd35f52c06d9

📥 Commits

Reviewing files that changed from the base of the PR and between 3991024 and 9346a98.

📒 Files selected for processing (3)
  • pkg/planner/core/expression_rewriter.go
  • pkg/planner/core/resolve_indices.go
  • pkg/sessionctx/variable/tidb_vars.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/planner/core/resolve_indices.go
  • pkg/sessionctx/variable/tidb_vars.go
  • pkg/planner/core/expression_rewriter.go

📝 Walkthrough

Walkthrough

Adds a default-enabled tidb_enable_on_duplicate_expression_reuse setting and plan-cache integration. Planner rewriting and index resolution now memoize eligible ON DUPLICATE expressions, while CloneWithArgs preserves shared subtrees across builtin signatures. Tests, benchmarks, and documentation cover the behavior.

Changes

ON DUPLICATE expression reuse

Layer / File(s) Summary
Reuse setting and plan cache integration
pkg/sessionctx/variable/*, pkg/planner/core/plan_cache_*, pkg/planner/core/common_plans.go
Adds the session/global setting, hint support, default value, plan-cache key component, and plan metadata flag.
Expression clone support
pkg/expression/builtin.go, pkg/expression/builtin_clone_with_args.go, pkg/expression/builtin_regexp.go, pkg/expression/scalar_function.go, pkg/expression/*test.go
Adds CloneWithArgs across builtin signatures and ScalarFunction, preserving signature state while replacing arguments.
Rewrite memoization and gating
pkg/planner/core/planbuilder.go, pkg/planner/core/expression_rewriter.go, pkg/planner/core/planbuilder_test.go
Enables memoization when expression complexity passes the threshold, generates restore-based keys, filters unsafe expressions, and reuses rewritten subtrees.
Memoized index resolution and validation
pkg/planner/core/resolve_indices.go, pkg/planner/core/*test.go, docs/note/*
Reuses resolved expressions with schema-specific memos and adds tests, benchmarks, integration coverage, implementation notes, and documented behavior boundaries.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SessionVars
  participant PlanBuilder
  participant ExpressionRewriter
  participant InsertResolveIndices
  SessionVars->>PlanBuilder: Enable ON DUPLICATE expression reuse
  PlanBuilder->>ExpressionRewriter: Rewrite assignments with memo
  ExpressionRewriter->>ExpressionRewriter: Reuse eligible rewritten subexpressions
  PlanBuilder->>InsertResolveIndices: Resolve rewritten expressions
  InsertResolveIndices->>InsertResolveIndices: Reuse resolved expression trees
Loading

Possibly related PRs

  • pingcap/tidb#69165: Both changes modify plan-cache key construction and builtin cloning behavior.

Suggested reviewers: qw4990, yudongusa

Poem

A rabbit hops through memoed code,
Sharing roots along the road.
Builtins clone with args anew,
Plans keep cache keys in view.
Carrots cheer the compiler crew!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and clearly describes the main change: reusing ODKU expressions in planner/expression code.
Description check ✅ Passed The PR description matches the template and covers issue reference, problem summary, implementation, checklist, and release note.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

Error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions
The command is terminated due to an error: can't load config: unsupported version of the configuration: "" See https://golangci-lint.run/docs/product/migration-guide for migration instructions


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/expression/builtin_clone_with_args.go`:
- Around line 637-641: Update builtinIntervalIntSig.CloneWithArgs to copy the
source instance’s hasNullable value onto newSig after cloning the base function,
matching builtinIntervalRealSig.CloneWithArgs so cloned INTERVAL expressions
preserve nullable evaluation behavior.

In `@pkg/sessionctx/variable/tidb_vars.go`:
- Line 1441: Add a Go doc comment immediately before
DefTiDBEnableOnDuplicateExpressionReuse that begins with the constant name and
documents its purpose as the default setting for enabling ON DUPLICATE
expression reuse.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a5d9b9ca-d353-4add-872b-16b099af1afe

📥 Commits

Reviewing files that changed from the base of the PR and between 6910cef and e238b04.

📒 Files selected for processing (19)
  • docs/note/insert_on_duplicate_update_expression_reuse_summary.md
  • pkg/expression/BUILD.bazel
  • pkg/expression/builtin.go
  • pkg/expression/builtin_clone_with_args.go
  • pkg/expression/builtin_regexp.go
  • pkg/expression/builtin_vectorized_test.go
  • pkg/expression/scalar_function.go
  • pkg/expression/scalar_function_test.go
  • pkg/planner/core/common_plans.go
  • pkg/planner/core/expression_rewriter.go
  • pkg/planner/core/plan_cache_test.go
  • pkg/planner/core/plan_cache_utils.go
  • pkg/planner/core/planbuilder.go
  • pkg/planner/core/planbuilder_test.go
  • pkg/planner/core/resolve_indices.go
  • pkg/sessionctx/variable/session.go
  • pkg/sessionctx/variable/setvar_affect.go
  • pkg/sessionctx/variable/sysvar.go
  • pkg/sessionctx/variable/tidb_vars.go

Comment thread pkg/expression/builtin_clone_with_args.go
Comment thread pkg/sessionctx/variable/tidb_vars.go
@codecov

codecov Bot commented Jul 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.83582% with 46 lines in your changes missing coverage. Please review.
✅ Project coverage is 57.1888%. Comparing base (a633ea3) to head (9346a98).
⚠️ Report is 46 commits behind head on feature/release-8.5-materialized-view.

Additional details and impacted files
@@                              Coverage Diff                              @@
##           feature/release-8.5-materialized-view     #69936        +/-   ##
=============================================================================
- Coverage                                57.7421%   57.1888%   -0.5533%     
=============================================================================
  Files                                       1815       1817         +2     
  Lines                                     659304     668823      +9519     
=============================================================================
+ Hits                                      380696     382492      +1796     
- Misses                                    252162     259783      +7621     
- Partials                                   26446      26548       +102     
Flag Coverage Δ
integration 36.3481% <25.7462%> (-1.2634%) ⬇️
unit 71.6585% <82.8358%> (-0.3384%) ⬇️

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

Components Coverage Δ
dumpling 52.9278% <ø> (ø)
parser ∅ <ø> (∅)
br 52.7293% <ø> (-10.6979%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@windtalker

Copy link
Copy Markdown
Contributor Author

/test unit-test

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
pkg/planner/core/planbuilder.go (1)

4870-4872: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Abort the AST traversal once the threshold is reached.

While Enter correctly stops descending into child nodes by returning true for skipChildren, returning true from Leave instructs the visitor to continue processing siblings and the rest of the AST horizontally.

You can completely short-circuit and terminate the entire AST walk immediately once the threshold is met by returning false from Leave.

💡 Proposed change to abort the visitor
-func (*onDuplicateFunctionCounter) Leave(in ast.Node) (ast.Node, bool) {
-	return in, true
+func (c *onDuplicateFunctionCounter) Leave(in ast.Node) (ast.Node, bool) {
+	return in, c.count < minOnDuplicateFunctionCountForExpressionReuse
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/planner/core/planbuilder.go` around lines 4870 - 4872, Update
onDuplicateFunctionCounter.Leave to return false instead of true, so AST
traversal terminates immediately when the duplicate-function threshold has been
reached. Leave Enter and the node return value unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@pkg/planner/core/planbuilder.go`:
- Around line 4870-4872: Update onDuplicateFunctionCounter.Leave to return false
instead of true, so AST traversal terminates immediately when the
duplicate-function threshold has been reached. Leave Enter and the node return
value unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 4ad22610-025e-4ce3-be1a-cf7fbc0b6287

📥 Commits

Reviewing files that changed from the base of the PR and between e74eef4 and 3991024.

📒 Files selected for processing (22)
  • docs/note/insert_on_duplicate_update_expression_reuse_summary.md
  • pkg/expression/BUILD.bazel
  • pkg/expression/builtin.go
  • pkg/expression/builtin_clone_with_args.go
  • pkg/expression/builtin_compare.go
  • pkg/expression/builtin_compare_test.go
  • pkg/expression/builtin_regexp.go
  • pkg/expression/builtin_vectorized_test.go
  • pkg/expression/scalar_function.go
  • pkg/expression/scalar_function_test.go
  • pkg/planner/core/common_plans.go
  • pkg/planner/core/expression_rewriter.go
  • pkg/planner/core/integration_test.go
  • pkg/planner/core/plan_cache_test.go
  • pkg/planner/core/plan_cache_utils.go
  • pkg/planner/core/planbuilder.go
  • pkg/planner/core/planbuilder_test.go
  • pkg/planner/core/resolve_indices.go
  • pkg/sessionctx/variable/session.go
  • pkg/sessionctx/variable/setvar_affect.go
  • pkg/sessionctx/variable/sysvar.go
  • pkg/sessionctx/variable/tidb_vars.go
🚧 Files skipped from review as they are similar to previous changes (15)
  • pkg/sessionctx/variable/tidb_vars.go
  • pkg/sessionctx/variable/session.go
  • pkg/sessionctx/variable/setvar_affect.go
  • pkg/planner/core/plan_cache_test.go
  • pkg/expression/builtin_regexp.go
  • pkg/expression/scalar_function.go
  • pkg/sessionctx/variable/sysvar.go
  • pkg/planner/core/common_plans.go
  • pkg/expression/builtin_vectorized_test.go
  • pkg/planner/core/plan_cache_utils.go
  • pkg/expression/builtin.go
  • pkg/planner/core/planbuilder_test.go
  • pkg/planner/core/integration_test.go
  • pkg/planner/core/expression_rewriter.go
  • pkg/expression/builtin_clone_with_args.go

Comment on lines +388 to +400
type onDuplicateExprMemo struct {
cache map[string]expression.Expression
frames []onDuplicateMemoFrame
restoreBuf bytes.Buffer
restoreCtx *format.RestoreCtx

keyableNodes map[ast.Node]struct{}
depthFrames []onDuplicateExprDepthFrame
}

const maxOnDuplicateExprReuseKeyDepth = 16

type onDuplicateMemoFrame struct {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you add some new comments for these newly added structures and their fields?

func (er *expressionRewriter) Enter(inNode ast.Node) (ast.Node, bool) {
if er.onDuplicateMemo != nil {
frame := onDuplicateMemoFrame{stackLen: er.ctxStackLen()}
if _, ok := inNode.(*ast.SetCollationExpr); ok {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do we need to handle setcollation specially here? Please add some comments especially some examples..

Comment thread pkg/planner/core/resolve_indices.go Outdated
return err
}
if !p.isOnDuplicateExpressionReuseEnabled() {
for _, asgn := range p.OnDuplicate {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

L874-905 and L909-940 seem duplicated, could we merge them into one branch? For example, if !p.isOnDuplicateExpressionReuseEnabled() just set these 2 memo to nil, and let resolveExprIndicesWithMemo hand these 2 scenarios seperately.

@windtalker

Copy link
Copy Markdown
Contributor Author

/test check-dev2

1 similar comment
@windtalker

Copy link
Copy Markdown
Contributor Author

/test check-dev2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note Denotes a PR that will be considered when it comes time to generate release notes. sig/planner SIG: Planner size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants