关于算子中range_key/range/range_cond的疑问

有如下表t1,其中c1是主键,且建了索引e1,索引列分别为c2和c3


EXPLAIN SELECT * FROM t1 WHERE c2 < 1 AND c3 < 1 AND c4 < 1 的执行计划如上图所示。

疑问1:OPERATOR为TABLE FULL SCAN,但从下面的out信息看看is_index_back=true,range_cond([t1.c2<1])应该是走了索引,NAME列为什么不是T1(e1)?

疑问2:该执行计划是用了索引e1,所以range_key([t1.c2],[t2.c3],[t1.c1]),但range为什么是(NULL,MAX,MAX;1,MIN,MIN)? 如下图示例,该索引只有签到列c2起作用,即range_cond([t1.c2<1]) c2列最小从null开始(OB对null特殊处理排序),C3以及C1列要扫描所有的值;直到C2=1这没问题,为什么后面对应的C3以及C2只是MIN而不是MAX呢?

疑问3:filter_before_indexback[true,false],表名C3可以在索引扫描的时候回表前参与过滤计算(相当于索引虽然不能直接使用C3,但是可以将C3下推参与计算),降低回表查询的数据量进一步性能,这个理解是否有偏差?

TABLE FULL SCAN 表示查询计划中表示全表扫描的操作,是不需要走索引的,is_index_back表示是否有进行回表,具体算子输出信息您可以参考:OceanBase分布式数据库-海量数据 笔笔算数

个人理解:
1、我在4.2.1的环境复现是table range scan的,并且也正确显示,需要原厂老师确认一下。


2、c2<1,你可以把它看作null<c2<1,因此扫描位置是从(null,max,max)开始,到(1,min,min)结束,开始位置是因为null不满足条件,因此从索引c2=null的最后的位置开始扫描,结束是因为到(1,min,min)时,c2=1已经不满足条件了,因此后面就不需要扫描了。
3、你的理解是对的,回表前过滤就是为了降低回表的数据量。

1 个赞

第1点可能要看表具体数据,我猜测是不是因为实际没有从索引中返回所以判断有问题就认为是全表扫描了?

问题1只是打印显示名字,ppt中应该是早期版本跑出来的,实际有是走索引
问题2,用个case来说明
case:c1 <= 1

range:range_key([t2.c1]), range(NULL ; 1]

为什么左端起始值是NULL不是MIN?

首先,注意到c1 <= 1这个条件是NULL reject的,NULL值应当被排除在结果之外;其次,这和数据排序有关,在mysql中,是NULL first的,也就是说,NULL值排在所有有效值的前面

然后(max,min)是表示alway false, (min,max)表示全集

问题3:你的理解是正确的,回表前过滤

理解,null值肯定是不满足c2<1的条件的,而它又排在最前面,所以肯定是从最后一个也就是(null,max,max)开始扫了。

请问你的版本是多少?
目前我看了 4.2.1.3 和 4.2.1.6 下 执行计划是下面这种, 是可以走索引。

create table t10(id int primary key,c2 int, c3 int ,c4 int); 
insert into t10 values(1,10,20,40),(2,8,16,32),(3,6,9,27),(4,16,32,64);
create index t10_ind1 on t10(c2,c3); 
ALTER SYSTEM SET enable_sql_extension = TRUE;

-- 退出重进
analyze table t10 compute statistics;
explain select * from t10 where c2<1 and c3<1 and c4 <1;


(root@10.0.0.62:2881) [test]> explain select * from t10 where c2<1 and c3<1 and c4 <1;                                                                     
+-----------------------------------------------------------------------------------------------------+                                                    
| Query Plan                                                                                          |                                                    
+-----------------------------------------------------------------------------------------------------+                                                    
| =========================================================                                           |                                                    
| |ID|OPERATOR        |NAME         |EST.ROWS|EST.TIME(us)|                                           |                                                    
| ---------------------------------------------------------                                           |                                                    
| |0 |TABLE RANGE SCAN|t10(t10_ind1)|0       |4           |                                           |                                                    
| =========================================================                                           |                                                    
| Outputs & filters:                                                                                  |                                                    
| -------------------------------------                                                               |                                                    
|   0 - output([t10.id], [t10.c2], [t10.c3], [t10.c4]), filter([t10.c3 < 1], [t10.c4 < 1]), rowset=16 |                                                    
|       access([t10.id], [t10.c2], [t10.c3], [t10.c4]), partitions(p0)                                |                                                    
|       is_index_back=true, is_global_index=false, filter_before_indexback[true,false],               |                                                    
|       range_key([t10.c2], [t10.c3], [t10.id]), range(NULL,MAX,MAX ; 1,MIN,MIN),                     |                                                    
|       range_cond([t10.c2 < 1])                                                                      |
+-----------------------------------------------------------------------------------------------------+         
12 rows in set (0.02 sec)  

