范围查询冗余条件消除
操作场景
社区MySQL在处理 WHERE col = const 或 WHERE col > const 等谓词时,若列上存在索引且优化器选择 Range scan 访问方式,范围扫描的上下界已隐含保证了谓词成立。然而,SQL 层仍会对每一行重复求值,造成不必要的 CPU 开销。对于大范围扫描场景,这种冗余求值的性能损失尤为突出。
以查询 SELECT * FROM t WHERE a = 5 AND b > 3 为例,若索引为 (a, b),范围扫描的 min_key 为 [5, 3]、max_key 为 [5, +∞],则 a = 5 已被范围下界完全保证,无需逐行求值。但社区 MySQL 仅支持 REF 访问的等值消除(test_if_ref),对 Range scan 场景下的等值及不等式谓词均不做消除。
理论上,可引入类似 REF 访问的等值消除机制来处理 Range scan,但面临以下挑战:
- 边界信息访问安全:Range scan 的区间边界存储在 QUICK_RANGE 结构中,需安全读取并与谓词常量比较。若比较语义与存储引擎不一致,可能导致谓词被误删,进而产生错误查询结果。
- 合并区间语义丢失:OR 子句产生的合并区间与原始谓词形式可能不一致。例如 c=3 OR c>2 会被合并为 c>2 的单区间(无上界),此时无法判断 c=3 是否冗余,强行消除会导致语义错误。
- NDP 下推条件受影响:NDP(Near Data Processing)下推依赖 WHERE 条件,冗余消除后可能导致可下推条件减少,反而降低整体性能。
因此,TaurusDB支持范围查询冗余条件消除功能。当表使用Range scan访问时,优化器自动识别并移除已被范围上下界隐含保证的谓词,减少SQL层的重复求值开销,提升查询性能。
转换过程
原查询:
SELECT * FROM t WHERE a = 5 AND b > 3
若索引为(a, b),Range scan 的 min_key=[5, 3],max_key=[5, +∞]:
- a = 5:min_key 和 max_key 中 a 的值均为5,被范围隐含保证,无需求值,因此被消除。
- b > 3:min_key 中 b 的值为 3,但范围下界为包含(>=),谓词为严格大于(>),两者语义不同,无法消除,即被保留。
转换后:
SQL层实际求值的条件(a=5 已被消除)。
SELECT * FROM t WHERE b > 3
前提条件
- TaurusDB内核版本为2.0.42.230600或以上的版本。内核版本的查询方法请参见如何查看云数据库 TaurusDB实例的版本号。
- 参数rds_empty_redundant_check_in_range_scan设置为ON。详细内容请参见修改TaurusDB实例参数。
支持的谓词类型
| 谓词类型 | SQL 示例 | 消除条件 |
|---|---|---|
| 等值 | col = 5 | 范围的min_key和max_key中该列的值均等于常量。 |
| 小于 | col < 10 | 范围的max_key等于常量,且范围上界为严格小于(NEAR_MAX 标记)。 |
| 小于等于 | col <= 10 | 范围的max_key等于常量。 |
| 大于 | col > 3 | 范围的min_key等于常量,且范围下界为严格大于(NEAR_MIN 标记)。 |
| 大于等于 | col >= 3 | 范围的min_key等于常量 |
| BETWEEN | col BETWEEN 3 AND 10 | 范围的min_key 等于下界常量且 max_key 等于上界常量。 |
| IS NULL | col IS NULL | 范围的min_key和max_key的NULL标记位均为1。 |
支持的查询语句
- SELECT
- INSERT ... SELECT
- REPLACE ... SELECT
- 支持视图、PREPARED STMT
- 支持组合索引中的非首列字段
- 支持 DESC 索引
- 支持可空字段的 NULL 字节处理
约束限制
- 仅支持简单的Range scan(单区间扫描),不支持 index merge、skip scan。
- 仅支持单区间(ranges.size() == 1),不支持 OR 产生的不相交多区间。
- 不支持前缀索引段(即索引列定义了前缀长度,如col(10))。
- 不支持BIT类型字段。
- 不支持动态范围扫描(Dynamic Range,即执行时才确定范围的场景)。
- 不支持OR子句内部的谓词消除。OR子句会被临时禁用冗余检查,确保正确性。
- 不支持NOT BETWEEN。
- 不支持外连接中NULL扩展表的谓词消除。
- 当 NDP(Near Data Processing)可能启用时,冗余条件消除自动禁用,优先保证NDP下推效果。
- Hypergraph 优化器的 EXPLAIN 输出尚未适配该特性。
使用方法
该特性通过会话参数rds_empty_redundant_check_in_range_scan控制。
| 参数 | 级别 | 默认值 | 说明 |
|---|---|---|---|
| rds_empty_redundant_check_in_range_scan | SESSION / GLOBAL | OFF | 索引范围扫描时,SQL层去除冗余的条件并对存储引擎返回的行不再执行冗余条件的检查,扩大了Offset Pushdown的生效范围。
|
示例
- 创建测试表并插入数据:
CREATE TABLE t1 (a INT, b INT, INDEX(a, b)); INSERT INTO t1 VALUES (1,2),(2,3),(3,3),(4,3),(5,5),(2,5),(3,7);
- 关闭冗余条件消除,查看执行计划:
SET SESSION rds_empty_redundant_check_in_range_scan = OFF; EXPLAIN FORMAT=TREE SELECT * FROM t1 WHERE a = 5 AND b > 3;
执行计划中保留了Filter层,SQL层需要逐行求值a = 5 和 b > 3:
mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 WHERE a = 5 AND b > 3; +-----------------------------------------------------------------------------------------------------------------------+ | EXPLAIN | +-----------------------------------------------------------------------------------------------------------------------+ | -> Filter: ((t1.a = 5) and (t1.b > 3)) (cost=0.46 rows=1) -> Index range scan on t1 using a (cost=0.46 rows=1) +-----------------------------------------------------------------------------------------------------------------------+ - 开启冗余条件消除,查看执行计划:
SET SESSION rds_empty_redundant_check_in_range_scan = ON; EXPLAIN FORMAT=TREE SELECT * FROM t1 WHERE a = 5 AND b > 3;
`Filter` 层消失,`a = 5` 和 `b > 3` 均已被范围扫描的上下界隐含保证,无需SQL层重复求值:
mysql> EXPLAIN FORMAT=TREE SELECT * FROM t1 WHERE a = 5 AND b > 3; +--------------------------------------------------------+ | EXPLAIN | +--------------------------------------------------------+ | -> Index range scan on t1 using a (cost=0.46 rows=1) +--------------------------------------------------------+
- 验证查询结果一致性:
SET SESSION rds_empty_redundant_check_in_range_scan = OFF; SELECT * FROM t1 WHERE a = 2 AND b > 2;
SET SESSION rds_empty_redundant_check_in_range_scan = ON; SELECT * FROM t1 WHERE a = 2 AND b > 2;
两种模式下查询结果完全一致:
+------+------+ | a | b | +------+------+ | 2 | 3 | | 2 | 5 | +------+------+
性能测试
以下测试基于1000万行宽表,组合主键上的等值+范围查询场景,验证冗余条件消除对查询性能的提升。
- 准备测试数据。
SET SESSION cte_max_recursion_depth = 10000001;
DROP TABLE IF EXISTS big_test; CREATE TABLE big_test ( a INT NOT NULL, b INT NOT NULL, c INT NOT NULL, d INT NOT NULL, e INT NOT NULL, f1 VARCHAR(200), f2 VARCHAR(200), f3 VARCHAR(200), PRIMARY KEY (a, b, c, d, e) ) ENGINE=InnoDB ROW_FORMAT=COMPACT;
INSERT INTO big_test (a, b, c, d, e, f1, f2, f3) WITH RECURSIVE seq AS ( SELECT 1 AS n UNION ALL SELECT n+1 FROM seq WHERE n < 10000000 ) SELECT (n-1) DIV 2000000 + 1 AS a, (n-1) DIV 200000 MOD 10 + 1 AS b, (n-1) DIV 20000 MOD 10 + 1 AS c, (n-1) DIV 200 MOD 100 + 1 AS d, n AS e, REPEAT(CONCAT('val', n MOD 1000), 5) AS f1, REPEAT(CONCAT('key', n MOD 500), 5) AS f2, REPEAT(CONCAT('dat', n MOD 200), 5) AS f3 FROM seq; - 执行计划对比。
查询WHERE a = 3 AND b > 3,匹配约200万行。其中a = 3可被范围下界隐含保证,属于冗余谓词。
- 关闭冗余条件消除时,EXPLAIN 显示Using where,SQL层需逐行求值a = 3 AND b > 3。
SET SESSION rds_empty_redundant_check_in_range_scan = OFF;
EXPLAIN SELECT SUM(LENGTH(f1)) FROM big_test WHERE a = 3 AND b > 3; +----+-------------+----------+-------+---------------+---------+---------+------+---------+----------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+----------+-------+---------------+---------+---------+------+---------+----------+-------------+ | 1 | SIMPLE | big_test | range | PRIMARY | PRIMARY | 8 | NULL | 2719004 | 100.00 | Using where | +----+-------------+----------+-------+---------------+---------+---------+------+---------+----------+-------------+
- 开启冗余条件消除时,a = 3被消除,EXPLAIN 的 Extra 列不再显示 Using where。
SET SESSION rds_empty_redundant_check_in_range_scan = ON;
EXPLAIN SELECT SUM(LENGTH(f1)) FROM big_test WHERE a = 3 AND b > 3; +----+-------------+----------+-------+---------------+---------+---------+------+---------+----------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+----------+-------+---------------+---------+---------+------+---------+----------+-------+ | 1 | SIMPLE | big_test | range | PRIMARY | PRIMARY | 8 | NULL | 2719004 | 100.00 | NULL | +----+-------------+----------+-------+---------------+---------+---------+------+---------+----------+-------+
- 关闭冗余条件消除时,EXPLAIN 显示Using where,SQL层需逐行求值a = 3 AND b > 3。
- 查询耗时对比
SET SESSION rds_empty_redundant_check_in_range_scan = OFF; SELECT SUM(LENGTH(f1)) FROM big_test WHERE a = 3 AND b > 3;
SET SESSION rds_empty_redundant_check_in_range_scan = ON; SELECT SUM(LENGTH(f1)) FROM big_test WHERE a = 3 AND b > 3;
- 冗余条件消除关闭
mysql> SELECT SUM(LENGTH(f1)) FROM big_test WHERE a = 3 AND b > 3; +-----------------+ | SUM(LENGTH(f1)) | +-----------------+ | 41230000 | +-----------------+ 1 row in set (0.69 sec)
- 冗余条件消除开启
mysql> SELECT SUM(LENGTH(f1)) FROM big_test WHERE a = 3 AND b > 3; +-----------------+ | SUM(LENGTH(f1)) | +-----------------+ | 41230000 | +-----------------+ 1 row in set (0.51 sec)
表3 查询耗时对比 模式
Extra
耗时
说明
OFF
Using where
0.69s
SQL层对200万行逐行求值 a=3 AND b>3。
ON
NULL
0.51s
a=3 被消除,仅求值 b>3,性能提升约 26%。
- 冗余条件消除关闭