PolarDB for MySQL uses cost-based query transformation (CBQT) to rewrite complex queries into equivalent forms with lower execution cost. Unlike rule-based transformations that apply unconditionally, CBQT evaluates multiple rewriting strategies and selects the one with the lowest estimated cost—taking into account cardinality, access methods, and join order.
Prerequisites
Before using CBQT, verify your cluster version:
Old version of CBQT: PolarDB for MySQL 8.0.2, revision 8.0.2.2.0 or later
New version of CBQT: PolarDB for MySQL 8.0.2, revision 8.0.2.2.19 or later
To check your revision version, see Query the engine version.
How it works
Query transformation rewrites one SQL statement into an equivalent SQL statement. For example:
SELECT *
FROM d1
JOIN f1 ON d1.c1 = f1.c1
LEFT JOIN (
SELECT d2.c2 AS d2_c2, f2.c3 AS f2_c3
FROM d2, f2
WHERE d2.c1 = f2.c1
) derived
ON derived.d2_c2 = d1.c2
AND derived.f2_c3 = f1.c3;After materialized table merging, this becomes:
SELECT *
FROM d1
JOIN f1 ON d1.c1 = f1.c1
LEFT JOIN (d2
JOIN f2 ON TRUE)
ON d2.c1 = f2.c1
AND f2.c3 = f1.c3
AND d2.c2 = d1.c2;MySQL Community Edition applies this transformation unconditionally based on equivalence rules—without checking whether it actually reduces cost. If the indexes of d1/f1 and d2/f2 are not correlated, merging forces each row from the d1-f1 join to trigger a separate d2-f2 join, which can significantly increase execution time.
CBQT solves this by building a state space of all possible transformation combinations before committing to any. For two candidate transformations A and B, the state space contains four combinations: None, A only, B only, and AB. CBQT estimates the execution cost of each combination and selects the lowest-cost plan.
For the materialized table merging example, the execution plan after merging looks like this:
The execution plan before merging looks like this:
Whether the merged plan is better depends on whether the indexes of d2/f2 and d1/f1 are correlated, and on the size of the join result set. CBQT calculates both costs and applies the transformation only if it reduces cost.
Transformations that always reduce cost are treated as rule-based transformations and applied whenever the relevant conditions are met.
Key concepts
Nested query and nesting depth
A nested query is a query block nested inside another query. The inner query block is the subquery (also called the inner query); the enclosing block is the parent query (also called the outer query). Nesting depth counts the number of nested layers.
Example with nesting depth 2:
SELECT * FROM t1 WHERE t1.a IN (SELECT t2.b FROM t2 WHERE t2.c = t1.b);The UNION operator combines statements at the same nesting level. The following query has nesting depth 3:
SELECT *
FROM t1
WHERE t1.a IN (
SELECT dt.b
FROM (
SELECT b, c FROM t2
UNION
SELECT b, c FROM t3
) dt
WHERE dt.c = t1.b
);Transformation
The process of rewriting a query into an equivalent form, such as the materialized table merging shown above.
Transformation object
The query element that a transformation rule applies to. For materialized table merging, the transformation object is the materialized table. For the subquery-to-SEMI JOIN transformation, it is the subquery.
Iteration
Each pass over all transformation objects is one iteration. After a transformation is applied, new objects may become eligible for further transformation—for example, a subquery that becomes a materialized table can then be merged. CBQT repeats until no new transformations are possible or until the iteration limit is reached.
Enable and configure CBQT
Two parameters control CBQT globally:
| Parameter | Level | Default | Description |
|---|---|---|---|
cbqt_enabled | Global and session | ON | Enables or disables CBQT. Valid values: ON, OFF, REPLICA_ON (enables CBQT on read-only nodes only) |
cbqt_version | Global and session | 1 | Selects the CBQT version. 1 = old version, 2 = new version |
Old version of CBQT
The old version is available from revision 8.0.2.2.0.
CBQT can take time to evaluate all combinations in the state space. To protect short-lived queries, set a cost threshold: CBQT only activates when a query's estimated cost exceeds cbqt_cost_threshold.
For correlated subqueries that MySQL Community Edition does not support unnesting, the old version decorrelates them using GROUP BY clauses.
For transformations that MySQL Community Edition applies unconditionally, you can opt into cost-based evaluation. The derived merge feature is controlled by the derived_merge_cost_based flag in polar_optimizer_switch.
Parameters
| Parameter | Level | Default | Description |
|---|---|---|---|
cbqt_cost_threshold | Global and session | 100000 | The minimum query cost that triggers CBQT. CBQT is skipped for queries with lower estimated costs. Valid values: 0–18446744073709551615 |
cbqt_timeout | Global and session | 200 ms | The maximum time CBQT spends searching for the optimal plan. If the timeout expires, CBQT uses the best plan found so far. Set to 0 to disable the timeout. Valid values: 0–18446744073709551615 |
polar_optimizer_switch | Global and session | — | A set of flags that control individual optimization features. See the following table for valid flags |
`polar_optimizer_switch` flags:
| Flag | Default | Description |
|---|---|---|
unnest_use_window_function | ON | Decorrelates subqueries using window functions |
unnest_use_group_by | ON | Decorrelates subqueries using GROUP BY clauses (cost-based) |
derived_merge_cost_based | OFF | Applies derived merge based on cost rather than unconditionally |
New version of CBQT
The new version is available from revision 8.0.2.2.19 and introduces finer-grained control over iteration count, nesting depth, search strategy, and individual transformation rules.
Parameters
| Parameter | Level | Default | Description |
|---|---|---|---|
cbqt_iteration_limit | Global | 1 | The maximum number of CBQT iterations. Higher values increase the chance of finding the optimal plan but consume more time. Valid values: 1–10 |
cbqt_max_nested_level | Global and session | 5 | The maximum nesting depth CBQT processes. Queries deeper than this value are not transformed. Valid values: 1–64 |
cbqt_search_strategy | Global | auto | The strategy for selecting the optimal plan. See the following table for valid values |
cbqt_rule_switch | Global and session | — | A set of flags that enable or disable individual transformation rules. See the following table for valid flags |
`cbqt_search_strategy` values:
| Value | Description |
|---|---|
auto | Adaptive search (default). PolarDB selects linear or twoPass based on the query |
linear | Compares execution cost before and after each transformation independently and keeps the better plan |
twoPass | Compares the cost of applying all transformations uniformly versus applying none, then selects the better approach |
`cbqt_rule_switch` flags (all default `ON`):
| Flag | Description |
|---|---|
merge_derived | Cost-based materialized table merging |
subquery_to_derived | Cost-based transformation from subquery to materialized table |
unnest_subquery_by_groupby | Cost-based decorrelation using GROUP BY clauses |
unnest_subquery_by_windows | Decorrelation using window functions |
derived_projection_pruning | Materialized table projection pruning |
cond_pushdown | Condition pushdown |
heuristic_merge_derived | Heuristic materialized table merging |
subquery_to_semijoin | Transformation from subquery to SEMI JOIN |
heuristic_coalesce_subquery | Heuristic subquery folding |
coalesce_subquery | Cost-based subquery folding |