Subquery optimization

更新时间:
复制 MD 格式

PolarDB for MySQL automatically rewrites SQL statements at the optimizer stage to eliminate redundant subqueries and pre-compute constant ones. This reduces execution plan complexity and improves query performance — a common issue when Object-Relational Mapping (ORM) frameworks generate deeply nested queries.

Scope

  • Product series: Cluster Edition and Standard Edition

  • Kernel version: MySQL 8.0.2, revision 8.0.2.2.19 or later

Enable subquery optimization

Control subquery optimization with the loose_simplify_subq_mode parameter.

The parameter name differs depending on where you set it:

  • PolarDB console: Use the loose_ prefix (loose_simplify_subq_mode). Some parameters carry this prefix for compatibility with MySQL configuration files.

  • Database session (command line or client): Remove the loose_ prefix and use SET simplify_subq_mode = '...'.

ParameterLevelDescription
loose_simplify_subq_modeGlobal/SessionControls subquery optimization. Valid values: REPLICA_ON (default) — enables optimization on read-only (RO) nodes only; ON — enables the feature; OFF — disables optimization.

How it works

The optimizer applies three types of rewrites at the optimizer stage:

  1. Redundant SELECT elimination — strips unnecessary SELECT wrapper layers around aggregate functions or constant expressions.

  2. [NOT] EXISTS pre-evaluation — replaces EXISTS subqueries with TRUE or FALSE when the result can be determined without executing the subquery.

  3. ANY/ALL LIMIT injection — adds LIMIT 1 to constant-projection subqueries inside ANY or ALL clauses to avoid full table scans.

Each scenario below shows the exact rewrite the optimizer applies.

Optimization scenarios

Scenario 1: Eliminate redundant SELECT nesting

When it applies: The subquery wraps only an aggregate function or expression with no other complex logic.

Rewrite pattern:

-- Before
SELECT (SELECT SUM(a) FROM t2) FROM dual;

-- After
SELECT SUM(`test`.`t2`.`a`) AS `sum(a)` FROM `test`.`t2`

The same elimination applies in HAVING clauses, where the optimizer flattens multiple layers of nesting:

-- Before
SELECT SUM(a) FROM t2 HAVING (SELECT(SELECT(SELECT count(b))));

-- After
SELECT SUM(`testdb`.`t2`.`a`) AS `SUM(a)` from `testdb`.`t2` HAVING (0 <> count(`testdb`.`t2`.`b`))

Verify with an example:

  1. Prepare test data:

    DROP TABLE IF EXISTS t2;
    CREATE TABLE t2 (
        id INT PRIMARY KEY AUTO_INCREMENT,
        a INT,
        b INT
    );
    INSERT INTO t2 (a, b) VALUES (10, 100), (20, NULL), (50, 200), (120, NULL);
  2. Check the execution plan without optimization:

    SET simplify_subq_mode = 'OFF';
    EXPLAIN SELECT SUM(a) FROM t2 HAVING (SELECT(SELECT(SELECT count(b))));
    SHOW WARNINGS;

    Result — four nested SELECT layers remain:

    /* select#1 */ select sum(`testdb`.`t2`.`a`) AS `SUM(a)` from `testdb`.`t2` having (0 <> (/* select#2 */ select (/* select#3 */ select (/* select#4 */ select count(`testdb`.`t2`.`b`)))))
  3. Check the execution plan with optimization enabled:

    SET simplify_subq_mode = 'ON';
    EXPLAIN SELECT SUM(a) FROM t2 HAVING (SELECT(SELECT(SELECT count(b))));
    SHOW WARNINGS;

    Result — all nesting is eliminated:

    /* select#1 */ select sum(`testdb`.`t2`.`a`) AS `SUM(a)` from `testdb`.`t2` having (0 <> count(`testdb`.`t2`.`b`))

Scenario 2: Pre-evaluate [NOT] EXISTS subqueries

When it applies: The optimizer can statically determine that an EXISTS or NOT EXISTS subquery always returns true (non-empty) or false (empty).

Rewrite pattern:

-- Non-empty result: EXISTS clause is removed entirely
-- Before
SELECT * FROM t1 WHERE EXISTS(SELECT MAX(a) FROM t2);
-- After
SELECT * FROM t1

-- Empty result (WHERE/HAVING evaluates to false, or LIMIT 0): replaced with false
-- Before
SELECT * FROM t1 WHERE EXISTS(SELECT max(a) FROM t2 HAVING 1=2);
-- After
SELECT * FROM t1 WHERE false

This prevents the subquery from executing at all.

Verify with an example:

  1. Prepare test data:

    DROP TABLE IF EXISTS t1;
    DROP TABLE IF EXISTS t2;
    CREATE TABLE t1 (id INT);
    CREATE TABLE t2 (val INT);
    INSERT INTO t1 VALUES (1), (2);
  2. Check the execution plan without optimization:

    SET simplify_subq_mode = 'OFF';
    EXPLAIN SELECT * FROM t1 WHERE EXISTS(SELECT MAX(a) FROM t2);
    SHOW WARNINGS;

    Result — the EXISTS subquery is evaluated at runtime:

    /* select#1 */ select `testdb`.`t1`.`id` AS `id` from `testdb`.`t1` where exists(/* select#2 */ select max(`testdb`.`t1`.`id`) from `testdb`.`t2`)
  3. Check the execution plan with optimization enabled:

    SET simplify_subq_mode = 'ON';
    EXPLAIN SELECT * FROM t1 WHERE EXISTS(SELECT MAX(a) FROM t2);
    SHOW WARNINGS;

    Result — the EXISTS clause is gone:

    /* select#1 */ select `testdb`.`t1`.`id` AS `id` from `testdb`.`t1`

Scenario 3: Add LIMIT 1 to constant projections in ANY/ALL subqueries

When it applies: The subquery inside an ANY or ALL clause selects only a constant (no column references). Retrieving multiple identical rows is wasteful, so the optimizer injects LIMIT 1.

Rewrite pattern:

-- Before
SELECT * FROM t1 WHERE a > ANY (SELECT 1 FROM t2);

-- After
SELECT * FROM t1 WHERE a > ANY (SELECT 1 FROM t2 LIMIT 1);

This avoids a full table scan on t2 when the constant result is the same for every row.

Apply in production

Before enabling subquery optimization for critical workloads, follow these steps:

  • Run full regression tests in a staging environment first. In rare edge cases — such as queries that depend on a specific execution order or subquery execution count — the optimization may produce different behavior, even though logical equivalence is guaranteed in most cases.

  • Keep table statistics up to date. Scenario 2 depends on table statistics to infer whether a set is empty or non-empty. Run ANALYZE TABLE regularly so the optimizer makes accurate decisions.