首批通过分布式安全可靠测评,为关键业务系统打造
TABLE SCAN
更新时间:2023-12-20 14:46:00
TABLE SCAN 算子是存储层和 SQL 层的接口,用于展示优化器选择哪个索引来访问数据。
在 OceanBase 数据库 V4.1.0 版本中,对于普通索引和全局索引,索引回表的逻辑是封装在 TABLE SCAN 算子中的;在执行计划展示时,会有 is_index_back 标识算子是否需要回表,以及 is_global_index 标识算子是否是扫描全局索引。
示例:含 TABLE SCAN 算子的执行计划
obclient> CREATE TABLE t1(c1 INT PRIMARY KEY, c2 INT, c3 INT, c4 INT,
INDEX k1(c2,c3));
Query OK, 0 rows affected
Q1:
obclient> EXPLAIN EXTENDED SELECT * FROM t1 WHERE c1 = 1\G
*************************** 1. row ***************************
Query Plan:
| ==================================
|ID|OPERATOR |NAME|EST. ROWS|COST|
----------------------------------
|0 |TABLE GET|t1 |1 |53 |
==================================
Outputs & filters:
-------------------------------------
0 - output([t1.c1(0x7f22fbe69340)], [t1.c2(0x7f22fbe695c0)], [t1.c3(0x7f22fbe69840)], [t1.c4(0x7f22fbe69ac0)]), filter(nil),
access([t1.c1(0x7f22fbe69340)], [t1.c2(0x7f22fbe695c0)], [t1.c3(0x7f22fbe69840)], [t1.c4(0x7f22fbe69ac0)]), partitions(p0),
is_index_back=false,
range_key([t1.c1(0x7f22fbe69340)]), range[1 ; 1],
range_cond([t1.c1(0x7f22fbe69340) = 1(0x7f22fbe68cf0)])
Q2:
obclient> EXPLAIN EXTENDED SELECT * FROM t1 WHERE c2 < 1 AND c3 < 1 AND
c4 < 1\G
*************************** 1. row ***************************
Query Plan:
| ======================================
|ID|OPERATOR |NAME |EST. ROWS|COST |
--------------------------------------
|0 |TABLE SCAN|t1(k1)|100 |12422|
======================================
Outputs & filters:
-------------------------------------
0 - output([t1.c1(0x7f22fbd1e220)], [t1.c2(0x7f227decec40)], [t1.c3(0x7f227decf9b0)], [t1.c4(0x7f22fbd1dfa0)]), filter([t1.c3(0x7f227decf9b0) < 1(0x7f227decf360)], [t1.c4(0x7f22fbd1dfa0) < 1(0x7f22fbd1d950)]),
access([t1.c2(0x7f227decec40)], [t1.c3(0x7f227decf9b0)], [t1.c4(0x7f22fbd1dfa0)], [t1.c1(0x7f22fbd1e220)]), partitions(p0),
is_index_back=true, filter_before_indexback[true,false],
range_key([t1.c2(0x7f227decec40)], [t1.c3(0x7f227decf9b0)], [t1.c1(0x7f22fbd1e220)]),
range(NULL,MAX,MAX ; 1,MIN,MIN),
range_cond([t1.c2(0x7f227decec40) < 1(0x7f227dece5f0)])
Q3:
obclient> CREATE TABLE t2(c1 INT PRIMARY KEY, c2 INT, c3 INT) PARTITION BY
HASH(c1) PARTITIONS 4;
Query OK, 0 rows affected
obclient> CREATE INDEX i2 ON t2(c2) GLOBAL;
Query OK, 0 rows affected
obclient> EXPLAIN SELECT * FROM t2 WHERE c2 = 1;
+------------------------------------------------------------------+
| Query Plan |
+------------------------------------------------------------------+
| ======================================================== |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| -------------------------------------------------------- |
| |0 |DISTRIBUTED TABLE SCAN|t2(i2)|1 |28 | |
| ======================================================== |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([t2.c1], [t2.c2], [t2.c3]), filter(nil), rowset=256 |
| 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]) |
+------------------------------------------------------------------+
O4:
obclient> CREATE TABLE t3(pk INT, c1 INT, c2 INT, PRIMARY KEY(pk));
Query OK, 0 rows affected
obclient> INSERT INTO t3 (pk,c1,c2) SELECT LEVEL, MOD(LEVEL,3), LEVEL FROM DUAL CONNECT BY LEVEL <= 10000;
Query OK, 10000 rows affected
Records: 10000 Duplicates: 0 Warnings: 0
obclient> CREATE INDEX i1 ON t3(c1,c2);
Query OK, 0 rows affected
obclient> EXPLAIN SELECT * FROM t3 WHERE c2 = 1;
+-----------------------------------------------------------------------------------------+
| Query Plan |
+-----------------------------------------------------------------------------------------+
| ================================================= |
| |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| |
| ------------------------------------------------- |
| |0 |INDEX SKIP SCAN|T3(I1)|1 |11 | |
| ================================================= |
| Outputs & filters: |
| ------------------------------------- |
| 0 - output([T3.PK], [T3.C1], [T3.C2]), filter([T3.C2 = 1]), rowset=256 |
| 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) |
+-----------------------------------------------------------------------------------------+
12 rows in set
上述示例中,执行计划展示中的 outputs & filters 详细展示了 TABLE SCAN 算子的输出信息如下:
| 信息名称 | 含义 |
|---|---|
| operator | TABLE SCAN 算子的 operator 有三种形式:TABLE SCAN、TABLE GET 和 INDEX SKIP SCAN。
|
| name | 选择用哪个索引来访问数据。选择的索引的名字会跟在表名后面,如果没有索引的名字,则说明执行的是主表扫描。 这里需要注意,在 OceanBase 数据库中,主表和索引的组织结构是一样的,主表本身也是一个索引。更多信息,可以参考 索引的存储。 |
| output | 该算子的输出列。 |
| filter | 该算子的过滤谓词。 由于示例中 Q1 查询的 TABLE GET 算子没有设置 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 。 |
| filter_before_indexback | 与每个 filter 对应,表明该 filter 是可以直接在索引上进行计算,还是需要索引回表之后才能计算。 例如,在 Q2 查询中,当 filter 为 c3 < 1 时, 可以直接在索引上计算,能减少回表数量;当 filter 为 c4 < 1 时,需要回表取出 c4 列之后才能计算。 |
| range_key/range/range_cond |
|
| prefix_columns_cnt | 表示 INDEX SKIP SCAN 前缀列的个数。 |
| skip_scan_range | 表示 INDEX SKIP SCAN 从后缀列抽取的扫描范围。 |
Q4 查询中 INDEX SKIP SCAN 可以在满足一定条件下利用已有的索引来加速扫描。OceanBase 数据库在满足如下限制条件时会尝试选择 INDEX SKIP SCAN:
- 表上有收集过统计信息。
- 查询条件中包含联合索引的后缀列,并且不是其他索引最左前缀。
- 优化器比较 Index Skip Scan 和全表扫描的代价,发现 Index Skip Scan 代价更低。