Left Join Elimination
Scenarios
In enterprise applications, ORM frameworks, view expansions, reporting queries, and generic SQL templates frequently generate complex SQL statements containing multiple LEFT JOIN clauses. The table on the right side of a LEFT JOIN is referenced only within the join condition, meaning that the query does not actually read any columns from that table. In other cases, rows from the left-hand table may undergo row amplification due to the join, but subsequent GROUP BY or DISTINCT clauses eliminate the amplification effect. When executing such queries, community MySQL still generates access paths and executes the join operations for these LEFT JOIN clauses. For complex queries, this introduces additional table scans, index lookups, join computations, and intermediate result processing, all of which degrade query performance.
TaurusDB supports left join elimination, allowing the optimizer to remove right-hand tables that do not affect the final result before generating the execution plan. This ensures that the query engine accesses only the tables required to produce the correct output, reducing CPU, I/O, and join execution overhead.
This optimization primarily covers three scenarios:
- Elimination based on GROUP BY or DISTINCT: If the right-hand table only amplifies the row count of the left-hand table, a subsequent GROUP BY or DISTINCT removes duplicates, and the query does not contain expressions (such as aggregate functions) that depend on the row count, the right-hand table can be removed.
- Elimination based on uniqueness: If the equality predicates in the ON condition cover all columns of a unique key on the right-hand table, each row from the left-hand table can match at most one row from the right-hand table and the right-hand table can be removed. Derived tables that produce unique rows through GROUP BY or DISTINCT are also treated as unique.
- Elimination based on subquery semantics (EXISTS/IN): When a LEFT JOIN appears inside an EXISTS, IN, NOT EXISTS, or NOT IN subquery and the right-hand table does not affect the boolean evaluation of that subquery, the right-hand table of the LEFT JOIN can be removed.
How It Works
Left join elimination is a semantic query-rewriting optimization based on equivalence rules of relational algebra. The optimizer identifies and removes redundant LEFT JOIN operations while guaranteeing that the final result set remains unchanged.
SELECT t1.a FROM t1 LEFT JOIN t2 ON t2.a = t1.b;
SELECT t1.a FROM t1;
Left join elimination occurs during the query resolver phase, after join structure simplification and join information collection. The optimizer first simplifies the join tree and then analyzes whether the right-hand table of a LEFT JOIN is referenced externally and whether it satisfies uniqueness or deduplication criteria. If the table qualifies, it is removed from the subsequent execution plan. Consequently, once this feature is enabled, eliminated tables will not appear in EXPLAIN outputs. You can inspect the join_elimination node and check the number of eliminated tables in the join_preparation step of an optimizer_trace.
Conversion Process
SELECT t1.a FROM t1 LEFT JOIN t2 ON t2.a > t1.b GROUP BY t1.a;
SELECT t1.a FROM t1 GROUP BY t1.a;
Because t2 appears only in the ON condition of the LEFT JOIN, the predicate t2.a > t1.b may cause a single row in t1 to match multiple rows in t2. However, since GROUP BY t1.a removes all duplicate rows introduced by t2, the optimizer can eliminate access to the t2 table.
Prerequisites
- The TaurusDB kernel version must be 2.0.75.260300 or later. For details about how to check the kernel version, see How Can I Check the Version of a TaurusDB Instance?
- left_join_elimination in the optimizer_switch system parameter must be ON. For details, see Modifying TaurusDB Instance Parameters.
- The SQL statement must contain LEFT JOIN or RIGHT JOIN and meet the conditions for left join elimination without affecting the final query results.
Supported Query Statements
- SELECT
- INSERT ... SELECT
- REPLACE ... SELECT
- CREATE TABLE ... SELECT
- Subqueries within any query statement
- Eligible queries inside views, derived tables, and prepared statements
Constraints
| Category | Item | Description |
|---|---|---|
| Supported join types | LEFT JOIN | Elimination is supported. |
| RIGHT JOIN | It is converted to an equivalent LEFT JOIN during the resolver phase and is eligible for elimination if criteria are met. | |
| Unsupported scenarios | INNER JOIN | Inner join elimination is not supported. |
| Multi-table UPDATE | Left join elimination is not supported in multi-table UPDATE statements. | |
| Multi-table DELETE | Left join elimination is not supported in multi-table DELETE statements. | |
| Field reference restrictions | SELECT | General rule: If any query projection column references a column from the right-hand table, elimination is not applied. Subquery exception: Inside EXISTS/IN subqueries, this restriction is bypassed. This is because EXISTS checks row existence and IN does not depend on right-hand column values. References here do not block elimination. |
| WHERE | General rule: If any filter predicate references a column from the right-hand table, elimination is not applied. Subquery exception: Inside EXISTS/IN subqueries, this restriction is bypassed. This is because EXISTS checks row existence and IN does not depend on right-hand column values. References here do not block elimination. | |
| GROUP BY | If columns of the right-hand table are referenced in the grouping columns, elimination is not applied. | |
| HAVING | If columns of the right-hand table are referenced in the post-aggregation filtering conditions, elimination is not applied. | |
| ORDER BY | If columns of the right-hand table are referenced in the sorting criteria, elimination is not applied. | |
| Window functions | If columns of the right-hand table are referenced in the definition or invocation of a window function, elimination is not applied. | |
| Other JOIN ON conditions | If columns of the right-hand table are referenced in the ON conditions of other joins, elimination is not applied. | |
| ON DUPLICATE KEY UPDATE | If columns of the right-hand table are referenced in write-conflict update clauses, elimination is not applied. | |
| DEFAULT(col) function | If columns of the right-hand table are referenced within the DEFAULT(col) function call, elimination is not applied. | |
| Aggregate functions | Aggregate functions are included. | Elimination is not applied under the GROUP BY or DISTINCT rules. |
| Safety criteria met | Elimination may still be applied if other safety constraints (such as uniqueness rules) are fully satisfied. | |
| DISTINCT + Window functions | Only DISTINCT is used for deduplication and window functions are included. | The right-hand table is not eliminated based on the DISTINCT rule. |
| GROUP BY also exists. | The optimizer continues to evaluate elimination using GROUP BY semantics. | |
| Uniqueness evaluation restrictions | OR conditions | Join predicates containing OR conditions are ineligible for elimination. |
| <=> Comparison | NULL-safe equality comparisons are ineligible for elimination. | |
| Non-deterministic expressions | Clauses containing non-deterministic functions, such as RAND(), are ineligible for elimination. | |
| String and non-string comparison | Comparisons where the right-hand side is a string column and the other side is non-string are ineligible for elimination. | |
| Collation mismatch | If both sides are strings but their collation does not match the right-hand column's collation, the predicate is ineligible for elimination. | |
| Other cases | Even if uniqueness-based elimination is skipped, the table may still be eliminated through GROUP BY/DISTINCT or EXISTS/IN subquery semantics. |
How to Use
You can manage left join elimination by configuring the optimizer_switch parameter.
| Parameter | Level | Description |
|---|---|---|
| optimizer_switch | Global, Session | The left join elimination feature is controlled through the left_join_elimination flag within the optimizer_switch parameter. To enable this feature, set left_join_elimination to on. To disable this feature, set the parameter to off. It is disabled by default. |
SET optimizer_switch='left_join_elimination=ON';
SET optimizer_switch='left_join_elimination=OFF';
Examples
CREATE TABLE t1(a INT PRIMARY KEY, b INT); CREATE TABLE t2(a INT PRIMARY KEY, b INT); INSERT INTO t1 VALUES(1,1),(2,3),(7,NULL); INSERT INTO t2 VALUES(2,2),(4,5),(5,4); ANALYZE TABLE t1,t2;
SET optimizer_switch='left_join_elimination=off'; EXPLAIN FORMAT=TREE SELECT t1.a FROM t1 LEFT JOIN t2 ON t2.a > t1.b GROUP BY t1.a\G
*************************** 1. row ***************************
EXPLAIN: -> Group (no aggregates)
-> Nested loop left join (cost=1.70 rows=9)
-> Index scan on t1 using PRIMARY (cost=0.55 rows=3)
-> Filter: (t2.a > t1.b) (cost=0.18 rows=3)
-> Index range scan on t2 (re-planned for each iteration) (cost=0.18 rows=3) SET optimizer_switch='left_join_elimination=on'; EXPLAIN FORMAT=TREE SELECT t1.a FROM t1 LEFT JOIN t2 ON t2.a > t1.b GROUP BY t1.a\G
*************************** 1. row *************************** EXPLAIN: -> Table scan on t1 (cost=0.55 rows=3)
SET optimizer_switch='left_join_elimination=off'; EXPLAIN FORMAT=TREE SELECT t1.a FROM t1 LEFT JOIN t2 ON t2.a = t1.b\G
*************************** 1. row ***************************
EXPLAIN: -> Nested loop left join (cost=1.60 rows=3)
-> Table scan on t1 (cost=0.55 rows=3)
-> Single-row index lookup on t2 using PRIMARY (a=t1.b) (cost=0.28 rows=1) SET optimizer_switch='left_join_elimination=on'; EXPLAIN FORMAT=TREE SELECT t1.a FROM t1 LEFT JOIN t2 ON t2.a = t1.b\G *************************** 1. row *************************** EXPLAIN: -> Table scan on t1 (cost=0.55 rows=3)
SET optimizer_switch='left_join_elimination=off'; EXPLAIN FORMAT=TREE SELECT t0.a FROM t1 AS t0 WHERE EXISTS ( SELECT t2.b FROM t1 LEFT JOIN t2 ON t2.a > t1.b WHERE t0.a = t1.b )\G
*************************** 1. row ***************************
EXPLAIN: -> Remove duplicate t0 rows using temporary table (weedout) (cost=2.75 rows=9)
-> Nested loop left join (cost=2.75 rows=9)
-> Nested loop inner join (cost=1.60 rows=3)
-> Filter: (t1.b is not null) (cost=0.55 rows=3)
-> Table scan on t1 (cost=0.55 rows=3)
-> Single-row index lookup on t0 using PRIMARY (a=t1.b) (cost=0.28 rows=1)
-> Filter: (t2.a > t1.b) (cost=0.55 rows=3)
-> Index range scan on t2 (re-planned for each iteration) (cost=0.55 rows=3) SET optimizer_switch='left_join_elimination=on';
EXPLAIN FORMAT=TREE SELECT t0.a
FROM t1 AS t0
WHERE EXISTS (
SELECT t2.b
FROM t1 LEFT JOIN t2 ON t2.a > t1.b
WHERE t0.a = t1.b
)\G
*************************** 1. row ***************************
EXPLAIN: -> Nested loop inner join (cost=1.15 rows=3)
-> Filter: (`<subquery2>`.b is not null) (cost=0.60 rows=3)
-> Table scan on <subquery2> (cost=0.60 rows=3)
-> Materialize with deduplication (cost=0.55 rows=3)
-> Filter: (t1.b is not null) (cost=0.55 rows=3)
-> Table scan on t1 (cost=0.55 rows=3)
-> Single-row index lookup on t0 using PRIMARY (a=`<subquery2>`.b) (cost=0.35 rows=1) The execution plan shows that table t2 inside the subquery has been eliminated, indicating that left join elimination was successfully executed.
{
"steps": [
{
"join_preparation": {
"steps": [
{
"join_elimination": {
"number_of_eliminated_tables": 1,
"expanded_query": "/* select#1 */ select `t0`.`a` AS `a` from `t1` `t0` semi join (`t1`) where ((`t0`.`a` = `t1`.`b`))"
}
}
]
}
}
]
} Feedback
Was this page helpful?
Provide feedbackThank you very much for your feedback. We will continue working to improve the documentation.See the reply and handling status in My Cloud VOC.
For any further questions, feel free to contact us through the chatbot.
Chatbot