逆置与Like匹配

声明:本文受《DBA日记》启发而成。

 

Like匹配是SQL语句的一种重要功能。Like结合通配符使用可以实现模糊查询,解决字符串匹配问题。当查询的数据集合比较大的时候,我们常常希望借助索引来加快检索效率。

 

默认的索引行为

 

like查询是否会执行索引路径,很大程度上取决于使用的通配符结构。如果我们仔细分析下B*树索引的结构,就可以判断出:‘XXX%’类型的匹配结构,是可以执行索引搜索的。

 

通过下面实验证明:

 

//构造原始数据集合

SQL> create table t as select * from dba_objects;

 

Table created

//owner列上建索引

SQL> create index idx_t_owner on t(owner);

 

Index created

 

//收集统计信息

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

 

PL/SQL procedure successfully completed

 

 

此时,我们使用通配符进行查询。

 

SQL> select * from t where owner like 'PE%';

 

已选择139行。

 

已用时间:  00: 00: 00.06

 

执行计划

----------------------------------------------------------

Plan hash value: 1516787156

-------------------------------------------------------------------------------------------

| Id  | Operation                   | Name        | Rows  | Bytes | Cost (%CPU)| Time     |

-------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT            |             |  2231 |   204K|    67   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| T           |  2231 |   204K|    67   (0)| 00:00:01 |

|*  2 |   INDEX RANGE SCAN          | IDX_T_OWNER |  2231 |       |     7   (0)| 00:00:01 |

-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   2 - access("OWNER" LIKE 'PE%')

       filter("OWNER" LIKE 'PE%')

 

统计信息

----------------------------------------------------------

        381  recursive calls

          0  db block gets

         82  consistent gets

          0  physical reads

          0  redo size

      16321  bytes sent via SQL*Net to client

        484  bytes received via SQL*Net from client

         11  SQL*Net roundtrips to/from client

          3  sorts (memory)

          0  sorts (disk)

        139  rows processed

 

发现,虽然使用了like,但是还是执行了索引路径。

 

结论:在匹配字段通配符不是在开头的情况下,like走索引列的效率比较高,往往优化器会选择这条路径。

 

 

通配符在开头

 

在一些情况下,我们需要进行通配符在开头的检索。这种情况下,使用一般的索引一般是不会被选择为实际执行路径的。

 

SQL> select * from t where owner like '%AT';

 

已选择139行。

 

已用时间:  00: 00: 00.12

 

执行计划

----------------------------------------------------------

Plan hash value: 1601196873

--------------------------------------------------------------------------

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |--------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |      |  2666 |   255K|   165   (2)| 00:00:02 |

|*  1 |  TABLE ACCESS FULL| T    |  2666 |   255K|   165   (2)| 00:00:02 |

--------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - filter("OWNER" LIKE '%AT')

统计信息

----------------------------------------------------------

        838  recursive calls

          0  db block gets

        912  consistent gets

          1  physical reads

          0  redo size

      10424  bytes sent via SQL*Net to client

        484  bytes received via SQL*Net from client

         11  SQL*Net roundtrips to/from client

         24  sorts (memory)

          0  sorts (disk)

        139  rows processed

 

Oracle优化器选择了全表扫描作为执行路径。应该说,在索引和全表扫描两种方式下,Oracle选择了全表扫描,原因是因为索引路径的成本Cost更高。

 

 

思路:如果匹配的字符串前面不存在通配符,走索引的路径成本相对较低了。那么,怎么将其转化到后面呢?使用逆置的函数索引。

 

函数索引的原理,就是利用函数处理出一个隐藏列,再对隐藏列进行索引处理。

 

SQL> create index idx_t_fun on t(reverse(owner));

 

Index created

 

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

 

PL/SQL procedure successfully completed

 

同时,在搜索的时候,要将匹配的字符串也逆置处理。例如,如果要处理匹配的字符串是’%AT’。那么进行函数匹配的时候,就要使用‘TA%’

 

SQL> select * from t where reverse(owner) like reverse('%AT');

 

已选择139行。

 

已用时间:  00: 00: 00.04

 

执行计划

----------------------------------------------------------

Plan hash value: 3350694433

 

-----------------------------------------------------------------------------------------

| Id  | Operation                   | Name      | Rows  | Bytes | Cost (%CPU)| Time     |

-----------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT            |           |  2132 |   204K|    64   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| T         |  2132 |   204K|    64   (0)| 00:00:01 |

|*  2 |   INDEX RANGE SCAN          | IDX_T_FUN |  2132 |       |     7   (0)| 00:00:01 |

-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   2 - access(REVERSE("OWNER") LIKE 'TA%')

       filter(REVERSE("OWNER") LIKE 'TA%')

 

统计信息

----------------------------------------------------------

        117  recursive calls

          0  db block gets

         52  consistent gets

          0  physical reads

          0  redo size

      10424  bytes sent via SQL*Net to client

        484  bytes received via SQL*Net from client

         11  SQL*Net roundtrips to/from client

          3  sorts (memory)

          0  sorts (disk)

        139  rows processed

 

修改之后,发现执行路径已经可以走索引路径了。而且执行时间也缩小到原来全表扫描的1/3

 

 

 

请使用浏览器的分享功能分享到微信等