既然是全表扫描,那为什么还要回表呢?is_index_back=true代表哪里的回表呢?

感谢您的解答,再追问一下:
1、问题是走了索引,而不是全表扫描没错吧?
该例子是楼上同学回答的 OceanBase分布式数据库-海量数据 笔笔算数 中Q2,版本是V4.3.1;

2、null您这里说的是mysql null first 所以开始是(null,max,max), 那oracle 呢?(min,max,max)?

3、(max,min)是表示alway false,(min,max)表示全集,那(min,min)表示所有的最小值与max,max)所有最大值?

OceanBase分布式数据库-海量数据 笔笔算数 中Q2例子,版本是V4.3.1。

所以如果SELECT * FROM t1 WHERE c2 < =1 AND c3 < 1 AND c4 < 1 因为C2<=1的话,包含了等于1的时候,最后一个扫描范围就是(1,max,max)了吧?

这个文档应该是写错了。
4.3.1 下的结果也是会用索引,只有用了索引,range_cond( c2<1) 才解释的过去。

obclient [test]> analyze table t10 compute statistics; explain select * from t10 where c2<1 and c3<1 and c4 <1; 
Query OK, 0 rows affected (0.128 sec)

+-----------------------------------------------------------------------------------------------------+
| Query Plan                                                                                          |
+-----------------------------------------------------------------------------------------------------+
| =========================================================                                           |
| |ID|OPERATOR        |NAME         |EST.ROWS|EST.TIME(us)|                                           |
| ---------------------------------------------------------                                           |
| |0 |TABLE RANGE SCAN|t10(t10_ind1)|1       |7           |                                           |
| =========================================================                                           |
| Outputs & filters:                                                                                  |
| -------------------------------------                                                               |
|   0 - output([t10.id], [t10.c2], [t10.c3], [t10.c4]), filter([t10.c3 < 1], [t10.c4 < 1]), rowset=16 |
|       access([t10.id], [t10.c2], [t10.c3], [t10.c4]), partitions(p0)                                |
|       is_index_back=true, is_global_index=false, filter_before_indexback[true,false],               |
|       range_key([t10.c2], [t10.c3], [t10.id]), range(NULL,MAX,MAX ; 1,MIN,MIN),                     |
|       range_cond([t10.c2 < 1])                                                                      |
+-----------------------------------------------------------------------------------------------------+
12 rows in set (0.021 sec)
obclient [test]> status;
--------------
obclient  Ver 2.2.4 Distrib 10.4.18-MariaDB, for Linux (x86_64) using readline 5.1

Connection id:          3221601054
Current database:       test
Current user:           root@10.0.0.65
SSL:                    Not in use
Current pager:          stdout
Using outfile:          ''
Using delimiter:        ;
Server version:         OceanBase 4.3.1.0 (r100000212024051522-d409d05b00a146adc4c86aec459ab9d6b0ae2c80) (Built May 15 2024 22:24:33)
Protocol version:       10
Connection:             10.0.0.65 via TCP/IP
Server characterset:    utf8mb4
Db     characterset:    utf8mb4
Client characterset:    utf8mb4
Conn.  characterset:    utf8mb4
TCP port:               2881
Protocol:               Compressed
Active                  --------------

不是,如果c2<=1,扫描终点是(1,1, min), c2=1也不是扫描c2=1的所有数据,因为还有c3<1这个条件,c2=1满足,所以前面1位是1,c3<1的条件定位到后面两位是(1,min),和c2<1的道理是一样的,所以终点是(1,1,min)