Skip to content

planner, sessionctx: enable non-eval scalar subquery explain by default - #68566

Open
hawkingrei wants to merge 6 commits into
pingcap:masterfrom
hawkingrei:issue-66679-explain-subquery
Open

hawkingrei wants to merge 6 commits into
pingcap:masterfrom
hawkingrei:issue-66679-explain-subquery

Conversation

@hawkingrei

@hawkingrei hawkingrei commented May 21, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: close #66679

Problem Summary:

EXPLAIN can currently evaluate scalar subqueries before producing the plan unless tidb_opt_enable_non_eval_scalar_subquery is enabled explicitly. That can make EXPLAIN slow for heavy scalar subqueries, while the generated plan hides the subquery work that caused the delay.

What changed and how does it work?

This PR enables tidb_opt_enable_non_eval_scalar_subquery by default.

With the default enabled, plain EXPLAIN keeps scalar subqueries visible in the displayed plan instead of pre-evaluating them during preprocessing. The regression test also checks that EXPLAIN ... (select sleep(0.3)) does not increase RewritePhaseInfo.PreprocessSubQueries, while both ScalarSubQuery and sleep(0.3) remain visible in the plan.

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.

Commands:

go test -tags=intest,deadlock ./pkg/planner/core/casetest/scalarsubquery -run '^TestExplainNonEvaledSubquery$' -count=1
go test -tags=intest,deadlock ./pkg/planner/indexadvisor -run '^TestIndexAdvisorFixControl43817$' -count=1
./tools/check/failpoint-go-test.sh pkg/planner/core/casetest/rule -run '^TestCorrelate$' -count=1
go test -tags=intest,deadlock ./pkg/sessionctx/vardef -count=1
go test -tags=intest,deadlock ./pkg/sessionctx/variable/tests -run '^TestDefaultValuesAreSettable$' -count=1
(cd tests/integrationtest && ./run-tests.sh -r tpch)
./tools/check/failpoint-go-test.sh pkg/planner/core/casetest/windows -run '^TestWindowSubqueryOuterRef$' -count=1
go test -tags=intest,deadlock ./pkg/planner/core/casetest/logicalplan -run '^TestGroupBySchema$' -count=1
(cd tests/integrationtest && ./run-tests.sh -r expression/time)
make server SERVER_OUT=tests/integrationtest/integrationtest_tidb-server-norace
(cd tests/integrationtest && ./run-tests.sh -s ./integrationtest_tidb-server-norace -r index_merge)
(cd tests/integrationtest && ./run-tests.sh -s ./integrationtest_tidb-server-norace -r executor/issues)
(cd tests/integrationtest && ./run-tests.sh -s ./integrationtest_tidb-server-norace -r planner/core/casetest/rule/rule_join_reorder)
./tools/check/failpoint-go-test.sh pkg/planner/core/issuetest -run '^TestPlannerIssueRegressions$' -count=1
git diff --check
make lint

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

Please refer to Release Notes Language Style Guide to write a quality release note.

Enable EXPLAIN to show scalar subquery plans without evaluating those subqueries by default.

Summary by CodeRabbit

  • Bug Fixes

    • EXPLAIN output no longer embeds evaluated scalar-subquery results when showing non-evaluated plans, preserving clearer plan structure.
  • Behavior

    • Default EXPLAIN/display behavior changed to present scalar subqueries in their non-evaluated form by default for more consistent plan trees.
  • Tests

    • Many tests and golden outputs updated to toggle the session EXPLAIN mode and match revised EXPLAIN formatting and counters.

Review Change Stack

@ti-chi-bot ti-chi-bot Bot added release-note Denotes a PR that will be considered when it comes time to generate release notes. size/S Denotes a PR that changes 10-29 lines, ignoring generated files. sig/planner SIG: Planner labels May 21, 2026
@coderabbitai

coderabbitai Bot commented May 21, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Flip EXPLAIN-evaluated-subquery default to show non-eval scalar subqueries, add a centralized EXPLAIN gating helper in the rewriter, snapshot/restore the EXPLAIN flag in QueryPlanCost, and update unit/integration tests and golden outputs accordingly.

Changes

Scalar Subquery Explanation Default

