The TABLE SCAN operator is an interface between the storage layer and the SQL layer, used to indicate which index (or base table) the optimizer has selected for data access.
Starting from OceanBase Database V4.1.0, the logic for index backtracking is encapsulated within the TABLE SCAN operator for both regular and global indexes. In the execution plan, the is_index_back attribute indicates whether the operator requires backtracking, and the is_global_index attribute indicates whether the operator is scanning a global index.
TABLE SCAN operator types
TABLE SCAN represents a series of basic operations for reading data from a table. Depending on the query conditions and data access paths (whether an index is used, which index is used, and the scanning method), TABLE SCAN is displayed as follows in OPERATOR:
Operator type |
Description |
Number of returned rows |
Trigger conditions |
|---|---|---|---|
| TABLE GET | Exact match on the primary key, single-row access. Locates unique or non-existent data rows based on primary key equality conditions. | 0 rows or 1 row. | WHERE conditions include primary key equality queries, such as WHERE primary_key = ?. |
| TABLE RANGE SCAN | Scans data intervals that satisfy one or more range conditions (range_cond) based on the ordered nature of the index key values. |
0 rows or multiple rows, depending on the selectivity of range_cond. |
WHERE conditions include range queries on indexed columns, such as WHERE indexed_column > ?. |
| DISTRIBUTED TABLE RANGE SCAN | Cross-partition scan of global indexes or distributed tables. Distributed continuous range: interval scans are performed on multiple partitions of a global index. | 0 rows or multiple rows, depending on the number of matching rows in all partitions. | Queries on the global index of a partitioned table when the query conditions cannot locate the data to a single partition. |
| TABLE SKIP SCAN | Skips scanning the suffix columns of a composite index. Multi-segment skip range: first enumerates different values of the index prefix columns, then scans specified intervals on the suffix columns for each value. | 0 rows or multiple rows, depending on the number of different prefix column values and the scan results of each segment. | WHERE conditions only include non-leftmost columns of a composite index, and all queried columns are covered by the index (covering index scan), such as WHERE index(col1, col2) with the condition col2 = ?. |
| TABLE FULL SCAN | Full table scan. No range restrictions, scans all data. | All rows. | No effective index or forced full table scan. |
TABLE GET
TABLE GET directly locates data using the primary key (Primary Key). Each execution returns 0 rows or 1 row of data. It is typically used in scenarios where the WHERE condition includes primary key equality queries.
TABLE GET Example
Create a table
tbl1and define columncol1as the primary key.obclient> CREATE TABLE tbl1(col1 INT PRIMARY KEY, col2 INT, col3 INT, col4 INT);Q1: View the execution plan containing the
TABLE GETtype.obclient> EXPLAIN SELECT * FROM tbl1 WHERE col1 = 1;The result is as follows:
+------------------------------------------------------------------------------------------+ | Query Plan | +------------------------------------------------------------------------------------------+ | ========================================= | | |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| | | ----------------------------------------- | | |0 |TABLE GET|TBL1|1 |5 | | | ========================================= | | Outputs & filters: | | ------------------------------------- | | 0 - output([TBL1.COL1], [TBL1.COL2], [TBL1.COL3], [TBL1.COL4]), filter(nil), rowset=16 | | access([TBL1.COL1], [TBL1.COL2], [TBL1.COL3], [TBL1.COL4]), partitions(p0) | | is_index_back=false, is_global_index=false, | | range_key([TBL1.COL1]), range[1 ; 1], | | range_cond([TBL1.COL1 = 1]) | +------------------------------------------------------------------------------------------+ 12 rows in set
In the Q1 query result, the operator type (OPERATOR) is TABLE GET. The detailed explanation of each parameter in the Outputs & filters section of the execution plan is as follows:
Information name |
Description |
Example explanation |
|---|---|---|
| output | The list of columns output by the operator. | output([TBL1.COL1], [TBL1.COL2], [TBL1.COL3], [TBL1.COL4]) indicates that the query will return the col1, col2, col3, and col4 columns from the tbl1 table. |
| filter | The filtering conditions (predicates) to be applied by the operator. | filter(nil) indicates no additional filtering conditions, and data is directly output after being located by range_cond. |
| rowset | The vectorized size of the current operator. | rowset=16 indicates the vectorized size of the current operator is 16. |
| access | The list of columns needed to be accessed by the operator to complete the computation. The order may differ from output and is typically related to the index structure. |
access([TBL1.COL1], [TBL1.COL2], [TBL1.COL3], [TBL1.COL4]) indicates the values of col1, col2, col3, and col4 need to be read. |
| partitions | The partitions to be scanned by the query. | partitions(p0) indicates that the query only accesses the partition named p0.
NoteIf the table is not a partitioned table, the entire table is considered a single partition named |
| is_index_back | Indicates whether the operator requires a "back to table" operation. That is, whether it needs to retrieve data from other columns in the main table after locating the primary key through the index. | is_index_back=false indicates no back to table operation is needed. In Q1, since the primary key (col1) is used for exact lookup and all columns are needed, and the primary key index (main table) already contains all column data, no back to table operation is needed. |
| is_global_index | Indicates whether a global index is used in the query. | is_global_index=false indicates no global index is used. |
| range_key | The row key (rowkey) column that determines the sorting method of the scanned range. For a table (main table), the row key is the primary key; for an index, the row key is the index key. |
range_key([TBL1.COL1]) indicates that the scan range is determined by the primary key column col1. |
| range | The start and end range of the index scan, in the format (start key value ; end key value). |
range[1 ; 1] indicates that the scan range is from col1=1 to col1=1, which is an exact point query, not a range scan. |
| range_cond | The query conditions that determine the above scan range (range). |
range_cond([TBL1.COL1 = 1]) directly corresponds to the WHERE col1 = 1 condition in the SQL statement. The optimizer deduces range[1 ; 1] based on this condition. |
TABLE RANGE SCAN
TABLE RANGE SCAN is a type of range scan. It scans a continuous range of an index or the primary table based on the given query conditions (range predicate) and returns zero or more rows. This type is suitable for queries with range conditions such as >, <, BETWEEN, and LIKE prefix matching.
TABLE RANGE SCAN example
Create a table named
tbl2and define thecol1column as the primary key.obclient> CREATE TABLE tbl2(col1 INT PRIMARY KEY, col2 INT, col3 INT, col4 INT);Create an index named
idx_tbl2on thecol2andcol3columns of thetbl2table.obclient> CREATE INDEX idx_tbl2 ON tbl2(col2, col3);Q2: View the execution plan containing the
TABLE RANGE SCANtype.obclient> EXPLAIN SELECT * FROM tbl2 WHERE col2 < 1 AND col3 < 1 AND col4 < 1;The returned result is as follows:
+-----------------------------------------------------------------------------------------------------------------------+ | Query Plan | +-----------------------------------------------------------------------------------------------------------------------+ | ========================================================== | | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| | | ---------------------------------------------------------- | | |0 |TABLE RANGE SCAN|TBL2(IDX_TBL2)|1 |5 | | | ========================================================== | | Outputs & filters: | | ------------------------------------- | | 0 - output([TBL2.COL1], [TBL2.COL2], [TBL2.COL3], [TBL2.COL4]), filter([TBL2.COL3 < 1], [TBL2.COL4 < 1]), rowset=16 | | access([TBL2.COL1], [TBL2.COL2], [TBL2.COL3], [TBL2.COL4]), partitions(p0) | | is_index_back=true, is_global_index=false, filter_before_indexback[true,false], | | range_key([TBL2.COL2], [TBL2.COL3], [TBL2.COL1]), range(MIN,MIN,MIN ; 1,MIN,MIN), | | range_cond([TBL2.COL2 < 1]) | +-----------------------------------------------------------------------------------------------------------------------+ 12 rows in set
In the Q2 query result, the operator type (OPERATOR) is TABLE RANGE SCAN. The detailed explanation of each parameter in the Outputs & filters section of the execution plan is as follows:
Information |
Description |
Example |
|---|---|---|
| output | The list of columns output by the operator. | output([TBL2.COL1], [TBL2.COL2], [TBL2.COL3], [TBL2.COL4]) indicates that the query will return the col1, col2, col3, and col4 columns from the tbl2 table. |
| filter | The filtering conditions (predicates) to be applied by the operator. | filter([TBL2.COL3 < 1], [TBL2.COL4 < 1]) indicates two filters: col3 < 1 and col4 < 1. The col2 < 1 condition in the WHERE clause is not listed here because it is used to determine the scan range (range_cond). |
| rowset | The vectorized size of the current operator. | rowset=16 indicates that the vectorized size of the current operator is 16. |
| access | The list of columns to be accessed by the operator to complete the computation. The order may differ from output, and it is typically related to the index structure. |
access([TBL2.COL1], [TBL2.COL2], [TBL2.COL3], [TBL2.COL4]) indicates that the values of the col1, col2, col3, and col4 columns need to be read. |
| partitions | The partitions to be scanned by the query. | partitions(p0) indicates that the query only accesses the partition named p0.
NoteIf the table is not partitioned, the entire table is considered a single partition named |
| is_index_back | Indicates whether the operator requires a "table lookup" operation. | is_index_back=true indicates that a table lookup is required. Since the query uses the index idx_tbl2 (based on col2 and col3), but the col4 column, which needs to be returned, is not part of this index, a table lookup is necessary to obtain the value. |
| is_global_index | Indicates whether the query uses a global index. | is_global_index=false indicates that a global index is not used. |
| filter_before_indexback | A boolean array corresponding to the conditions in filter, indicating whether the filter can be directly computed on the index or requires a table lookup. |
filter_before_indexback[true,false] corresponds to the two conditions in filter:
|
| range_key | The row key (rowkey) of the scanned range, which determines the data sorting method. For the table (primary table), the row key is the primary key; for the index, the row key is the index key. |
range_key([TBL2.COL2], [TBL2.COL3], [TBL2.COL1]) indicates that the actual key order of the index idx_tbl2 is (col2, col3), with the primary key col1 automatically appended to ensure uniqueness. |
| range | The start and end range of the index scan, formatted as (start key value ; end key value). |
range(MIN,MIN,MIN ; 1,MIN,MIN) defines the specific scan boundaries:
|
| range_cond | The query conditions that determine the above scan range (range). |
range_cond([TBL2.COL2 < 1]) indicates that only the col2 < 1 condition is used to quickly locate the start and end points of the index scan. Other conditions (col3 < 1, col4 < 1) are used for filtering after the scan. |
DISTRIBUTED TABLE RANGE SCAN
DISTRIBUTED TABLE RANGE SCAN is an operation for scanning a range of data in a global index or distributed table. When a table is partitioned and a query uses a global index, the optimizer selects this operator to scan the index data across partitions.
DISTRIBUTED TABLE RANGE SCAN Example
Create a hash-partitioned table
tbl3and define thecol1column as the primary key.obclient> CREATE TABLE tbl3(col1 INT PRIMARY KEY, col2 INT, col3 INT) PARTITION BY HASH(col1) PARTITIONS 4;Create a global index
idx_tbl3on thecol2column of thetbl3table.obclient> CREATE INDEX idx_tbl3 ON tbl3(col2) GLOBAL;Q3: View the execution plan that includes the
DISTRIBUTED TABLE RANGE SCANoperator.obclient> EXPLAIN SELECT * FROM tbl3 WHERE col2 = 1;The result is as follows:
+-----------------------------------------------------------------------------+ | Query Plan | +-----------------------------------------------------------------------------+ | ====================================================================== | | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| | | ---------------------------------------------------------------------- | | |0 |DISTRIBUTED TABLE RANGE SCAN|TBL3(IDX_TBL3)|1 |5 | | | ====================================================================== | | Outputs & filters: | | ------------------------------------- | | 0 - output([TBL3.COL1], [TBL3.COL2], [TBL3.COL3]), filter(nil), rowset=16 | | access([TBL3.COL1], [TBL3.COL2], [TBL3.COL3]), partitions(p0) | | is_index_back=true, is_global_index=true, | | range_key([TBL3.COL2], [TBL3.COL1]), range(1,MIN ; 1,MAX), | | range_cond([TBL3.COL2 = 1]) | +-----------------------------------------------------------------------------+ 12 rows in set
In the Q3 query result, the operator type (OPERATOR) is DISTRIBUTED TABLE RANGE SCAN. The detailed explanation of each parameter in the Outputs & filters section of the execution plan is as follows:
Information Name |
Description |
Example Explanation |
|---|---|---|
| output | The list of columns output by the operator. | output([TBL3.COL1], [TBL3.COL2], [TBL3.COL3]) indicates that the query will return the col1, col2, and col3 columns from the tbl1 table. |
| filter | The filtering conditions (predicates) to be applied by the operator. | filter(nil) indicates that no additional filtering conditions are needed. The condition col2 = 1 is already applied as a range condition (range_cond) during the index scan, so no further filtering is required. |
| rowset | The vectorized size of the current operator. | rowset=16 indicates that the vectorized size of the current operator is 16. |
| access | The list of columns that the operator needs to access for computation. The order may differ from output and is typically related to the index structure. |
access([TBL3.COL1], [TBL3.COL2], [TBL3.COL3]) indicates that the values of the col1, col2, and col3 columns need to be read. |
| partitions | The partitions to be scanned by the query. | partitions(p0) indicates that the query only accesses the partition named p0. The TBL3 table is a hash-partitioned table with 4 partitions. The optimizer uses the global index idx_tbl3 to locate all data rows where col2 = 1 in the p0 partition, so only this partition needs to be scanned, avoiding a full table scan. |
| is_index_back | Indicates whether the operator requires a "table lookup" operation. | is_index_back=true indicates that a table lookup is required. The index idx_tbl3 contains only the col2 and primary key col1 columns. To obtain the value of col3, the system must use the primary key col1 to look up the data partition in the main table after finding the target row via the index. |
| is_global_index | Indicates whether the query uses a global index. | is_global_index=true indicates that a global index is used. The scan uses the global index idx_tbl3. A global index is independent of the table's partitioning structure and can be distributed across different partitions from the main table. It is suitable for queries like Q3, which are performed on non-partitioning keys. |
| range_key | The row key (rowkey) column that determines the sorting of data in the scan range. For the main table, the row key is the primary key; for the index, the row key is the index key. |
range_key([TBL3.COL2], [TBL3.COL1]) indicates that the scan first sorts by the index's first column col2, and in cases where col2 values are the same, it then sorts by the primary key col1. |
| range | The start and end range of the index scan, formatted as (start key value ; end key value). |
range(1,MIN ; 1,MAX) defines the specific scan boundaries. Since the query condition is col2 = 1, all index entries with col2 equal to 1 are scanned. The start point is (1, minimum value), and the end point is (1, maximum value), covering all possible col1 values under this condition. |
| range_cond | The query condition that determines the scan range (range). |
range_cond([TBL3.COL2 = 1]) indicates that the scan range (range) is determined by the query condition col2 = 1. This condition allows the optimizer to efficiently perform a range scan using the index, rather than a full index scan. |
TABLE SKIP SCAN
TABLE SKIP SCAN is a special type of range scan that allows the optimizer to "jump" over the prefix columns of a composite index and directly scan the suffix columns. When the query conditions include a non-leading column of a composite index and that column has good selectivity, the optimizer may choose this operator to avoid a full table scan.
TABLE SKIP SCAN example
Create a table
tbl4and define columncol1as the primary key.obclient> CREATE TABLE tbl4(col1 INT PRIMARY KEY, col2 INT, col3 INT);Create an index
idx_tbl4on columnscol2andcol3of the tabletbl4.obclient> CREATE INDEX idx_tbl4 ON tbl4(col2, col3);Q4: View the execution plan that includes
TABLE SKIP SCAN.obclient> EXPLAIN SELECT /*+ INDEX_SS(tbl4 idx_tbl4) */ * FROM tbl4 WHERE col3 = 1;Note
The
INDEX_SShint specifies that the index skip scan should be performed using a specific index on the table object.The result is as follows:
+-----------------------------------------------------------------------------------------------------+ | Query Plan | +-----------------------------------------------------------------------------------------------------+ | ========================================================= | | |ID|OPERATOR |NAME |EST.ROWS|EST.TIME(us)| | | --------------------------------------------------------- | | |0 |TABLE SKIP SCAN|TBL4(IDX_TBL4)|1 |3 | | | ========================================================= | | Outputs & filters: | | ------------------------------------- | | 0 - output([TBL4.COL1], [TBL4.COL2], [TBL4.COL3]), filter([TBL4.COL3 = 1]), rowset=16 | | access([TBL4.COL1], [TBL4.COL3], [TBL4.COL2]), partitions(p0) | | is_index_back=false, is_global_index=false, filter_before_indexback[false], | | range_key([TBL4.COL2], [TBL4.COL3], [TBL4.COL1]), range(MIN,MIN,MIN ; MAX,MAX,MAX)always true | | prefix_columns_cnt = 1 , skip_scan_range(1,MIN ; 1,MAX) | +-----------------------------------------------------------------------------------------------------+ 12 rows in set
In the Q4 query result, the operator type (OPERATOR) is TABLE SKIP SCAN. The following table explains the parameters in the Outputs & filters section of the execution plan:
Parameter |
Description |
Example |
|---|---|---|
| output | The list of columns output by the operator. | output([TBL4.COL1], [TBL4.COL2], [TBL4.COL3]) indicates that the query will return the col1, col2, and col3 columns from the tbl4 table. |
| filter | The filtering conditions (predicates) to be applied by the operator. | filter([TBL4.COL3 = 1]) indicates that the col3 = 1 filter will be applied after data scanning. |
| rowset | The vectorized size of the current operator. | rowset=16 indicates that the vectorized size of the current operator is 16. |
| access | The list of columns that the operator needs to access to complete the computation. The order may differ from output and is typically related to the index structure. |
access([TBL4.COL1], [TBL4.COL3], [TBL4.COL2]) indicates that the values of the col1, col2, and col3 columns need to be read. |
| partitions | The partitions to be scanned by the query. | partitions(p0) indicates that the query only accesses the partition named p0.
NoteIf the table is not partitioned, the entire table is considered a single partition named |
| is_index_back | Indicates whether the operator requires a "back to table" operation. | is_index_back=false indicates that no back to table operation is needed for this scan. |
| is_global_index | Indicates whether the query uses a global index. | is_global_index=false indicates that a regular index, not a global index, is being used. |
| filter_before_indexback | A boolean array corresponding to the conditions in filter, indicating whether the filter can be computed directly on the index or needs to be computed after a back to table operation. |
filter_before_indexback[false] corresponds to the single filter condition col3 = 1. false indicates that this condition cannot be applied during the scan of the idx_tbl4 index because col3 is not the leading column of the composite index (col2, col3, col1). This is the reason for triggering the SKIP SCAN. |
| range_key | The row key (rowkey) columns that determine the sorting order of the scanned data. For a table (main table), the row key is the primary key; for an index, the row key is the index key. |
range_key([TBL4.COL2], [TBL4.COL3], [TBL4.COL1]) indicates that the scan will first use the leading column col2 of the index, then the second column col3, and finally the primary key col1 if col2 and col3 have the same values. |
| range | The start and end range of the index scan, in the format (start key value ; end key value). |
range(MIN,MIN,MIN ; MAX,MAX,MAX)always true indicates that the optimizer plans to scan the entire index. always true is because the query condition col3=1 does not include the leading column col2 of the index, so the optimizer cannot determine the exact start and end points at the index level. |
| prefix_columns_cnt | A parameter specific to the TABLE SKIP SCAN type. Indicates the number of prefix columns skipped during the skip scan. |
prefix_columns_cnt = 1 indicates that the first column (col2) of the index key is skipped, and the scan starts directly from the second column (col3). |
| skip_scan_range | A parameter specific to the TABLE SKIP SCAN type. Indicates the scan range extracted from the suffix columns of the index. |
skip_scan_range(1,MIN ; 1,MAX) indicates that for each unique value of the skipped leading column col2, a range scan is performed for col3=1, and the results are merged. Here, (1, MIN ; 1, MAX) illustrates the scan range for a specific col2 value at the col3=1 point. |
In the Q4 query, TABLE SKIP SCAN can accelerate the scan by utilizing existing indexes under certain conditions. OceanBase Database will attempt to select TABLE SKIP SCAN if the following conditions are met:
- Statistics have been collected on the table.
- The query conditions include a suffix column of a composite index and this column is not the leading prefix of another index.
- The optimizer compares the cost of
TABLE SKIP SCANwith that of a full table scan and finds thatTABLE SKIP SCANis cheaper.
TABLE FULL SCAN
TABLE FULL SCAN indicates a full table scan. A range value of (MIN ; MAX) in the execution plan indicates a full table scan. This typically occurs when no suitable index exists or when the optimizer determines that a full table scan is more cost-effective.
TABLE FULL SCAN example
Create a table named
tbl5.obclient> CREATE TABLE tbl5(col1 INT, col2 INT, col3 INT);Execute Q5 to view the execution plan that includes the
TABLE FULL SCANoperator.obclient> EXPLAIN SELECT * FROM tbl5 WHERE col1 = 1;The query returns the following result:
+-----------------------------------------------------------------------------------------+ | Query Plan | +-----------------------------------------------------------------------------------------+ | =============================================== | | |ID|OPERATOR |NAME|EST.ROWS|EST.TIME(us)| | | ----------------------------------------------- | | |0 |TABLE FULL SCAN|TBL5|1 |3 | | | =============================================== | | Outputs & filters: | | ------------------------------------- | | 0 - output([TBL5.COL1], [TBL5.COL2], [TBL5.COL3]), filter([TBL5.COL1 = 1]), rowset=16 | | access([TBL5.COL1], [TBL5.COL2], [TBL5.COL3]), partitions(p0) | | is_index_back=false, is_global_index=false, filter_before_indexback[false], | | range_key([TBL5.__pk_increment]), range(MIN ; MAX)always true | +-----------------------------------------------------------------------------------------+ 11 rows in set
In the query result of Q5, the operator type (OPERATOR) is TABLE FULL SCAN. The following table describes the parameters in the Outputs & filters section of the execution plan.
Parameter |
Description |
Example |
|---|---|---|
| output | The list of columns output by the operator. | output([TBL5.COL1], [TBL5.COL2], [TBL5.COL3]) indicates that the query will return the col1, col2, and col3 columns from the tbl5 table. |
| filter | The filter condition (predicate) to be applied by the operator. | filter([TBL5.COL1 = 1]) indicates that the filter condition col1 = 1 will be applied during the scan. |
| rowset | The vectorized size of the current operator. | rowset=16 indicates that the vectorized size of the current operator is 16. |
| access | The list of columns that the operator needs to access to perform the calculation. The order may differ from output and is typically related to the index structure. |
access([TBL5.COL1], [TBL5.COL2], [TBL5.COL3]) indicates that the values of the col1, col2, and col3 columns need to be read. |
| partitions | The partitions to be scanned by the query. | partitions(p0) indicates that the query only accesses the partition named p0.
NoteIf the table is not partitioned, the entire table is considered a single partition named |
| is_index_back | Indicates whether the operator needs to perform a "back to table" operation. | is_index_back=false indicates that no back to table operation is needed. Since Q5 directly performs a full table scan on the main table (TABLE FULL SCAN) without using an index, no back to table operation is required. |
| is_global_index | Indicates whether the query uses a global index. | is_global_index=false indicates that the scan is not on a global index. The scan is performed on the table itself (main table). |
| filter_before_indexback | A boolean array that corresponds to the conditions in filter, indicating whether the filter can be calculated directly on the index or needs to be calculated after a back to table operation. |
filter_before_indexback[false] indicates that the filter condition col1 = 1 cannot be calculated before a back to table operation. Since Q5 performs a main table scan without involving an index, this parameter is false. |
| range_key | The row key (rowkey) column that determines the sorting order of the scanned data. For a table (main table), the row key is the primary key; for an index, the row key is the index key. |
range_key([TBL5.__pk_increment]) indicates that the rowkey column being scanned is __pk_increment. This is a hidden primary key column automatically generated by OceanBase Database for tables without a primary key. |
| range | The start and end range of the index scan, in the format (start key value ; end key value). |
range(MIN ; MAX)always true indicates that the scan range spans from the minimum to the maximum row key value, and this condition is always true. This is a typical characteristic of a full table scan. |
