Index Condition Pushdown Based on Cost Estimation
Scenarios
In the MySQL Community Edition, index condition pushdown (ICP) is an optimization technique that pushes WHERE conditions down to the storage engine layer for evaluation. However, the decision to enable ICP is made after the execution plan has already been selected. This rule-based, delayed decision-making can lead to two major issues:
- Missed equality access path selection: When multiple indexes share the same leading column (for example, idx_a(a), idx_ab(a,b), and idx_abc(a,b,c)), the optimizer compares candidate indexes primarily based on the number of rows to be scanned while choosing the equality access path. For a query with WHERE a = 4 AND c = 9, all three indexes scan the same number of rows for a = 4, so the narrowest index idx_a wins. However, the fact that only idx_abc can filter c = 9 using ICP is completely ignored.
- Missed range access path selection: Range-scan candidates are compared against the full-table-scan baseline using the raw cost reported by the storage engine. This cost reflects only raw row-read overhead and is unaware of any conditions that could be pushed down. For a query with WHERE a BETWEEN 2 AND 2.5 AND c = 7, the raw cost of all range-scan candidates may exceed that of a full table scan, causing the optimizer to abandon index scans. This leaves no opportunity for ICP to participate.
TaurusDB addresses these issues by introducing cost-based ICP selection. When the feature is enabled, the optimizer pre-estimates ICP performance benefits during both equality and range access path selection. This incorporates ICP's filtering effect into cost estimation, allowing wider indexes that benefit from ICP to compete fairly and enabling the optimizer to choose a better execution plan.
How It Works
Cost-based ICP selection is an optimization strategy that pre-evaluates the ICP filtering benefit during access path selection. The optimizer moves ICP's cost impact forward into the plan generation phase while ensuring the result set remains unchanged.
How ICP Improves Performance
During a secondary index scan, if the query needs to access columns not covered by the index (a non-covering index scenario), InnoDB must perform a lookup in the clustered index for every matching index row to retrieve the complete record. The SQL layer then evaluates the WHERE condition row by row.
When the WHERE condition involves trailing key columns of the index (such as c = 9 on idx_abc(a, b, c)), these column values are already present in the index records. This means that the storage engine can directly evaluate the condition before initiating a row lookup.
ICP leverages this characteristic. It pushes the WHERE condition down to the storage engine layer. The engine evaluates the pushed-down predicates during the index scan, skipping row lookups for any records that fail to meet the condition.
Performance benefits of ICP are driven by two main factors:
- Reduced lookup I/O: Rows filtered out by ICP do not require clustered index lookups. This reduces disk I/O and buffer pool access overhead. For highly selective conditions, the number of table lookups can be drastically reduced.
- Reduced CPU overhead: The storage engine evaluates conditions directly on the index record's key values. This avoids the overhead of constructing complete expression trees, invoking virtual functions, and performing type conversions at the SQL layer.
For example, given the query with WHERE a = 4 AND c = 9, during an index scan on idx_abc: The engine first locates the index records using a = 4 and then evaluates the condition on column c (checks if c is equal to 9) in the index records. Rows that fail to satisfy the condition are skipped and do not require clustered index lookups. For rows satisfying the condition, the engine performs lookups. Without ICP, the engine performs a row lookup for every row matching a = 4 before the SQL layer evaluates the predicate c = 9. With ICP enabled, it reduces the number of row lookups from all rows matching a = 4 to only those matching a = 4 AND c = 9.
Application Scenarios
This feature is applied during the optimizer planning phase, specifically during the candidate comparison stage for both equality access path selection and range access path selection. The optimizer first checks whether the index supports ICP, whether there are pushdown-eligible conditions on trailing key columns, and whether the filtering threshold is satisfied. If all conditions are met, the optimizer deducts the estimated ICP benefit from the original cost and uses the adjusted cost for candidate comparison. After enabling this feature, you may observe that a wider index is selected in the execution plan and that Using index condition appears in the Extra column in the EXPLAIN output.
- Equality access scenarios
Assume that the table has two indexes: idx_a(a) and idx_abc(a, b, c). In MySQL Community Edition, both indexes scan the same number of rows for a = 4 but idx_a is selected because it is narrower. However, idx_abc can push down the condition c = 9 to the storage engine layer using ICP, reducing the number of row lookups. With this feature enabled, the optimizer estimates the ICP benefit for idx_abc, allowing idx_abc to win the candidate comparison.
- Range scan scenarios
In MySQL Community Edition, range scan candidates are compared based on their original costs. When the costs of different indexes are similar, the narrowest index is selected. However, the wider idx_abc can use ICP to filter out rows matching c = 7, reducing both row lookups and row evaluation overhead. With this feature enabled, the optimizer incorporates the ICP filtering benefit into cost estimation, allowing idx_abc to become the optimal choice.
Cost Model and Protection Mechanisms
- Cost Model
ICP provides performance benefits through both I/O and CPU savings, consistent with MySQL's existing cost formulas:
- I/O savings: Rows filtered out by ICP bypass clustered index lookups, reducing I/O overhead.
- CPU savings: Evaluating conditions at the storage engine layer is less expensive than evaluation at the SQL layer. Rows filtered out do not require complete expression evaluation at the SQL layer.
- Benefit cap: ICP benefits are capped to ensure that they do not exceed the proportion of cost that ICP can actually influence, preventing distorted cost adjustments.
- Protection Mechanisms
To prevent index selection deviation from overly optimistic ICP benefit estimation, the feature implements several protection mechanisms.
- Filtering threshold: Cost adjustments are applied only when the selectivity of the pushdown-eligible conditions of ICP is sufficiently low, ensuring that ICP can filter out a sufficient proportion of rows. This threshold excludes conditions with unreliable selectivity estimates (such as inequalities or multiple OR combinations) to prevent poor index choices.
- Benefit cap: ICP benefits cannot exceed the proportion of cost ICP can influence, ensuring that adjusted costs remain non-negative and reasonable.
- Engine-side cost factor: Condition evaluation at the engine side is modeled as cheaper than evaluation at the SQL side, preventing overestimation of CPU savings.
Prerequisites
- The TaurusDB kernel version must be 2.0.78.260602 or later. For details about how to check the kernel version, see How Can I Check the Version of a TaurusDB Instance?
- The icp_cost_based parameter in optimizer_switch is set to ON.
Supported Query Statements
- SELECT
- INSERT ... SELECT (single-table query)
- REPLACE ... SELECT (single-table query)
- CREATE TABLE ... SELECT (single-table query)
- Single-table queries in each branch of a UNION
Constraints
- Only single-table queries are supported. Multi-table JOIN queries and subqueries are not supported. Cross-table conditions in multi-table joins may lead to inaccurate selectivity estimation and introduce regression risks.
- Multi-table UPDATE and DELETE statements are not supported.
- FULLTEXT indexes are not supported. FULLTEXT indexes use a different ICP mechanism.
- Vector indexes (HA_VECTOR) are not supported. Vector indexes rely on a different access method.
- Clustered primary keys are not supported because ICP provides little benefit for clustered primary keys.
- Indexes on virtually generated columns are not supported.
- Index-only scans using covering indexes are not supported. Covering indexes do not require ICP.
- Index merge and index scans ordered by row ID are not supported.
- Reverse index scans are not supported. ICP is skipped by the execution layer during reverse scans.
- ICP cost estimation for BLOB, TEXT, and GEOMETRY columns is not supported.
- ICP cost estimation for prefix index segments is not supported.
- When no histogram is available, the optimizer relies on fixed values to estimate the selectivity of WHERE clauses. These estimates may deviate significantly from the actual data distribution. As a result, ICP-based cost adjustments may occasionally lead to suboptimal index choices and performance degradation. If this occurs, you can disable this feature by running SET optimizer_switch='icp_cost_based=off' to restore the original behavior.
How to Use
You can manage cost-based ICP selection through the optimizer_switch parameter.
| Parameter | Level | Description |
|---|---|---|
| optimizer_switch | Global, Session | Enable or disable cost-based ICP selection using icp_cost_based=on or icp_cost_based=off. The feature is disabled by default. |
SET optimizer_switch='icp_cost_based=ON';
SET optimizer_switch='icp_cost_based=OFF';
You can use optimizer_trace to check whether cost-based ICP selection is applied.
- When ICP provides measurable cost savings, the trace includes fields such as icp_filter_effect and icp_cost.
- When ICP is skipped, the trace includes the icp_skip_reason field, which explains the reason for skipping. Common values include:
- no_icp_candidates: The index has no pushdown-eligible conditions on trailing key columns.
- filter_above_threshold: The selectivity of pushdown-eligible conditions is too high, so ICP provides negligible cost savings.
- can_not_consider_icp: The index (for example, FULLTEXT indexes or clustered primary keys) does not support ICP.
- all_keyparts_bound: All key parts of the index are already bound by the chosen access method, so ICP is unnecessary.
Examples
CREATE TABLE t ( id INT NOT NULL AUTO_INCREMENT, a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT, payload CHAR(32), PRIMARY KEY(id), INDEX idx_a(a), INDEX idx_ab(a, b), INDEX idx_abc(a, b, c), INDEX idx_abcd(a, b, c, d) ); INSERT INTO t (a, b, c, d, payload) WITH RECURSIVE number_series AS ( SELECT 1 AS n UNION ALL SELECT n + 1 FROM number_series WHERE n < 1000 ) SELECT n % 5 + 1, n % 10 + 1, n % 100 + 1, n % 1000 + 1, LPAD(n, 32, 'x') FROM number_series; ANALYZE TABLE t;
SET optimizer_switch='icp_cost_based=off'; EXPLAIN SELECT COUNT(payload) FROM t WHERE a = 4 AND c = 9\G
mysql> EXPLAIN SELECT COUNT(payload) FROM t WHERE a = 4 AND c = 9\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
partitions: NULL
type: ref
possible_keys: idx_a,idx_ab,idx_abc,idx_abcd
key: idx_a
key_len: 4
ref: const
rows: 200
filtered: 10.00
Extra: Using where
1 row in set, 1 warning (0.00 sec) SET optimizer_switch='icp_cost_based=on'; EXPLAIN SELECT COUNT(payload) FROM t WHERE a = 4 AND c = 9\G
mysql> EXPLAIN SELECT COUNT(payload) FROM t WHERE a = 4 AND c = 9\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
partitions: NULL
type: ref
possible_keys: idx_a,idx_ab,idx_abc,idx_abcd
key: idx_abc
key_len: 4
ref: const
rows: 200
filtered: 10.00
Extra: Using index condition
1 row in set, 1 warning (0.00 sec) SET optimizer_switch='icp_cost_based=on'; SET optimizer_trace='enabled=on'; SELECT COUNT(payload) FROM t WHERE a = 4 AND c = 9; SELECT TRACE FROM information_schema.OPTIMIZER_TRACE\G
{
"access_type": "ref",
"index": "idx_a",
"icp_skip_reason": "no_icp_candidates",
"rows": 200,
"cost": 25.25,
"chosen": true
},
{
"access_type": "ref",
"index": "idx_abc",
"icp_original_cost": 25.25,
"icp_filter_effect": 0.1,
"icp_cost": 11.5,
"rows": 200,
"cost": 11.5,
"chosen": true
} idx_a has no pushdown-eligible conditions on trailing key columns, so icp_skip_reason: no_icp_candidates is displayed. idx_abc can push down c = 9 through ICP, reducing the cost from 25.25 to 11.5. As a result, idx_abc wins the candidate comparison.
SET optimizer_switch='icp_cost_based=off'; EXPLAIN SELECT COUNT(payload) FROM t WHERE a BETWEEN 2 AND 2.5 AND c = 7\G
mysql> EXPLAIN SELECT COUNT(payload) FROM t WHERE a BETWEEN 2 AND 2.5 AND c = 7\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
partitions: NULL
type: range
possible_keys: idx_a,idx_ab,idx_abc,idx_abcd
key: idx_a
key_len: 4
ref: NULL
rows: 200
filtered: 10.00
Extra: Using index condition; Using where
1 row in set, 1 warning (0.00 sec) SET optimizer_switch='icp_cost_based=on'; EXPLAIN SELECT COUNT(payload) FROM t WHERE a BETWEEN 2 AND 2.5 AND c = 7\G
mysql> EXPLAIN SELECT COUNT(payload) FROM t WHERE a BETWEEN 2 AND 2.5 AND c = 7\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t
partitions: NULL
type: range
possible_keys: idx_a,idx_ab,idx_abc,idx_abcd
key: idx_abc
key_len: 4
ref: NULL
rows: 200
filtered: 10.00
Extra: Using index condition
1 row in set, 1 warning (0.01 sec) Use optimizer_trace to examine how ICP adjusts the cost of each range-scan candidate:
SET optimizer_switch='icp_cost_based=on'; SET optimizer_trace='enabled=on'; SELECT COUNT(payload) FROM t WHERE a BETWEEN 2 AND 2.5 AND c = 7; SELECT TRACE FROM information_schema.OPTIMIZER_TRACE\G
{
"index": "idx_a",
"icp_skip_reason": "no_icp_candidates",
"cost": 70.26,
"chosen": true
},
{
"index": "idx_abc",
"icp_original_cost": 70.26,
"icp_filter_effect": 0.1,
"icp_cost": 11.76,
"chosen": true
} idx_a has no pushdown-eligible conditions on trailing key columns, so icp_skip_reason: no_icp_candidates is displayed. idx_abc can push down c = 7 through ICP, reducing the cost from 70.26 to 11.76. As a result, idx_abc wins the candidate comparison.
What is your overall rating for this page?
Thank 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