首批通过分布式安全可靠测评,为关键业务系统打造
TABLE SCAN
更新时间:2025-11-27 19:46:27
TABLE SCAN 算子是存储层和 SQL 层的接口,用于展示优化器选择哪个索引来访问数据。
在 OceanBase 数据库 V4.1.0 版本中,对于普通索引和全局索引,索引回表的逻辑是封装在 TABLE SCAN 算子中的;在执行计划展示时,会有 is_index_back 标识算子是否需要回表,以及 is_global_index 标识算子是否是扫描全局索引。 示例:含 TABLE SCAN 算子的执行计划
Q1:
obclient> CREATE TABLE t1(c1 INT PRIMARY KEY, c2 INT, c3 INT, c4 INT);
obclient> CREATE INDEX e1 ON t1(c1,c2);
obclient> EXPLAIN SELECT * FROM t1 WHERE c1 = 1;
+--------------------------------------------------------------------------+
| Query Plan |
+--------------------------------------------------------------------------+
| ========================================= |
| |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| |
| ----------------------------------------- |
| |0 |TABLE GET|T1 |1 |3 | |
| ========================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T1.C1], [T1.C2], [T1.C3], [T1.C4]), filter(nil), rowset=16 |
| access([T1.C1], [T1.C2], [T1.C3], [T1.C4]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([T1.C1]), range[1 ; 1], |
| range_cond([T1.C1 = 1]) |
+--------------------------------------------------------------------------+
上述示例中,执行计划展示中的 outputs & filters 详细展示了 TABLE SCAN 算子的输出信息如下:
| 信息名称 | 含义 |
|---|---|
| operator | TABLE SCAN 算子的 operator 有三种形式:TABLE SCAN、TABLE GET 和 TABLE SKIP SCAN。
|
| name | 选择用哪个索引来访问数据。选择的索引的名字会跟在表名后面,如果没有索引的名字,则说明执行的是主表扫描。 这里需要注意,在 OceanBase 数据库中,主表和索引的组织结构是一样的,主表本身也是一个索引。 |
| output | 该算子的输出列。 |
| filter | 该算子的过滤谓词。 由于示例中 Q1 查询的 TABLE GET 算子没有设置 filter,所以为 nil。 |
| partitions | 查询需要扫描的分区。 |
| is_index_back | 该算子是否需要回表。 例如,在 Q1 查询中,因为选择了主表,所以不需要回表。 |
| is_global_index | 该算子是否是扫描全局索引。例如,在Q2查询中,使用的索引为普通索引,所以is_global_index 为 false。在 Q3 查询中,使用了 t2 表的 i2 索引,i2 索引为全局索引,所以 is_global_index 为 true。 |
| range_key/range/range_cond |
|
Q2:
obclient> CREATE INDEX e1 ON t1(c2,c3);
obclient> EXPLAIN SELECT * FROM t1 WHERE c2 < 1 AND c3 < 1 AND c4 < 1;
+---------------------------------------------------------------------------------------------------------------------------------------------+
| Query Plan |
+---------------------------------------------------------------------------------------------------------------------------------------------+
| ================================================== |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| -------------------------------------------------- |
| |0 |TABLE RANGE SCAN|T1(E1)|1 |5 | |
| ================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T1.C1], [T1.C2], [T1.C3], [T1.C4]), filter([T1.C3 < cast(1, NUMBER(-1, -85))], [T1.C4 < cast(1, NUMBER(-1, -85))]), rowset=16 |
| access([T1.C1], [T1.C2], [T1.C3], [T1.C4]), partitions(p0) |
| is_index_back=true, is_global_index=false, filter_before_indexback[true,false], |
| range_key([T1.C2], [T1.C3], [T1.C1]), range(MIN,MIN,MIN ; 1,MIN,MIN), |
| range_cond([T1.C2 < cast(1, NUMBER(-1, -85))]) |
+---------------------------------------------------------------------------------------------------------------------------------------------+
12 rows in set
上述示例中,执行计划展示中的 outputs & filters 详细展示了 TABLE SCAN 算子的输出信息如下:
| 信息名称 | 含义 |
|---|---|
| operator | TABLE SCAN 算子的 operator 有三种形式:TABLE SCAN、TABLE GET 和 TABLE SKIP SCAN。
TABLE RANGE SCAN 表示按 RANGE 条件从表中检索数据的操作。 |
| name | 选择用哪个索引来访问数据。选择的索引的名字会跟在表名后面,如果没有索引的名字,则说明执行的是主表扫描。 这里需要注意,在 OceanBase 数据库中,主表和索引的组织结构是一样的,主表本身也是一个索引。 |
| output | 该算子的输出列。 |
| filter | 该算子的过滤谓词。 示例 Q2 中 filter([T1.C2 < 1], [T1.C3 < 1], [T1.C4 < 1]) 表示对表 T1 的全表扫描的结果应用了三个过滤条件,分别是 C2 列的值小于 1,C3 列的值小于 1,以及 C4 列的值小于 1。在查询执行时,满足这些条件的行会被包含在最终的结果集中。 |
| partitions | 查询需要扫描的分区。 |
| is_index_back | 该算子是否需要回表。 例如,在 Q1 查询中,因为选择了主表,所以不需要回表。在 Q2 查询中,索引列是 (c2,c3), 由于查询需要返回 c4 列,所以需要回表。 |
| is_global_index | 该算子是否是扫描全局索引。例如,在Q2查询中,使用的索引为普通索引,所以is_global_index 为 false。在 Q3 查询中,使用了 t2 表的 i2 索引,i2 索引为全局索引,所以 is_global_index 为 true。 |
| filter_before_indexback | 与每个 filter 对应,表明该 filter 是可以直接在索引上进行计算,还是需要索引回表之后才能计算。 例如,在 Q2 查询中,当 filter 为 c3 < 1 时,可以直接在索引上计算,能减少回表数量;当 filter 为 c4 < 1 时,需要回表取出 c4 列之后才能计算。 |
| range_key/range/range_cond |
|
Q3:
obclient> CREATE TABLE t2(c1 INT PRIMARY KEY, c2 INT, c3 INT) PARTITION BY HASH(c1) PARTITIONS 4;
obclient> CREATE INDEX i2 ON t2(c2) GLOBAL;
obclient> EXPLAIN SELECT * FROM t2 WHERE c2 = 1;
+-----------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------+
| ============================================================== |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| -------------------------------------------------------------- |
| |0 |DISTRIBUTED TABLE RANGE SCAN|T2(I2)|1 |30 | |
| ============================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T2.C1], [T2.C2], [T2.C3]), filter(nil), rowset=16 |
| access([T2.C1], [T2.C2], [T2.C3]), partitions(p0) |
| is_index_back=true, is_global_index=true, |
| range_key([T2.C2], [T2.C1]), range(1,MIN ; 1,MAX), |
| range_cond([T2.C2 = 1]) |
+-----------------------------------------------------------------+
上述示例中,执行计划展示中的 outputs & filters 详细展示了 TABLE SCAN 算子的输出信息如下:
| 信息名称 | 含义 |
|---|---|
| operator | TABLE SCAN 算子的 operator 有三种形式:TABLE SCAN、TABLE GET 和 TABLE SKIP SCAN。
DISTRIBUTED TABLE RANGE SCAN 表示在查询执行时会对分布式表进行区间扫描。 |
| name | 选择用哪个索引来访问数据。选择的索引的名字会跟在表名后面,如果没有索引的名字,则说明执行的是主表扫描。 这里需要注意,在 OceanBase 数据库中,主表和索引的组织结构是一样的,主表本身也是一个索引。 |
| output | 该算子的输出列。 |
| filter | 该算子的过滤谓词。 由于示例中 Q1 查询的 TABLE GET 算子没有设置 filter,所以为 nil。 |
| partitions | 查询需要扫描的分区。 |
| is_index_back | 该算子是否需要回表。 例如,在 Q1 查询中,因为选择了主表,所以不需要回表。在 Q2 查询中,索引列是 (c2,c3), 由于 Q2 查询需要返回 c4 列,所以需要回表。 |
| is_global_index | 该算子是否是扫描全局索引。例如,在Q2查询中,使用的索引为普通索引,所以is_global_index 为 false。在 Q3 查询中,使用了 t2 表的 i2 索引,i2 索引为全局索引,所以 is_global_index 为 true。 |
| filter_before_indexback | 与每个 filter 对应,表明该 filter 是可以直接在索引上进行计算,还是需要索引回表之后才能计算。 例如,在 Q2 查询中,当 filter 为 c3 < 1 时,可以直接在索引上计算,能减少回表数量;当 filter 为 c4 < 1 时,需要回表取出 c4 列之后才能计算。 |
| range_key/range/range_cond |
|
功能适用性
OceanBase 数据库的 MYSQL 租户不支持 Q4 示例中的 CONNECT BY LEVEL 语法。
Q4:
obclient> CREATE TABLE t3(pk INT, c1 INT, c2 INT, PRIMARY KEY(pk));
obclient> INSERT INTO t3 (pk,c1,c2) SELECT LEVEL, MOD(LEVEL,3), LEVEL FROM DUAL CONNECT BY LEVEL <= 10000;
obclient> CREATE INDEX i1 ON t3(c1,c2);
obclient> EXPLAIN SELECT /*+ INDEX_SS(t3 i1) */ * FROM t3 WHERE c2 = 1;
+-----------------------------------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------------------------------+
| ================================================= |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| ------------------------------------------------- |
| |0 |TABLE SKIP SCAN|T3(I1)|1 |17 | |
| ================================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T3.PK], [T3.C1], [T3.C2]), filter([T3.C2 = 1]), rowset=16 |
| access([T3.PK], [T3.C2], [T3.C1]), partitions(p0) |
| is_index_back=false, is_global_index=false, filter_before_indexback[false], |
| range_key([T3.C1], [T3.C2], [T3.PK]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true |
| prefix_columns_cnt = 1 , skip_scan_range(1,MIN ; 1,MAX) |
+-----------------------------------------------------------------------------------------+
上述示例中,执行计划展示中的 outputs & filters 详细展示了 TABLE SCAN 算子的输出信息如下:
| 信息名称 | 含义 |
|---|---|
| operator | TABLE SCAN 算子的 operator 有三种形式:TABLE SCAN、TABLE GET 和 TABLE SKIP SCAN。
|
| name | 选择用哪个索引来访问数据。选择的索引的名字会跟在表名后面,如果没有索引的名字,则说明执行的是主表扫描。 这里需要注意,在 OceanBase 数据库中,主表和索引的组织结构是一样的,主表本身也是一个索引。 |
| output | 该算子的输出列。 |
| filter | 该算子的过滤谓词。 由于示例中 Q1 查询的 TABLE GET 算子没有设置 filter,所以为 nil。 |
| partitions | 查询需要扫描的分区。 |
| is_index_back | 该算子是否需要回表。 例如,在 Q1 查询中,因为选择了主表,所以不需要回表。在 Q2 查询中,索引列是 (c2,c3), 由于 Q2 查询需要返回 c4 列,所以需要回表。 |
| is_global_index | 该算子是否是扫描全局索引。例如,在Q2查询中,使用的索引为普通索引,所以is_global_index 为 false。在 Q3 查询中,使用了 t2 表的 i2 索引,i2 索引为全局索引,所以 is_global_index 为 true。 |
| filter_before_indexback | 与每个 filter 对应,表明该 filter 是可以直接在索引上进行计算,还是需要索引回表之后才能计算。 例如,在 Q2 查询中,当 filter 为 c3 < 1 时,可以直接在索引上计算,能减少回表数量;当 filter 为 c4 < 1 时,需要回表取出 c4 列之后才能计算。 |
| range_key/range/range_cond |
|
| prefix_columns_cnt | 表示 TABLE SKIP SCAN 前缀列的个数。 |
| skip_scan_range | 表示 TABLE SKIP SCAN 从后缀列抽取的扫描范围。 |
Q4 查询中 TABLE SKIP SCAN 可以在满足一定条件下利用已有的索引来加速扫描。OceanBase 数据库在满足如下限制条件时会尝试选择 TABLE SKIP SCAN:
- 表上有收集过统计信息。
- 查询条件中包含联合索引的后缀列,并且不是其他索引最左前缀。
- 优化器比较 TABLE SKIP SCAN 和全表扫描的代价,发现 TABLE SKIP SCAN 代价更低。
功能适用性
OceanBase 数据库的 MySQL 租户下暂不支持字符串转数值类型。
Q5:
obclient> CREATE TABLE t1(c1 NUMBER PRIMARY KEY, c2 DATE);
Query OK, 0 rows affected (0.17 sec)
obclient> CREATE INDEX i1 ON t1(c2);
Query OK, 0 rows affected (0.51 sec)
obclient> EXPLAIN SELECT /*+ index(t1 i1) */ c2 FROM t1 WHERE c2 = TIMESTAMP '2024-01-01 11:22:00';
+-----------------------------------------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------------------------------------+
| ================================================== |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| -------------------------------------------------- |
| |0 |TABLE RANGE SCAN|T1(I1)|1 |4 | |
| ================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T1.C2]), filter(nil), rowset=16 |
| access([T1.C2]), partitions(p0) |
| is_index_back=false, is_global_index=false, |
| range_key([T1.C2], [T1.C1]), range(2024-01-01 11:22:00,MIN ; 2024-01-01 11:22:00,MAX), |
| range_cond([cast(T1.C2, TIMESTAMP(19, 9)) = '2024-01-01 11:22:00.000000000']) |
+-----------------------------------------------------------------------------------------------+
上述示例中,执行计划展示中的 outputs & filters 详细展示了 TABLE RANGE SCAN 算子的输出信息如下:
| 信息名称 | 含义 |
|---|---|
| operator | TABLE SCAN 算子的 operator 有三种形式:TABLE SCAN、TABLE GET 和 TABLE SKIP SCAN。
|
| name | 选择用哪个索引来访问数据。选择的索引的名字会跟在表名后面,如果没有索引的名字,则说明执行的是主表扫描。 这里需要注意,在 OceanBase 数据库中,主表和索引的组织结构是一样的,主表本身也是一个索引。 |
| output | 该算子的输出列。 |
| filter | 该算子的过滤谓词。 由于示例中 Q5 查询的 TABLE RANGE SCAN 算子没有设置 filter,所以为 nil。 |
| partitions | 查询需要扫描的分区。 |
| is_index_back | 该算子是否需要回表。 例如,在 Q1 查询中,因为选择了主表,所以不需要回表。在 Q2 查询中,索引列是 (c2,c3,c1), 由于查询需要返回 c4 列,所以需要回表。 |
| is_global_index | 该算子是否是扫描全局索引。例如,在Q2查询中,使用的索引为普通索引,所以is_global_index 为 false。在 Q3 查询中,使用了 t2 表的 i2 索引,i2 索引为全局索引,所以 is_global_index 为 true。 |
| range_key/range/range_cond |
|