Layer / File(s) Summary
System variable default and rewriter
pkg/sessionctx/vardef/tidb_vars.go, pkg/planner/core/expression_rewriter.go
Change DefTiDBOptExplainEvaledSubquery default to true; add shouldExplainNonEvaledScalarSubQuery helper and force EXPLAIN path ScalarSubQuery RetType to mysql.TypeTiny with numeric coercibility.
Unit tests and plan expectations
pkg/planner/core/casetest/scalarsubquery/cases_test.go, pkg/planner/core/casetest/rule/rule_correlate_test.go, pkg/planner/core/casetest/logicalplan/logical_plan_builder_test.go, pkg/planner/core/casetest/windows/testdata/*
Update TestExplainNonEvaledSubquery to use substring helpers, read/toggle @@tidb_opt_enable_non_eval_scalar_subquery, reset RewritePhaseInfo.PreprocessSubQueries, and adjust expected EXPLAIN outputs; adjust correlate test and plan-tree/golden expectations.
Index advisor / QueryPlanCost EXPLAIN behavior
pkg/planner/indexadvisor/optimizer.go
QueryPlanCost snapshots/restores ExplainNonEvaledSubQuery and forces it to false while InExplainStmt = true during cost computation.
Integration test session toggles and expected outputs
tests/integrationtest/..., tests/integrationtest/t/..., tests/integrationtest/r/...
Add SET @@session.tidb_opt_enable_non_eval_scalar_subquery = 0;/=1 toggles across many integration and SQL test scripts and expected-result files to make EXPLAIN outputs deterministic for the changed behavior.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SessionVars
  participant ExpressionRewriter
  participant Planner
  participant IndexAdvisor
  Client->>SessionVars: SET/GET @@tidb_opt_enable_non_eval_scalar_subquery
  Client->>Planner: EXPLAIN format='plan_tree' query(with scalar subquery)
  Planner->>ExpressionRewriter: shouldExplainNonEvaledScalarSubQuery(...)
  ExpressionRewriter-->>Planner: register non-eval ScalarSubQuery (RetType=mysql.TypeTiny)
  Planner->>IndexAdvisor: QueryPlanCost (InExplainStmt=true, ExplainNonEvaledSubQuery=false)
  IndexAdvisor-->>Planner: cost snapshot/restore
  Planner-->>Client: plan rows (contains ScalarSubQuery / outputs)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • pingcap/tidb#66869: Updates window push-down expected plan JSONs related to ScalarSubQuery output wiring.
  • pingcap/tidb#67479: Adjusts planner_issue_test.go EXPLAIN assertions for row-in-subquery-with-exists and ScalarSubQuery output.

Suggested labels

size/M, compatibility-breaker, ok-to-test

Suggested reviewers

  • qw4990
  • terry1purcell
  • AilinKid

Poem

🐰 I hopped through plans where subqueries slept,
I checked the flags and gently leapt.
Tests now match strings and goldens align,
EXPLAIN wakes the branches in tidy design.
A little carrot, a tiny TypeTiny sign.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately describes the main change: enabling the non-eval scalar subquery explain feature by default.
Description check ✅ Passed The description follows the template with all required sections: issue number, problem summary, what changed, test checkboxes, side effects, and documentation notes with release note.
Linked Issues check ✅ Passed The PR directly addresses issue #66679 by implementing the required fix: enabling non-eval scalar subquery explain by default to prevent EXPLAIN from pre-evaluating heavy scalar subqueries and keeping subquery plans visible.
Out of Scope Changes check ✅ Passed All changes are in-scope: code modifications to enable the feature, test updates to verify behavior with the new default, and session configuration updates in integration tests to maintain expected behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@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/casetest/scalarsubquery/cases_test.go (1)

49-61: ⚡ Quick win

Consider adding a negative test case for completeness.

The test validates that with the new default (variable = 1), scalar subqueries are not evaluated during EXPLAIN. For comprehensive regression coverage, consider adding a test case that explicitly sets @@tidb_opt_enable_non_eval_scalar_subquery=0 and verifies that:

  • PreprocessSubQueries is > 0 (subquery was evaluated)
  • The plan output does NOT contain "ScalarSubQuery" or "sleep(0.3)" (subquery was replaced with its result)

This would ensure both behaviors are tested and prevent future regressions if the variable semantics change.

📋 Optional: Add negative test case
 	require.True(t, hasScalarSubQuery, "plan should keep the scalar subquery visible: %v", planRows)
 	require.True(t, hasSleepFunc, "plan should keep sleep(0.3) visible: %v", planRows)
+
+	// Verify the old behavior when variable is explicitly disabled
+	testKit.MustExec("set @@tidb_opt_enable_non_eval_scalar_subquery=0")
+	testKit.Session().GetSessionVars().RewritePhaseInfo.Reset()
+	rows = testKit.MustQuery("explain format = 'plan_tree' select * from t1 where a = (select sleep(0.3))").Rows()
+	require.Greater(t, testKit.Session().GetSessionVars().RewritePhaseInfo.PreprocessSubQueries, 0, "with variable disabled, scalar subquery should be evaluated")
🤖 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/casetest/scalarsubquery/cases_test.go` around lines 49 - 61,
Add a negative test: set @@tidb_opt_enable_non_eval_scalar_subquery=0 via
testKit.MustExec, reset session RewritePhaseInfo
(testKit.Session().GetSessionVars().RewritePhaseInfo.Reset()), run explain
format='plan_tree' on the same query used here (select * from t1 where a =
(select sleep(0.3))), then assert RewritePhaseInfo.PreprocessSubQueries is > 0
and verify the planRows (from testdata.ConvertRowsToStrings(rows)) do NOT
contain "ScalarSubQuery" nor "sleep(0.3)"; use the same boolean checks
(hasScalarSubQuery, hasSleepFunc) but expect false and include the planRows in
failure messages for debugging.
🤖 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/casetest/scalarsubquery/cases_test.go`:
- Around line 49-61: Add a negative test: set
@@tidb_opt_enable_non_eval_scalar_subquery=0 via testKit.MustExec, reset session
RewritePhaseInfo (testKit.Session().GetSessionVars().RewritePhaseInfo.Reset()),
run explain format='plan_tree' on the same query used here (select * from t1
where a = (select sleep(0.3))), then assert
RewritePhaseInfo.PreprocessSubQueries is > 0 and verify the planRows (from
testdata.ConvertRowsToStrings(rows)) do NOT contain "ScalarSubQuery" nor
"sleep(0.3)"; use the same boolean checks (hasScalarSubQuery, hasSleepFunc) but
expect false and include the planRows in failure messages for debugging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8859146e-ec97-4d1b-a299-1fe885023a0b

📥 Commits

Reviewing files that changed from the base of the PR and between 7acf1fa and 8205ed0.

📒 Files selected for processing (2)
  • pkg/planner/core/casetest/scalarsubquery/cases_test.go
  • pkg/sessionctx/vardef/tidb_vars.go

@ti-chi-bot ti-chi-bot Bot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed size/S Denotes a PR that changes 10-29 lines, ignoring generated files. labels May 21, 2026
@ti-chi-bot

ti-chi-bot Bot commented May 21, 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 terry1purcell 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/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels May 21, 2026
@codecov

codecov Bot commented May 21, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 46.15385% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.4307%. Comparing base (7acf1fa) to head (8d97fbf).

Additional details and impacted files
@@               Coverage Diff                @@
##             master     #68566        +/-   ##
================================================
- Coverage   76.3753%   75.4307%   -0.9447%     
================================================
  Files          2039       2021        -18     
  Lines        562887     566065      +3178     
================================================
- Hits         429907     426987      -2920     
- Misses       132062     139075      +7013     
+ Partials        918          3       -915     
Flag Coverage Δ
integration 41.4442% <46.1538%> (+1.6731%) ⬆️

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

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

@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/casetest/scalarsubquery/cases_test.go (1)

89-91: ⚡ Quick win

Tighten the “outer TableDual” assertion to avoid false positives.

The current check passes if any row contains TableDual; that can match an inner node and miss a root-shape regression. Assert on the root/first explain row (or exact expected id position) instead.

🤖 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/casetest/scalarsubquery/cases_test.go` around lines 89 - 91,
The test currently looks for "TableDual" anywhere in the explain output via
planContains(rows, ...), which can match inner nodes; instead assert the
root/first explain row contains "TableDual". Change the assertion to inspect
only the first explain row (use rows[0] or call planContains on a single-row
slice) and require that that first row includes "TableDual" (replace
require.True on hasOuterTableDual with an assertion that rows[0] contains
"TableDual"); keep references to rows, planContains, hasOuterTableDual and
planRows to locate and update the check.
🤖 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/casetest/scalarsubquery/cases_test.go`:
- Around line 89-91: The test currently looks for "TableDual" anywhere in the
explain output via planContains(rows, ...), which can match inner nodes; instead
assert the root/first explain row contains "TableDual". Change the assertion to
inspect only the first explain row (use rows[0] or call planContains on a
single-row slice) and require that that first row includes "TableDual" (replace
require.True on hasOuterTableDual with an assertion that rows[0] contains
"TableDual"); keep references to rows, planContains, hasOuterTableDual and
planRows to locate and update the check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bd73d049-84ab-4462-9096-342b4ec329a3

📥 Commits

Reviewing files that changed from the base of the PR and between a4699c4 and 280ce8e.

📒 Files selected for processing (2)
  • pkg/planner/core/casetest/scalarsubquery/cases_test.go
  • pkg/planner/core/expression_rewriter.go

@hawkingrei

Copy link
Copy Markdown
Contributor Author

/test mysql-test

@tiprow

tiprow Bot commented May 21, 2026

Copy link
Copy Markdown

@hawkingrei: The specified target(s) for /test were not found.
The following commands are available to trigger required jobs:

/test fast_test_tiprow
/test tidb_parser_test

Use /test all to run all jobs.

Details

In response to this:

/test mysql-test

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.

@hawkingrei

Copy link
Copy Markdown
Contributor Author

/test check-dev2

@tiprow

tiprow Bot commented May 21, 2026

Copy link
Copy Markdown

@hawkingrei: The specified target(s) for /test were not found.
The following commands are available to trigger required jobs:

/test fast_test_tiprow
/test tidb_parser_test

Use /test all to run all jobs.

Details

In response to this:

/test check-dev2

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.

This branch has not been deployed

No deployments
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/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

EXPLAIN executes heavy subqueries, EXPLAIN ANALYZE shows nothing about them

1 participant