---
title: "TABLE SCAN - OceanBase 数据库 V4.5.0 | OceanBase 文档中心"
description: TABLE SCAN TABLE SCAN 算子是存储层和 SQL 层的接口，用于展示优化器选择哪个索引来访问数据。 在 OceanBase 数据库 V4.1.0 版本中，对于普通索引和全局索引，索引回表的逻辑是封装在 TABLE SCAN 算子中的；在执行计划展示时，会有 is_index_back 标识算子是否需…
---
切换语言

- 中文站 - 简体中文
- International - English
- 日本站 - 日本語

文档反馈![](https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*P8CuR4UJ_FkAAAAAAAAAAAAADiGDAQ/original) OceanBase 数据库分布式版 - V 4.5.0

# TABLE SCAN

更新时间：2025-11-27 19:46:27

[编辑](https://github.com/oceanbase/oceanbase-doc/edit/V4.5.0/zh-CN/700.reference/1000.performance-tuning-guide/500.sql-optimization/200.sql-execution-plan/200.execution-plan-operator/100.table-scan.md)  

`TABLE SCAN` 算子是存储层和 SQL 层的接口，用于展示优化器选择哪个索引来访问数据。

在 OceanBase 数据库 V4.1.0 版本中，对于普通索引和全局索引，索引回表的逻辑是封装在 `TABLE SCAN` 算子中的；在执行计划展示时，会有 `is_index_back` 标识算子是否需要回表，以及 `is_global_index` 标识算子是否是扫描全局索引。 示例：含 `TABLE SCAN` 算子的执行计划

```sql
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`。   - `TABLE SCAN` 属于范围扫描，会返回 0 行或者多行数据。 - `TABLE GET` 直接用主键定位，返回 0 行或者 1 行数据。 - `TABLE SKIP SCAN` 属于范围扫描，跳跃执行，会返回 0 行或者多行数据。 |
| 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 | - `range_key`：索引的 `rowkey` 列。 - `range`：索引开始扫描和结束扫描的位置。判断是否是全表扫描需要关注 `range` 的范围。例如，对于一个 `rowkey` 有三列的场景，`range(MIN,MIN, MIN ; MAX, MAX, MAX)` 代表的就是真正意义上的全表扫描。 - `range_cond`：决定索引开始扫描和结束扫描位置的相关谓词。 |

```sql
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

```

| **信息名称** | **含义** |
| --- | --- |
| operator | `TABLE SCAN` 算子的 `operator` 有三种形式：`TABLE SCAN`、`TABLE GET` 和 `TABLE SKIP SCAN`。   - `TABLE SCAN` 属于范围扫描，会返回 0 行或者多行数据。 - `TABLE GET` 直接用主键定位，返回 0 行或者 1 行数据。 - `TABLE SKIP SCAN` 属于范围扫描，跳跃执行，会返回 0 行或者多行数据。  在 Q2 的查询中，`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 | - `range_key`：索引的 `rowkey` 列。 - `range`：索引开始扫描和结束扫描的位置。判断是否是全表扫描需要关注 `range` 的范围。例如，对于一个 `rowkey` 有三列的场景，`range(MIN,MIN, MIN ; MAX, MAX, MAX)` 代表的就是真正意义上的全表扫描。 - `range_cond`：决定索引开始扫描和结束扫描位置的相关谓词。 |

```sql
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])                                   |
+-----------------------------------------------------------------+

```

| **信息名称** | **含义** |
| --- | --- |
| operator | `TABLE SCAN` 算子的 `operator` 有三种形式：`TABLE SCAN`、`TABLE GET` 和 `TABLE SKIP SCAN`。   - `TABLE SCAN` 属于范围扫描，会返回 0 行或者多行数据。 - `TABLE GET` 直接用主键定位，返回 0 行或者 1 行数据。 - `TABLE SKIP SCAN` 属于范围扫描，跳跃执行，会返回 0 行或者多行数据。   在 Q3 的查询中，`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 | - `range_key`：索引的 `rowkey` 列。 - `range`：索引开始扫描和结束扫描的位置。判断是否是全表扫描需要关注 `range` 的范围。例如，对于一个 `rowkey` 有三列的场景，`range(MIN,MIN, MIN ; MAX, MAX, MAX)` 代表的就是真正意义上的全表扫描。 - `range_cond`：决定索引开始扫描和结束扫描位置的相关谓词。 |

#### 功能适用性

OceanBase 数据库的 MYSQL 租户不支持 Q4 示例中的 **CONNECT BY LEVEL** 语法。

```sql
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)                           |
+-----------------------------------------------------------------------------------------+

```

| **信息名称** | **含义** |
| --- | --- |
| operator | `TABLE SCAN` 算子的 `operator` 有三种形式：`TABLE SCAN`、`TABLE GET` 和 `TABLE SKIP SCAN`。   - `TABLE SCAN` 属于范围扫描，会返回 0 行或者多行数据。 - `TABLE GET` 直接用主键定位，返回 0 行或者 1 行数据。 - `TABLE SKIP SCAN` 属于范围扫描，跳跃执行，会返回 0 行或者多行数据。 |
| 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 | - `range_key`：索引的 `rowkey` 列。 - `range`：索引开始扫描和结束扫描的位置。判断是否是全表扫描需要关注 `range` 的范围。例如，对于一个 `rowkey` 有三列的场景，`range(MIN,MIN, MIN ; MAX, MAX, MAX)` 代表的就是真正意义上的全表扫描。 - `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 租户下暂不支持字符串转数值类型。

```sql
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`。   - `TABLE SCAN` 属于范围扫描，会返回 0 行或者多行数据。 - `TABLE GET` 直接用主键定位，返回 0 行或者 1 行数据。 - `TABLE SKIP SCAN` 属于范围扫描，跳跃执行，会返回 0 行或者多行数据。 - `TABLE RANGE SCAN` 属于范围扫描，会返回 0 行或者多行数据。 |
| 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 | - `range_key`：索引的 `rowkey` 列。 - `range`：索引开始扫描和结束扫描的位置。判断是否是全表扫描需要关注 `range` 的范围。例如，对于一个 `rowkey` 有三列的场景，`range(MIN,MIN, MIN ; MAX, MAX, MAX)` 代表的就是真正意义上的全表扫描。 - `range_cond`：决定索引开始扫描和结束扫描位置的相关谓词。 |

 上一篇 下一篇 ![有帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*y6ocSqN8cqsAAAAAAAAAAAAAARQnAQ)![无帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*BG9IQJyLHF8AAAAAAAAAAAAAARQnAQ)![反馈](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*eTWdQKCRKHwAAAAAAAAAAAAAARQnAQ)[AI](https://www.oceanbase.com/obi) 咨询热线
